API reference
The full request surface: authentication, the two run modes, 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-Function header picks
proxy (forward upstream) or rewrite (return
the rewritten body). 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/json
OpenAI
httpPOST /openai/v1/chat/completions
X-Condense-Auth-Token: ak_<your-condense-key>
Authorization: Bearer sk-<your-openai-key>
Content-Type: application/json
proxy vs. rewrite
The same pipeline runs for both; only the last step differs.
proxy (default)
|
Forward the rewritten body to the upstream provider and stream the response back to you. You pay for the upstream call. |
|---|---|
rewrite |
Return the rewritten body as JSON. No upstream call, no upstream cost. Useful for debugging which messages got packed, or for piping the result into a different runtime. |
bashcurl 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 "X-Condense-Function: rewrite" \
-d '{"model":"claude-haiku-4-5","max_tokens":256,"messages":[...]}'
rewrite must be enabled on your account.
Calls without the entitlement return 403.
In rewrite mode, a model that names a
condense compressor (e.g. helene-1)
selects it for that request. Any other
model keeps its provider meaning and your
account's default compressor runs. To compress a transcript without a
provider request shape 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-Function |
proxy (default) or rewrite. Unknown
values fall back to proxy.
|
| 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
Six short guides, one per provider × function.
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.
The two condense-managed functions:
- proxy: condense compresses your messages, forwards to the upstream provider, and returns the provider's normal completion. You pay for the upstream call. This is the default and what most callers want.
- rewrite: condense returns the compressed request body as JSON without contacting the upstream. No upstream key needed. Use it to inspect what condense would send, or to drive the upstream call yourself.
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 SystemExit
Same 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 SystemExit
Two-step: condense rewrites your body and hands it back without
calling upstream, then you forward it yourself. Header switch is
X-Condense-Function: rewrite. No
upstream key needed for the first call. Useful when you want to
inspect, log, or batch the compressed payload before spending
tokens.
bash# step 1: get the compressed body
curl https://api.condense.chat/openai/v1/chat/completions \
-H "X-Condense-Auth-Token: $CONDENSE_KEY" \
-H "X-Condense-Function: rewrite" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}' \
> rewritten.json
# step 2: forward it to OpenAI yourself
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @rewritten.json
python# tool loop where every turn is rewrite-then-forward
import json, os
from urllib.request import Request, urlopen
REWRITE = "https://api.condense.chat/openai/v1/chat/completions"
UPSTREAM = "https://api.openai.com/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 turn(messages):
body = json.dumps({"model":"gpt-4o-mini","messages":messages,"tools":TOOLS,"tool_choice":"auto"}).encode()
rewritten = urlopen(Request(REWRITE, method="POST", data=body, headers={
"X-Condense-Auth-Token": CON,
"X-Condense-Function": "rewrite",
"Content-Type": "application/json",
})).read()
return json.loads(urlopen(Request(UPSTREAM, method="POST", data=rewritten, headers={
"Authorization": f"Bearer {OAI}",
"Content-Type": "application/json",
})).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
msg = turn(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 SystemExit
Baseline: 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})
Anthropic two-step. The compressed body comes back from condense
and you forward it to
api.anthropic.com yourself.
x-api-key and
anthropic-version are only needed on
the upstream forward; the rewrite step takes the condense key
alone.
bash# step 1: get the compressed body
curl https://api.condense.chat/anthropic/v1/messages \
-H "X-Condense-Auth-Token: $CONDENSE_KEY" \
-H "X-Condense-Function: rewrite" \
-H "Content-Type: application/json" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}' \
> rewritten.json
# step 2: forward it to Anthropic yourself
curl 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 @rewritten.json
pythonimport json, os
from urllib.request import Request, urlopen
REWRITE = "https://api.condense.chat/anthropic/v1/messages"
UPSTREAM = "https://api.anthropic.com/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 turn(messages):
body = json.dumps({"model":"claude-haiku-4-5-20251001","max_tokens":1024,"messages":messages,"tools":TOOLS}).encode()
rewritten = urlopen(Request(REWRITE, method="POST", data=body, headers={
"X-Condense-Auth-Token": CON,
"X-Condense-Function": "rewrite",
"Content-Type": "application/json",
})).read()
return json.loads(urlopen(Request(UPSTREAM, method="POST", data=rewritten, headers={
"x-api-key": ANT,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
})).read())
messages = [{"role":"user","content":"Compute (17 + 25) * 3 using add and mul, then call final_answer."}]
for _ in range(6):
resp = turn(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.
direct and rewrite modes aren'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 this
pythonimport 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
rewrite capability on your account;
returns 403 otherwise.
Request body
model
string
Required
helene-1 or adeline-1. An unknown or
undeployed name returns 400 with the list of
currently selectable compressors.
messages
array
Required
{"role", "content"}). A message the compressor abstains on (empty, too short, or
quality-gated) comes back with its original content unchanged.
compression_rate
number
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: rewrite, 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.