On this page

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_url at /anthropic or /openai/v1 and everything else stays the same.
  • /v1/compress compacts 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/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

Bring 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 routesSend the host root (https://host/prefix). condense appends /v1/messages.
OpenAI routesSend 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

HeaderEffect
X-Condense-Auth-TokenRequired. Your condense API key (ak_…). Used for both Anthropic and OpenAI paths.
X-Condense-Upstream-UrlForward to this base instead of the provider default. Must be https with a public host. See Bring your own upstream.
X-Condense-Upstream-KeyCredential 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-IdOptional UUID. Groups requests into a session so the dashboard can show per-session savings. The dense CLI sets this for you.
Authorization / x-api-keyYour 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.

Provider
Mode

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})

Endpoints

POST/v1/compressdirect compression · no upstream call

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
The compressor to run, by public name, e.g. helene-1 or adeline-1. An unknown or undeployed name returns 400 with the list of currently selectable compressors.
messagesarrayRequired
The transcript, in OpenAI chat shape ({"role", "content"}). A message the compressor abstains on (empty, too short, or quality-gated) comes back with its original content unchanged.
compression_ratenumber
Optional, 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.
POST /v1/compress
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())
Response
json{
  "model": "helene-1",
  "messages": [
    {"role": "system", "content": "terse assistant."},
    {"role": "user", "content": "<surviving text, verbatim>"}
  ]
}
POST/anthropic/v1/messagesAnthropic dialect · chain-tracking proxy

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.

POST /anthropic/v1/messages
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())
Response is Anthropic's, verbatim.
POST/openai/v1/chat/completionsOpenAI dialect · chain-tracking proxy

Identical request and response shape to api.openai.com/v1/chat/completions. Streaming is preserved; tool calls round-trip.

POST /openai/v1/chat/completions
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())
Response is OpenAI's, verbatim.
POST/openai/v1/responsesOpenAI Responses dialect · Codex

Drop-in for the OpenAI Responses API (used by Codex). Same request and response shape as api.openai.com/v1/responses.

POST /openai/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())
Response is OpenAI's, verbatim.
GET/{provider}/…pass-through · forwarded verbatim

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.

GET /{provider}/…
import httpx

resp = httpx.get(
    "https://api.condense.chat/openai/v1/models",
    headers={"X-Condense-Auth-Token": "ak_...", "Authorization": "Bearer sk-..."},
)
print(resp.json())
Response is the provider's, verbatim.

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 429 carries a Retry-After header, 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.

StatusMeaning
400Bad request body, a malformed Authorization header, or an unknown model / out-of-range compression_rate on /v1/compress.
401Missing or invalid condense key.
403Your key is valid but the account isn't entitled to what the request asked for: compress, the proxy itself, or an upstream URL override.
429Rate-limited. The Retry-After response header says how many seconds to wait.
5xxUpstream 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.