Technical reference
One key, one base URL, and you are behind it.
Outcap is a proxy: your official SDK keeps working as it is, only the base URL and two headers change. The first half of this page gets a call through. The second is the header and code reference, because that is how you see what Outcap did with a request.
Everything starts in shadow mode. No request is modified until you turn something on, route by route, and the single exception announces itself with a response header.
Step 1
An Outcap key
Create an account, then generate a key (oc_…) from the dashboard. It identifies your project and nothing else. Your provider key stays yours: it travels in a header on every request, and is neither logged nor stored.
Step 2
Change the base URL
One format per provider, with no translation between the two. The path of your calls does not change: the host changes, and your SDK appends the rest as usual.
| Format | Base URL | What your SDK calls |
|---|---|---|
| OpenAI | https://proxy.outcap.tech/v1 | POST /v1/chat/completions |
| Anthropic | https://proxy.outcap.tech | POST /v1/messages |
Two convenience passthroughs exist as well, with no logging and no budget: GET /v1/models and POST /v1/messages/count_tokens. With several provider keys, they use only the first one.
Step 3
Your SDK, unchanged
No package to install, no client to replace. The four official combinations, to copy as they are.
OpenAI · JavaScript and TypeScript
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: "https://proxy.outcap.tech/v1",
apiKey: process.env.OUTCAP_KEY, // oc_…
defaultHeaders: {
"x-provider-key": process.env.OPENAI_API_KEY, // sk-… never stored
"x-outcap-route": "support-bot", // recommended
},
});OpenAI · Python
from openai import OpenAI
client = OpenAI(
base_url="https://proxy.outcap.tech/v1",
api_key=os.environ["OUTCAP_KEY"],
default_headers={
"x-provider-key": os.environ["OPENAI_API_KEY"],
"x-outcap-route": "support-bot",
},
)Anthropic · JavaScript and TypeScript
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
baseURL: "https://proxy.outcap.tech",
apiKey: process.env.OUTCAP_KEY,
defaultHeaders: {
"x-provider-key": process.env.ANTHROPIC_API_KEY,
"x-outcap-route": "extractor",
},
});Anthropic · Python
import anthropic
client = anthropic.Anthropic(
base_url="https://proxy.outcap.tech",
api_key=os.environ["OUTCAP_KEY"],
default_headers={
"x-provider-key": os.environ["ANTHROPIC_API_KEY"],
"x-outcap-route": "extractor",
},
)Step 4
Agent frameworks
An autonomous agent is exactly the traffic you want capped: it loops, it runs away, and nobody is watching. These frameworks all accept a base URL and custom headers, so Outcap plugs in by changing the model configuration.
LangChain · ChatOpenAI
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4o-mini",
base_url="https://proxy.outcap.tech/v1",
api_key=os.environ["OUTCAP_KEY"],
default_headers={
"x-provider-key": os.environ["OPENAI_API_KEY"],
"x-outcap-route": "agent-research",
},
)LangChain · ChatAnthropic
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(
model="claude-haiku-4-5",
base_url="https://proxy.outcap.tech",
api_key=os.environ["OUTCAP_KEY"],
default_headers={
"x-provider-key": os.environ["ANTHROPIC_API_KEY"],
"x-outcap-route": "agent-research",
},
)CrewAI · LLM
from crewai import LLM
# CrewAI goes through the OpenAI format, Outcap sits in front
llm = LLM(
model="openai/gpt-4o-mini",
base_url="https://proxy.outcap.tech/v1",
api_key=os.environ["OUTCAP_KEY"],
extra_headers={
"x-provider-key": os.environ["OPENAI_API_KEY"],
"x-outcap-route": "crew-agents",
},
)
# Agent(..., llm=llm)Same principle for LlamaIndex, AutoGen or the Vercel AI SDK: any client that exposes a base URL and headers works. Your agents stay in shadow mode until you turn something on.
Reference
Request headers
Two are required, three are optional and each unlocks a feature. Without x-outcap-user, for instance, there is no per customer budget and no margin policy: Outcap does not guess who is behind a request.
| Header | Required | Role |
|---|---|---|
| authorization: Bearer oc_… | required | Your Outcap key. Your SDK sets it from its apiKey parameter. The x-api-key header is accepted instead, for Anthropic clients. |
| x-provider-key | required | Your OpenAI or Anthropic key. Never logged, never persisted. You may pass up to five separated by commas, in priority order: the next one serves when a key is refused for itself, never on a provider rate limit. |
| x-outcap-route | optional | The tag of the feature making the call. Without it, the route is identified by a fingerprint of the first 256 characters of your system prompt: that works, but a tag is readable in a table. |
| x-outcap-user | optional | Your end customer's identifier. Enables per customer budgets and the margin policy. We store the value as it is: pass an opaque identifier, not an email. |
| traceparent | optional | The W3C header. If an OpenTelemetry export is configured, Outcap's trace attaches to yours and follows your sampling. It is never forwarded to the provider. |
Reference
Response headers
This is the product's vocabulary. Every decision Outcap makes about a request leaves a readable trace here, streaming included: a model served instead of another, a cap applied, a value redacted, a backup key used.
| Header | Values | Meaning |
|---|---|---|
| x-outcap-request-id | uuid | The identifier of this request, on every response from the proxy, streaming included. It is also the outcap.request.id attribute of your OpenTelemetry traces. |
| x-outcap-route | tag or fingerprint | The route resolved for this request. |
| x-outcap-simulated-cap | integer | The output cap that would have been sent if the route were active. Present in shadow mode. |
| x-outcap-cap-applied | integer | The output cap actually sent to the provider. Never applied to a reasoning model nor to a request that declares tools. |
| x-outcap-routed-to | model name | The model actually served when routing is active on the route. |
| x-outcap-margin | degraded, blocked | This end customer's margin changed the request, or refused it. degraded means a lever really was applied: cheaper model, shorter cap, or both. |
| x-outcap-cache | hit, miss | The answer comes from the route's exact cache, or it does not. A hit is logged at 0 tokens and $0, and the saving is counted separately. |
| x-outcap-clean-cut | sentence, json_repaired | The answer was cut by our cap, then finished at a sentence boundary or repaired into valid JSON. A cut caused by your own max_tokens is never rewritten and does not set this header. |
| x-outcap-fallback | model name | The intended model refused, this one served. It is not a routing saving and is never counted as one. |
| x-outcap-key-position | 1 to 5 | The position, inside your x-provider-key, of the key this answer came from. Present only when you pass several. |
| x-outcap-key-failover | integer | How many key changes happened on this request, when there was at least one. |
| x-outcap-guardrail | pass, flagged, redacted, blocked, error | The result of the input guardrails. Absent when they are off. |
| x-outcap-guardrail-detections | email=2,openai_key=1 | The types detected and their counts. Never the values, never an excerpt. |
| x-outcap-guardrail-coverage | partial, skipped | Part of the text was not read, or none of it was. Absent when the read was complete. |
| x-outcap-guardrail-shadow | redact, block | In shadow mode, what would have been applied had the policy been active. |
| x-outcap-ratelimit-limit | integer | On a rate limit 429: the limit that was reached. |
| x-outcap-ratelimit-window | 60, 3600, 86400 | On a rate limit 429: that limit's window, in seconds. |
| x-outcap-ratelimit-metric | requests, tokens | On a rate limit 429: what that limit counts. |
On an error response from the provider, its own retry headers reach you unchanged: retry-after, retry-after-ms, x-should-retry, the request identifier and the rate limit counters. Its organisation and workspace identifiers do not.
Reference
Error codes
A refusal from Outcap happens before any call to the provider: it costs zero tokens. The column that matters is the last thing your code needs to know: a budget does not free itself, a rate limit does.
| Code | Status | Retry | When |
|---|---|---|---|
| outcap_missing_key | 401 | no | No Outcap key in authorization nor in x-api-key. |
| outcap_invalid_key | 401 | no | Unknown or revoked Outcap key. |
| outcap_missing_provider_key | 401 | no | The x-provider-key header is absent. |
| outcap_invalid_provider_key | 400 | no | One of the keys is over 512 characters or contains a character outside visible ASCII. The value never appears in the error. |
| outcap_too_many_provider_keys | 400 | no | More than five keys in x-provider-key. |
| outcap_kill_switch | 429 | no | The project's kill switch is armed. Checked before any work proportional to the size of the body. |
| outcap_guardrail_blocked | 400 | no | Input guardrails found a category you set to refuse. |
| outcap_guardrail_unredactable | 400 | no | A value sits in a part Outcap never rewrites (tool arguments, thinking, tool results) and your setting is to refuse. |
| outcap_guardrail_scan_limit | 400 | no | Part of the text could not be read, and your setting refuses rather than send unscanned text. |
| outcap_guardrail_scan_budget | 429 | yes | The project's scan allowance is exhausted for the current minute. |
| outcap_guardrail_error | 400 | no | Internal guardrail error, under a policy that redacts or refuses. Neither the error text nor the value leaves. |
| outcap_margin_exceeded | 429 | no | This end customer has consumed the ceiling derived from their revenue, and you chose blocking over degradation. |
| outcap_budget_exceeded | 429 | no | A hard budget was reached on the project, key, route or end customer. |
| outcap_rate_limited | 429 | yes | One of your rate limits was reached. The wait is computed and handed to you. |
| outcap_upstream_error | 502 | no | The provider did not answer, or its body could not be read. The message carries network error codes only. |
| outcap_unknown_endpoint | 404 | no | A path Outcap does not proxy. |
429 · budget reached, do not retry
retry-after: 42317
x-should-retry: false
{
"error": {
"message": "Outcap budget exceeded (global budget): $10.000312 spent of $10 per day [scope: project].",
"type": "insufficient_quota",
"code": "outcap_budget_exceeded"
}
}429 · rate limit, retry in 12 s
retry-after: 12
retry-after-ms: 11480
x-should-retry: true
x-outcap-ratelimit-limit: 20
x-outcap-ratelimit-window: 60
x-outcap-ratelimit-metric: requests
{
"error": {
"message": "Outcap rate limit reached: 20 requests per minute [scope: end user cust_8f2]. Retry in 12s.",
"type": "rate_limit_exceeded",
"code": "outcap_rate_limited"
}
}Error bodies follow the format of the provider you are calling, so your SDK recognises them with no special code. On a budget, x-should-retry is false: the official OpenAI and Anthropic SDKs will not retry, and that is deliberate. On a rate limit, they retry on their own as long as the announced wait is under a minute. Two things to know about those limits: it is a token bucket and not a sliding window, so up to about twice the limit can get through across one window, and the counters restart full after a proxy restart, unlike budgets.
After the first call
Where to start
In this order. The first three change no request at all, and the third is the one that pays.
- 01Leave it in shadow modeThe default mode changes nothing. A route needs ten answers before a cap is learned, and answers with tool calls are deliberately left out of the calculation.
- 02Set a hard budgetAn amount per day across the whole project is enough to start. It is the one setting that cannot surprise you: beyond it, the request is refused before the call to the provider, for zero spend.
- 03Look at the routing suggestionsIn a route's detail view. The price gap is exact, computed on your real tokens and the providers' public prices. It is the only saving in the product that measures to the cent.
- 04Name your routesSet x-outcap-route on every call. A system prompt fingerprint works, but a table of tags is readable, and per route settings hang off it.
- 05Pass your customer's identifierIf you bill customers, x-outcap-user unlocks per customer budgets and the margin policy. Without it, Outcap does not know who is spending.
Plug it in, watch, decide afterwards
None of the above modifies a request until you turn it on, route by route, and every switch comes back off in one click.