← All docs

Sanction Gateway — zero-instrumentation cost governance

Point your LLM client at Sanction instead of the provider. Sanction forwards the call, reads token usage off the response, meters it against the agent's budget, and returns the answer untouched after metering succeeds. No log_tokens call to remember — change a base URL and add one header.

If the agent has already spent its daily token budget, the gateway returns 402 before calling the provider — the spend is stopped, not just recorded.

Base URL: https://getsanction.com/api/gateway/<provider> Auth: x-sanction-key: pxy_… (your agent key). Your provider key is forwarded as usual.

Anthropic (Python SDK)

from anthropic import Anthropic
client = Anthropic(
    base_url="https://getsanction.com/api/gateway/anthropic",
    api_key="<ANTHROPIC_API_KEY>",
    default_headers={"x-sanction-key": "pxy_…"},
)
client.messages.create(model="claude-haiku-4-5-20251001", max_tokens=100,
                       messages=[{"role": "user", "content": "hi"}])
# → metered automatically as gateway:anthropic

OpenAI (Python SDK)

from openai import OpenAI
client = OpenAI(
    base_url="https://getsanction.com/api/gateway/openai/v1",
    api_key="<OPENAI_API_KEY>",
    default_headers={"x-sanction-key": "pxy_…"},
)
client.chat.completions.create(model="gpt-4o-mini",
                               messages=[{"role": "user", "content": "hi"}])

Gemini (REST)

curl -X POST \
  "https://getsanction.com/api/gateway/gemini/v1beta/models/gemini-flash-latest:generateContent?key=$GOOGLE_API_KEY" \
  -H "x-sanction-key: pxy_…" -H "content-type: application/json" \
  -d '{"contents":[{"parts":[{"text":"hi"}]}]}'

What it meters

Every call lands in your Spend dashboard by agent, model, and gateway:<provider> task label, against the same daily token budget the policy enforces. Set per-agent budgets with PATCH /api/v1/agents.

Streaming

Streaming is metered and stays live — the gateway tees the SSE stream to your client untouched, so tokens arrive as the provider generates them, and settles the meter when the stream ends:

  • Anthropic & Gemini — usage is in the stream by default; metered automatically.
  • OpenAI — set stream_options: {include_usage: true} so the final chunk carries usage; otherwise a streamed OpenAI call can't be metered.

Notes / limits

  • Pricing is an estimate per model (see lib/gateway.ts); tune as needed.
  • Fail-closed is enforced at the budget gate, before the provider is called: if the daily token budget is exhausted, or Sanction can't read spend state (a DB outage makes the pre-call read throw), the call is stopped and the provider is never reached. For a non-streaming (JSON) response, if usage can't be metered afterward, Sanction withholds the body and returns 502. For a streaming response — where the client has already begun receiving bytes and withholding is impossible — the meter write is retried at stream end; a hard failure is logged and left as a single-call under-count rather than breaking the stream.
  • The agent still holds the provider key today. A vault-injected mode (the agent never sees the provider key) is the natural next step.