On this page
Get started
dense CLI
API reference
API reference
The full request surface: authentication, upstream selection, per-request headers, endpoints, and error shapes.
Introduction
condense.chat sits between your app and your LLM provider. Point your SDK at a provider route, add your condense key, keep your provider key: condense compacts the repeated context in each request before forwarding it upstream, with the same request and response shapes.
Choose the surface for your app:
- Provider routes are drop-in for the Anthropic and OpenAI SDKs. Point
base_urlat/anthropicor/openai/v1and everything else stays the same. /v1/compresscompacts a transcript directly and hands it back, with no upstream call. Pick a model per request.- Pass-through forwards any other provider path (a models list, embeddings) verbatim.
Base URL: https://api.condense.chat. The dialect is selected by the path prefix; the X-Condense-Upstream-Url header selects where the compacted request is forwarded. New here? Start with the Quickstart, choose a model on Models, and review Rate limits before production.
Authentication
Two keys travel on every request: your condense key (gates access to condense.chat) and your upstream provider key (charged for the model call). We never store the upstream key, only a sha256 fingerprint in the usage ledger.
The condense API key (ak_…) always travels in the X-Condense-Auth-Token header, for both providers. The upstream key goes in whatever header the provider expects: x-api-key for Anthropic, Authorization: Bearer for OpenAI. Using a custom header for the condense key means Authorization is always available for the upstream, with no precedence conflicts in any SDK.
Anthropic
httpPOST /anthropic/v1/messages
X-Condense-Auth-Token: ak_<your-condense-key>
x-api-key: sk-ant-<your-anthropic-key>
anthropic-version: 2023-06-01
Content-Type: application/jsonOpenAI
httpPOST /openai/v1/chat/completions
X-Condense-Auth-Token: ak_<your-condense-key>
Authorization: Bearer sk-<your-openai-key>
Content-Type: application/jsonBring your own upstream
By default a provider route forwards to that provider's own API: /anthropic/… to api.anthropic.com, /openai/… to api.openai.com. Send X-Condense-Upstream-Url to forward to something else instead — any endpoint that speaks the OpenAI or Anthropic request shape. A self-hosted gateway, a vLLM or router deployment, another vendor's compatible API: condense compacts the conversation exactly as it always does, then forwards there.
| Anthropic routes | Send the host root (https://host/prefix). condense appends /v1/messages. |
|---|---|
| OpenAI routes | Send the base including /v1 (https://host/v1), matching the SDK base_url convention. condense appends /chat/completions. |
bashcurl https://api.condense.chat/openai/v1/chat/completions \
-H "X-Condense-Auth-Token: ak_..." \
-H "Authorization: Bearer sk-..." \
-H "X-Condense-Upstream-Url: https://my-gateway.example.com/v1" \
-d '{"model":"my-model","messages":[...]}'The URL must be https with a public host; private and loopback addresses are rejected. If the upstream takes a different credential than the provider would, put it in X-Condense-Upstream-Key and it is used in place of whatever arrived in Authorization / x-api-key.
Overriding the upstream must be enabled on your account. Calls without the entitlement return 403.
This header is the whole story for pointing condense somewhere else — there is no second mechanism. To compress a transcript without any upstream call at all, use POST /v1/compress.
Per-request headers
| Header | Effect |
|---|---|
| X-Condense-Auth-Token | Required. Your condense API key (ak_…). Used for both Anthropic and OpenAI paths. |
| X-Condense-Upstream-Url | Forward to this base instead of the provider default. Must be https with a public host. See Bring your own upstream. |
| X-Condense-Upstream-Key | Credential for the upstream, overriding whatever arrived in Authorization / x-api-key. Useful when your own upstream takes a different key than the provider. |
| X-Condense-Session-Id | Optional UUID. Groups requests into a session so the dashboard can show per-session savings. The dense CLI sets this for you. |
| Authorization / x-api-key | Your upstream provider key (Anthropic uses x-api-key, OpenAI uses Authorization: Bearer). Forwarded verbatim; never stored. |
condense adds no custom response headers. Upstream response headers come back as-is, minus hop-by-hop headers and content-encoding (bodies are re-encoded in flight).
Worked examples
Short guides, one per provider × mode. Each is self-contained: a paragraph of what the mode does, the smallest possible curl that exercises it, then the same call in Python (no SDK, just urllib) wired into a three-turn tool loop that computes (17 + 25) * 3. Pick a tab; copy the snippet; replace the keys; run.
proxy is the condense path: condense compresses your messages, forwards to the upstream, and returns the provider's normal completion. You pay for the upstream call. This is the default and what most callers want.
direct tabs are baselines. They don't touch condense at all, included so you can compare and see exactly which headers change.
Baseline: talk to OpenAI directly. Sends one OpenAI key in Authorization; no condense involvement. Use this as the control variant when comparing against the proxy tab.
bash# single-turn smoke
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'python# three-turn tool loop; pip-free
import json, os
from urllib.request import Request, urlopen
URL = "https://api.openai.com/v1/chat/completions"
KEY = os.environ["OPENAI_API_KEY"]
TOOLS = [
{"type":"function","function":{"name":"add","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}}},
{"type":"function","function":{"name":"mul","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}}},
{"type":"function","function":{"name":"final_answer","parameters":{"type":"object","properties":{"text":{"type":"string"}}}},
]
def tool(name, args):
if name == "add": return str(args["a"] + args["b"])
if name == "mul": return str(args["a"] * args["b"])
return args["text"]
def post(messages):
body = json.dumps({"model":"gpt-4o-mini","messages":messages,"tools":TOOLS,"tool_choice":"auto"}).encode()
req = Request(URL, method="POST", data=body, headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
})
return json.loads(urlopen(req).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
msg = post(messages)["choices"][0]["message"]
if not msg.get("tool_calls"): print(msg.get("content") or ""); break
messages.append(msg)
for tc in msg["tool_calls"]:
args = json.loads(tc["function"]["arguments"] or "{}")
result = tool(tc["function"]["name"], args)
print(f"{tc['function']['name']}({args}) -> {result}")
messages.append({"role":"tool","tool_call_id":tc["id"],"content":result})
if tc["function"]["name"] == "final_answer": raise SystemExitSame shape as direct, but the URL is condense's /openai/v1/chat/completions, the condense key rides in X-Condense-Auth-Token, and your OpenAI key stays in Authorization. Condense compresses, forwards, streams back the upstream response. Your code doesn't change.
bashcurl https://api.condense.chat/openai/v1/chat/completions \
-H "X-Condense-Auth-Token: $CONDENSE_KEY" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'python# identical to the direct example, only URL + headers change
import json, os
from urllib.request import Request, urlopen
URL = "https://api.condense.chat/openai/v1/chat/completions"
OAI = os.environ["OPENAI_API_KEY"]
CON = os.environ["CONDENSE_KEY"]
TOOLS = [
{"type":"function","function":{"name":"add","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}}},
{"type":"function","function":{"name":"mul","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}}},
{"type":"function","function":{"name":"final_answer","parameters":{"type":"object","properties":{"text":{"type":"string"}}}},
]
def tool(name, args):
if name == "add": return str(args["a"] + args["b"])
if name == "mul": return str(args["a"] * args["b"])
return args["text"]
def post(messages):
body = json.dumps({"model":"gpt-4o-mini","messages":messages,"tools":TOOLS,"tool_choice":"auto"}).encode()
req = Request(URL, method="POST", data=body, headers={
"X-Condense-Auth-Token": CON,
"Authorization": f"Bearer {OAI}",
"Content-Type": "application/json",
})
return json.loads(urlopen(req).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
msg = post(messages)["choices"][0]["message"]
if not msg.get("tool_calls"): print(msg.get("content") or ""); break
messages.append(msg)
for tc in msg["tool_calls"]:
args = json.loads(tc["function"]["arguments"] or "{}")
result = tool(tc["function"]["name"], args)
print(f"{tc['function']['name']}({args}) -> {result}")
messages.append({"role":"tool","tool_call_id":tc["id"],"content":result})
if tc["function"]["name"] == "final_answer": raise SystemExitBaseline: talk to Anthropic directly. The Anthropic dialect puts the upstream key in x-api-key (not Authorization) and requires the anthropic-version header. Tools are typed; the model's tool calls come back as tool_use blocks; tool results go back as tool_result blocks.
bashcurl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}'pythonimport json, os
from urllib.request import Request, urlopen
URL = "https://api.anthropic.com/v1/messages"
KEY = os.environ["ANTHROPIC_API_KEY"]
TOOLS = [
{"name":"add","input_schema":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},
{"name":"mul","input_schema":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},
{"name":"final_answer","input_schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]},
]
def tool(name, args):
if name == "add": return str(args["a"] + args["b"])
if name == "mul": return str(args["a"] * args["b"])
return args["text"]
def post(messages):
body = json.dumps({
"model":"claude-haiku-4-5-20251001","max_tokens":1024,
"messages":messages,"tools":TOOLS,
}).encode()
req = Request(URL, method="POST", data=body, headers={
"x-api-key": KEY,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
})
return json.loads(urlopen(req).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
resp = post(messages)
blocks = resp["content"]
messages.append({"role":"assistant","content":blocks})
calls = [b for b in blocks if b["type"] == "tool_use"]
if not calls:
print("".join(b.get("text", "") for b in blocks)); break
results = []
for b in calls:
result = tool(b["name"], b["input"])
print(f"{b['name']}({b['input']}) -> {result}")
results.append({"type":"tool_result","tool_use_id":b["id"],"content":result})
if b["name"] == "final_answer": raise SystemExit
messages.append({"role":"user","content":results})Anthropic-dialect proxy. The condense key rides in X-Condense-Auth-Token (same as the OpenAI path); your Anthropic key stays in x-api-key. Streaming ("stream": true) works identically to the direct path.
bashcurl https://api.condense.chat/anthropic/v1/messages \
-H "X-Condense-Auth-Token: $CONDENSE_KEY" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}'pythonimport json, os
from urllib.request import Request, urlopen
URL = "https://api.condense.chat/anthropic/v1/messages"
ANT = os.environ["ANTHROPIC_API_KEY"]
CON = os.environ["CONDENSE_KEY"]
TOOLS = [
{"name":"add","input_schema":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},
{"name":"mul","input_schema":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},
{"name":"final_answer","input_schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]},
]
def tool(name, args):
if name == "add": return str(args["a"] + args["b"])
if name == "mul": return str(args["a"] * args["b"])
return args["text"]
def post(messages):
body = json.dumps({"model":"claude-haiku-4-5-20251001","max_tokens":1024,"messages":messages,"tools":TOOLS}).encode()
req = Request(URL, method="POST", data=body, headers={
"X-Condense-Auth-Token": CON,
"x-api-key": ANT,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
})
return json.loads(urlopen(req).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
resp = post(messages)
blocks = resp["content"]
messages.append({"role":"assistant","content":blocks})
calls = [b for b in blocks if b["type"] == "tool_use"]
if not calls:
print("".join(b.get("text", "") for b in blocks)); break
results = []
for b in calls:
result = tool(b["name"], b["input"])
print(f"{b['name']}({b['input']}) -> {result}")
results.append({"type":"tool_result","tool_use_id":b["id"],"content":result})
if b["name"] == "final_answer": raise SystemExit
messages.append({"role":"user","content":results})claude-agent-sdk-python is the official Anthropic SDK for spawning long-running tool-using agents. It shells out to claude under the hood, so all you need to do is point its ANTHROPIC_BASE_URL at condense's /anthropic prefix and pass your condense key via the SDK's env option. The direct mode isn't relevant here: the agent loop needs upstream completions.
bash# install once
pip install claude-agent-sdk
npm install -g @anthropic-ai/claude-code # the SDK shells out to thispythonimport anyio, os
from claude_agent_sdk import query, ClaudeAgentOptions
opts = ClaudeAgentOptions(env={
"ANTHROPIC_BASE_URL": "https://api.condense.chat/anthropic",
"ANTHROPIC_CUSTOM_HEADERS": f"X-Condense-Auth-Token: {os.environ['CONDENSE_KEY']}",
"ANTHROPIC_API_KEY": os.environ["ANT_API_TOKEN"],
})
async def main():
async for msg in query(prompt="Compute (17 + 25) * 3 step by step.", options=opts):
print(msg)
anyio.run(main)Every tool turn passes through condense, which compresses older context before forwarding upstream. To set additional per-request headers for one session (e.g. X-Condense-Session-Id), append them to ANTHROPIC_CUSTOM_HEADERS.
Endpoints
Compress a transcript and get it back in the same shape: N messages in, N messages out, each compressed independently with its role and order preserved. No conversation tracking, no upstream model call. Requires the compress capability on your account; returns 403 otherwise.
Request body
modelstringRequired
helene-1 or adeline-1. An unknown or undeployed name returns 400 with the list of currently selectable compressors.messagesarrayRequired
{"role", "content"}). A message the compressor abstains on (empty, too short, or quality-gated) comes back with its original content unchanged.compression_ratenumber
helene-1 only. Fraction of tokens to remove, between 0 and 1. Omit to let the model pick the rate. Out-of-range values return 400.import httpx
resp = httpx.post(
"https://api.condense.chat/v1/compress",
headers={"X-Condense-Auth-Token": "ak_..."},
json={
"model": "helene-1",
"compression_rate": 0.6,
"messages": [
{"role": "system", "content": "You are a terse assistant."},
{"role": "user", "content": "<a long transcript to compress>"},
],
},
)
print(resp.json())const resp = await fetch("https://api.condense.chat/v1/compress", {
method: "POST",
headers: {
"X-Condense-Auth-Token": "ak_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "helene-1",
compression_rate: 0.6,
messages: [
{ role: "system", content: "You are a terse assistant." },
{ role: "user", content: "<a long transcript to compress>" },
],
}),
});
console.log(await resp.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"model":"helene-1","compression_rate":0.6,"messages":[{"role":"system","content":"You are a terse assistant."},{"role":"user","content":"<a long transcript to compress>"}]}`)
req, _ := http.NewRequest("POST", "https://api.condense.chat/v1/compress", bytes.NewReader(body))
req.Header.Set("X-Condense-Auth-Token", "ak_...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = Client::new()
.post("https://api.condense.chat/v1/compress")
.header("X-Condense-Auth-Token", "ak_...")
.json(&json!({
"model": "helene-1",
"compression_rate": 0.6,
"messages": [
{"role": "system", "content": "You are a terse assistant."},
{"role": "user", "content": "<a long transcript to compress>"}
]
}))
.send().await?;
println!("{}", resp.text().await?);
Ok(())
}curl https://api.condense.chat/v1/compress \
-H "X-Condense-Auth-Token: ak_..." \
-H "Content-Type: application/json" \
-d '{
"model": "helene-1",
"compression_rate": 0.6,
"messages": [
{"role": "system", "content": "You are a terse assistant."},
{"role": "user", "content": "<a long transcript to compress>"}
]
}'json{
"model": "helene-1",
"messages": [
{"role": "system", "content": "terse assistant."},
{"role": "user", "content": "<surviving text, verbatim>"}
]
}Identical request and response shape to api.anthropic.com/v1/messages. Streaming (stream: true) is preserved end-to-end; all anthropic-* headers (prompt caching, betas) forward through unchanged.
import httpx
resp = httpx.post(
"https://api.condense.chat/anthropic/v1/messages",
headers={"X-Condense-Auth-Token": "ak_...", "x-api-key": "sk-ant-...", "anthropic-version": "2023-06-01"},
json={"model": "claude-haiku-4-5", "max_tokens": 256, "messages": [{"role": "user", "content": "hi"}]},
)
print(resp.json())const resp = await fetch("https://api.condense.chat/anthropic/v1/messages", {
method: "POST",
headers: {
"X-Condense-Auth-Token": "ak_...",
"x-api-key": "sk-ant-...",
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "claude-haiku-4-5", max_tokens: 256, messages: [{ role: "user", content: "hi" }] }),
});
console.log(await resp.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"model":"claude-haiku-4-5","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}`)
req, _ := http.NewRequest("POST", "https://api.condense.chat/anthropic/v1/messages", bytes.NewReader(body))
req.Header.Set("X-Condense-Auth-Token", "ak_...")
req.Header.Set("x-api-key", "sk-ant-...")
req.Header.Set("anthropic-version", "2023-06-01")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = Client::new()
.post("https://api.condense.chat/anthropic/v1/messages")
.header("X-Condense-Auth-Token", "ak_...")
.header("x-api-key", "sk-ant-...")
.header("anthropic-version", "2023-06-01")
.header("Content-Type", "application/json")
.json(&json!({ model: "claude-haiku-4-5", max_tokens: 256, messages: [{ role: "user", content: "hi" }] }))
.send().await?;
println!("{}", resp.text().await?);
Ok(())
}curl https://api.condense.chat/anthropic/v1/messages \
-H "X-Condense-Auth-Token: ak_..." \
-H "x-api-key: sk-ant-..." \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-haiku-4-5","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}'Identical request and response shape to api.openai.com/v1/chat/completions. Streaming is preserved; tool calls round-trip.
import httpx
resp = httpx.post(
"https://api.condense.chat/openai/v1/chat/completions",
headers={"X-Condense-Auth-Token": "ak_...", "Authorization": "Bearer sk-..."},
json={"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "hi"}]},
)
print(resp.json())const resp = await fetch("https://api.condense.chat/openai/v1/chat/completions", {
method: "POST",
headers: {
"X-Condense-Auth-Token": "ak_...",
"Authorization": "Bearer sk-...",
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hi" }] }),
});
console.log(await resp.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}`)
req, _ := http.NewRequest("POST", "https://api.condense.chat/openai/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("X-Condense-Auth-Token", "ak_...")
req.Header.Set("Authorization", "Bearer sk-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = Client::new()
.post("https://api.condense.chat/openai/v1/chat/completions")
.header("X-Condense-Auth-Token", "ak_...")
.header("Authorization", "Bearer sk-...")
.header("Content-Type", "application/json")
.json(&json!({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hi" }] }))
.send().await?;
println!("{}", resp.text().await?);
Ok(())
}curl https://api.condense.chat/openai/v1/chat/completions \
-H "X-Condense-Auth-Token: ak_..." \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'Drop-in for the OpenAI Responses API (used by Codex). Same request and response shape as api.openai.com/v1/responses.
import httpx
resp = httpx.post(
"https://api.condense.chat/openai/v1/responses",
headers={"X-Condense-Auth-Token": "ak_...", "Authorization": "Bearer sk-..."},
json={"model": "gpt-4o-mini", "input": "hi"},
)
print(resp.json())const resp = await fetch("https://api.condense.chat/openai/v1/responses", {
method: "POST",
headers: {
"X-Condense-Auth-Token": "ak_...",
"Authorization": "Bearer sk-...",
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "gpt-4o-mini", input: "hi" }),
});
console.log(await resp.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
body := []byte(`{"model":"gpt-4o-mini","input":"hi"}`)
req, _ := http.NewRequest("POST", "https://api.condense.chat/openai/v1/responses", bytes.NewReader(body))
req.Header.Set("X-Condense-Auth-Token", "ak_...")
req.Header.Set("Authorization", "Bearer sk-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = Client::new()
.post("https://api.condense.chat/openai/v1/responses")
.header("X-Condense-Auth-Token", "ak_...")
.header("Authorization", "Bearer sk-...")
.header("Content-Type", "application/json")
.json(&json!({ model: "gpt-4o-mini", input: "hi" }))
.send().await?;
println!("{}", resp.text().await?);
Ok(())
}curl https://api.condense.chat/openai/v1/responses \
-H "X-Condense-Auth-Token: ak_..." \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","input":"hi"}'Any other path under a provider prefix (a models list, embeddings, and so on) is forwarded to the upstream unchanged. The compression pipeline does not touch it; this is purely an SDK-compat convenience so one base URL covers the whole provider surface.
import httpx
resp = httpx.get(
"https://api.condense.chat/openai/v1/models",
headers={"X-Condense-Auth-Token": "ak_...", "Authorization": "Bearer sk-..."},
)
print(resp.json())const resp = await fetch("https://api.condense.chat/openai/v1/models", {
method: "GET",
headers: {
"X-Condense-Auth-Token": "ak_...",
"Authorization": "Bearer sk-...",
},
});
console.log(await resp.json());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.condense.chat/openai/v1/models", nil)
req.Header.Set("X-Condense-Auth-Token", "ak_...")
req.Header.Set("Authorization", "Bearer sk-...")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = Client::new()
.get("https://api.condense.chat/openai/v1/models")
.header("X-Condense-Auth-Token", "ak_...")
.header("Authorization", "Bearer sk-...")
.send().await?;
println!("{}", resp.text().await?);
Ok(())
}curl https://api.condense.chat/openai/v1/models \
-H "X-Condense-Auth-Token: ak_..." \
-H "Authorization: Bearer sk-..."Debugging requests
Failures come back as standard HTTP status codes with the dialect's normal error body; the full breakdown is in Errors below. Two things help when something goes wrong:
- Rate limits. A
429carries aRetry-Afterheader, retry after that delay with backoff. See Rate limits. - Request id. Every request is tagged with an internal condense id (
cx_…) that we log for troubleshooting. If a request misbehaves, note the time (and the request details if you have them) and reach us via contact, we can correlate it on our side.
Compression never breaks a call on its own: if the condense pipeline hits an internal error mid-request, the proxy forwards your original body upstream unchanged and the request completes without compression.
Backwards compatibility
condense avoids breaking changes. The request and response shapes are the provider's own (Anthropic / OpenAI), so upgrading condense never changes the wire format your SDK expects. Model ids are stable aliases: helene-1 and adeline-1 keep pointing at the current best engine while internal versions move underneath.
Changes we make freely, without notice:
- adding new routes, optional request fields, and response fields;
- adding new models behind new aliases;
- changing the format of opaque identifiers (keys, request ids).
Retirements are announced ahead of time on Deprecations, and notable changes land in the Changelog.
Errors
Errors follow standard HTTP status codes. Bodies are the dialect's native error JSON for upstream-origin failures (so SDK error parsers keep working); condense-origin failures use a small {"error": {"type": "...", "message": "..."}} shape.
| Status | Meaning |
|---|---|
| 400 | Bad request body, a malformed Authorization header, or an unknown model / out-of-range compression_rate on /v1/compress. |
| 401 | Missing or invalid condense key. |
| 403 | Your key is valid but the account isn't entitled to what the request asked for: compress, the proxy itself, or an upstream URL override. |
| 429 | Rate-limited. The Retry-After response header says how many seconds to wait. |
| 5xx | Upstream provider error. The upstream status and body are forwarded verbatim. |
Compression itself never breaks a conversation: if the condense pipeline hits an internal error mid-request, the proxy forwards your original body upstream unchanged and the call completes without compression.