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.

FormatBase URLWhat your SDK calls
OpenAIhttps://proxy.outcap.tech/v1POST /v1/chat/completions
Anthropichttps://proxy.outcap.techPOST /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.

HeaderRequiredRole
authorization: Bearer oc_…requiredYour Outcap key. Your SDK sets it from its apiKey parameter. The x-api-key header is accepted instead, for Anthropic clients.
x-provider-keyrequiredYour 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-routeoptionalThe 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-useroptionalYour 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.
traceparentoptionalThe 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.

HeaderValuesMeaning
x-outcap-request-iduuidThe 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-routetag or fingerprintThe route resolved for this request.
x-outcap-simulated-capintegerThe output cap that would have been sent if the route were active. Present in shadow mode.
x-outcap-cap-appliedintegerThe output cap actually sent to the provider. Never applied to a reasoning model nor to a request that declares tools.
x-outcap-routed-tomodel nameThe model actually served when routing is active on the route.
x-outcap-margindegraded, blockedThis 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-cachehit, missThe 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-cutsentence, json_repairedThe 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-fallbackmodel nameThe intended model refused, this one served. It is not a routing saving and is never counted as one.
x-outcap-key-position1 to 5The position, inside your x-provider-key, of the key this answer came from. Present only when you pass several.
x-outcap-key-failoverintegerHow many key changes happened on this request, when there was at least one.
x-outcap-guardrailpass, flagged, redacted, blocked, errorThe result of the input guardrails. Absent when they are off.
x-outcap-guardrail-detectionsemail=2,openai_key=1The types detected and their counts. Never the values, never an excerpt.
x-outcap-guardrail-coveragepartial, skippedPart of the text was not read, or none of it was. Absent when the read was complete.
x-outcap-guardrail-shadowredact, blockIn shadow mode, what would have been applied had the policy been active.
x-outcap-ratelimit-limitintegerOn a rate limit 429: the limit that was reached.
x-outcap-ratelimit-window60, 3600, 86400On a rate limit 429: that limit's window, in seconds.
x-outcap-ratelimit-metricrequests, tokensOn 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.

CodeStatusRetryWhen
outcap_missing_key401noNo Outcap key in authorization nor in x-api-key.
outcap_invalid_key401noUnknown or revoked Outcap key.
outcap_missing_provider_key401noThe x-provider-key header is absent.
outcap_invalid_provider_key400noOne 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_keys400noMore than five keys in x-provider-key.
outcap_kill_switch429noThe project's kill switch is armed. Checked before any work proportional to the size of the body.
outcap_guardrail_blocked400noInput guardrails found a category you set to refuse.
outcap_guardrail_unredactable400noA value sits in a part Outcap never rewrites (tool arguments, thinking, tool results) and your setting is to refuse.
outcap_guardrail_scan_limit400noPart of the text could not be read, and your setting refuses rather than send unscanned text.
outcap_guardrail_scan_budget429yesThe project's scan allowance is exhausted for the current minute.
outcap_guardrail_error400noInternal guardrail error, under a policy that redacts or refuses. Neither the error text nor the value leaves.
outcap_margin_exceeded429noThis end customer has consumed the ceiling derived from their revenue, and you chose blocking over degradation.
outcap_budget_exceeded429noA hard budget was reached on the project, key, route or end customer.
outcap_rate_limited429yesOne of your rate limits was reached. The wait is computed and handed to you.
outcap_upstream_error502noThe provider did not answer, or its body could not be read. The message carries network error codes only.
outcap_unknown_endpoint404noA 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Documentation · Outcap