AI Gateway
Point Claude Code, Codex, Gemini CLI, Cursor, Aider, or any Azure-OpenAI- or Ollama-speaking tool at a governed Scrydon endpoint — the developer keeps their tool, the admin keeps model policy, per-developer cost attribution, and audit.
The AI Gateway is a single endpoint an administrator hands to developers. Their existing coding tool points at it instead of a vendor API, and nothing about how they work changes. What changes is that every turn now runs through the same governed pipeline every other Scrydon capability call uses — model policy, DLP, moderation, audit, and metering — and is attributable to the developer who made it.
There is no second execution path. The router is a protocol translator in front of the pipeline your platform already runs.
Choose the walkthrough for your identity: your Scrydon account, a service account in Claude Code, or a CI pipeline.
Two facts to have in mind before you start. Cost figures are indicative — Scrydon's own estimate from catalog pricing, not a provider invoice, so they will not agree to the cent. And the models a developer can reach are the ones you enabled: the router enforces your allowlist at dispatch, so a model you have not enabled is refused no matter what a tool asks for.
For developers
Install the CLI and sign in
bunx scrydon login https://auth.your-company.comWhich URL? It depends on how your Scrydon is deployed, and both shapes
accept a login — so the wrong one signs you in and then 404s at router setup.
- Subdomain install (services on their own hosts): use the platform
host, often
auth.your-company.com. That is the shape every example on this page uses. - Subpath install (every service behind one host): use that single
host —
https://scrydon.your-company.com— with no path. Substitute it forauth.your-company.comeverywhere below.
Not sure? curl -o /dev/null -w '%{http_code}\n' <url>/api/auth/get-session
answers 200 on the right origin. Your administrator can confirm the shape;
it is routing.mode in the Helm chart.
This opens a device-authorization flow (RFC 8628). The CLI prints a short code and a URL; open the URL in a browser where you are already signed in to Scrydon, confirm the code matches, and approve.
The token is written to ~/.scrydon/credentials.json with mode 0600.
Running this in CI? Don't — a pipeline has no browser to approve a device code in, and this flow authenticates a person. See For pipelines below: a pipeline authenticates as its own service account with scrydon login --service-account.
Create a router key
bunx scrydon router setupThis mints an environment-scoped key and prints the shell lines to paste:
export ANTHROPIC_BASE_URL="https://auth.your-company.com/api/router"
export ENABLE_TOOL_SEARCH="true"
export OPENAI_BASE_URL="https://auth.your-company.com/api/router/v1"
export GOOGLE_GEMINI_BASE_URL="https://auth.your-company.com/api/router"
export ANTHROPIC_API_KEY="scry_…"
export OPENAI_API_KEY="scry_…"
export GEMINI_API_KEY="scry_…"If Claude Code is installed, setup also registers one user-scoped HTTP MCP
server named scrydon at
https://auth.your-company.com/api/mcp/v1. The registration stores
Authorization: Bearer ${ANTHROPIC_API_KEY} literally, so the router key is not
copied into Claude's configuration. An existing correct registration is kept;
a stale user registration is updated.
The two base URLs differ by one path segment, and that is not a typo. The Anthropic SDK appends /v1/messages to its base URL; the OpenAI SDK appends /chat/completions. Both land on the same endpoint from different starting points. Use the lines the CLI printed rather than composing them by hand — a wrong segment produces a bare 404 with nothing in it to explain the cause.
ENABLE_TOOL_SEARCH makes Claude Code keep the full MCP catalog locally and
send only the relevant tool definitions with each model request. Keep this line
when using a custom ANTHROPIC_BASE_URL; without it, Claude can send every
configured MCP tool to a provider at once and exceed that provider's tool
limit.
If you belong to more than one environment, setup asks which one to use — organisation and environment, and nothing else. A key is scoped to that pair, which is what an integration connection is keyed by, so the choice decides which models and tools you can reach and where the usage is recorded. There is no workspace to pick: connections are not scoped to one.
Confirm the organisation tool broker
The preceding router setup command configures this automatically and prints:
✓ Claude MCP configured (https://auth.your-company.com/api/mcp/v1)If Claude Code was not installed when setup ran, the CLI prints this fallback command. Run it after installing Claude Code:
claude mcp add-json --scope user scrydon \
'{"type":"http","url":"https://auth.your-company.com/api/mcp/v1","headers":{"Authorization":"Bearer ${ANTHROPIC_API_KEY}"}}'Claude discovers the enabled integration tools from Scrydon and searches that catalog locally. When it invokes an exact tool, the call returns to Scrydon, which resolves the current organisation binding, policy, and OAuth account before contacting the vendor. Vendor credentials are never installed in Claude Code.
For a user-delegated OAuth tool, Scrydon uses the connected account owned by the user who created the router key. It requires exactly one matching account on the selected product connection: no match, or more than one match, fails the tool call with remediation in the error. Scrydon never falls back to the organisation's shared execution account. OAuth app installations, OAuth client credentials, API keys, and credential-free products keep their organisation-managed behavior.
If GitHub, Slack, or another service is already available through a Scrydon integration, you do not need that vendor's separate MCP server. Directly configured third-party MCP servers bypass Scrydon's integration governance and also add their tools to Claude's local catalog.
Check it end to end
bunx scrydon router doctorChecking https://auth.your-company.com
✓ credential accepted
✓ 4 models available
anthropic/claude-opus-5
anthropic/claude-sonnet-5
openai/gpt-5
openai/gpt-5-mini
✓ completion succeeded (claude-opus-5 via anthropic)
✓ Claude MCP registered and connected
https://auth.your-company.com/api/mcp/v1
✓ 61 organization tools published by Scrydon MCP
Everything checks out. Models route, Claude MCP is connected, and organization tools are published.
Personal OAuth account readiness is checked when each tool is called.doctor walks both planes: the key authenticates, your organisation has models
enabled, a real completion round-trips, Claude's effective scrydon MCP entry
is connected, and Scrydon publishes a non-empty tool catalog for the selected
environment. The catalog check is live but read-only; doctor does not invoke a
Microsoft, GitHub, or other vendor action, refresh OAuth tokens, or prove that
you connected an account for every published tool. Personal OAuth readiness is
checked when that exact tool is called. Run doctor first whenever something
looks wrong — every failure below otherwise surfaces inside your other tool,
as a vendor-shaped error that names none of the causes.
Use your tool
Start your tool exactly as you normally would. Most read the environment variables you just set; the rest take the router URL in their own settings:
| Tool | What it reads | Notes |
|---|---|---|
| Claude Code | ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY | Keep ENABLE_TOOL_SEARCH="true". |
| Codex | OPENAI_BASE_URL, OPENAI_API_KEY | Codex speaks the Responses API; the router serves it at …/v1/responses. openai_base_url in ~/.codex/config.toml is the equivalent setting. |
| Gemini CLI | GOOGLE_GEMINI_BASE_URL, GEMINI_API_KEY | Pick a model from scrydon router doctor — ids look like google/gemini-2.5-pro. |
| Cursor, Aider, Continue, OpenCode | OPENAI_BASE_URL, OPENAI_API_KEY | Chat and embeddings both route. |
| Azure OpenAI SDK clients | endpoint https://auth.your-company.com/api/router, api-key = your router key | The deployment name is the model id (azure-foundry/gpt-5); api-version is accepted and ignored. |
| JetBrains AI Assistant, Zed, Open WebUI (Ollama mode) | Ollama host https://auth.your-company.com/api/router | The tool must be able to send Authorization: Bearer <router key>; keyless access does not exist. |
Commands
| Command | What it does |
|---|---|
scrydon router setup | Mint a key, print the environment, and configure Claude's user-scoped Scrydon MCP entry. --name labels the key; --no-key omits it from output. |
scrydon router env | Print the export lines again. |
scrydon router status | What this machine is configured for. Local state only — it does not contact the server. |
scrydon router doctor | Check model routing, Claude's MCP registration, and the live organization tool catalog. --model picks which model to probe with. |
scrydon router forget <url> | Remove the local key. Does not revoke it — do that in Settings → Access Tokens. |
When something fails
The router answers in your tool's own error dialect, so the message reaches you rather than being swallowed. What the common ones mean:
| What you see | What happened | What to do |
|---|---|---|
authentication_error / invalid_api_key | The key is wrong, expired, or revoked. | scrydon router setup to mint a new one. |
permission_error naming a model | That model is not enabled for your organisation. | Ask an administrator, or use one from scrydon router doctor. |
invalid_request_error mentioning a policy | A guardrail (DLP or moderation) refused the turn. | The message says which. This is deliberate and retrying will not help. |
rate_limit_error mentioning a spend cap | You, your team, or the organisation reached a budget. | Retry-After is the real time until the period resets — hours, not seconds. An administrator can raise the cap. |
Invalid 'tools': array too long | Claude Code is not deferring MCP tools locally. | Re-run scrydon router env and export ENABLE_TOOL_SEARCH="true" before starting Claude Code. |
Claude MCP registration missing | Models are routed, but Claude was never connected to Scrydon's tool broker. | Re-run scrydon router setup, or run the exact claude mcp add-json command doctor prints. |
no organization tools are enabled | The MCP connection works, but its environment-scoped catalog is empty. | Ask an administrator to enable the integration products in Settings → Platform → Extensions. |
Your Scrydon identity has no connected account | The tool uses delegated OAuth, but the router-key owner has no account on that product connection. | Connect your account in Settings → Integrations, then retry the tool call. |
Your Scrydon identity has multiple connected accounts | More than one personal account matches, so Scrydon refuses to choose one arbitrarily. | Disconnect the unused account in Settings → Integrations, then retry. |
A 404 on every call | The base URL is missing or has the wrong path segment. | scrydon router env. |
What is not supported
The router refuses these rather than ignoring them, because a parameter silently dropped gives you wrong output with no way to find out:
n,logprobs,top_logprobs,seed,logit_bias,presence_penalty,frequency_penalty(and their Gemini / Ollama spellings)- the deprecated
functions/function_callfields — usetools/tool_choice - server-side conversation state: Responses
previous_response_id,item_reference,background; GeminicachedContent. Send the full input each turn — your tool already does. - a vendor's hosted tools (
web_search,file_search,googleSearch,codeExecution, …). Organisation tools reach you through the Scrydon MCP server instead, governed. - pre-tokenized embedding input (integer arrays) — the router has no tokenizer for the target model.
- a single text part above 1,000,000 characters (roughly 250k tokens); the request as a whole is bounded at 16 MiB.
- Anthropic
count_tokens, Ollama/api/generate,/api/embedand model management.
Sending any of them returns a 400 naming the field. Sending them as null (which some SDKs do for unset options) is fine. Fields that carry no request semantics for a hosted model — store, include, safetySettings, num_ctx, keep_alive, user — are accepted and ignored.
Reasoning content is not surfaced on the OpenAI /chat/completions shape — that wire format has no channel for it, and putting it in content would corrupt the assistant turn. The Anthropic /v1/messages shape carries thinking blocks in full, including their signatures.
Use Claude Code with a service account
Use this walkthrough to send a message from your terminal as an organization-owned service
account. You need Bun, curl, jq, and Claude Code
installed. Your Scrydon deployment and CLI must support service-account machine-token login.
For CI jobs with an OIDC provider, use secretless federation.
Prepare the service account
As an organization admin, open Settings → Members → Service accounts and create an account
with capabilities:write. Pin it to an environment with a configured Claude provider connection
and an enabled Claude model. Copy its client ID and the secret shown once at creation; rotate
the secret if you no longer have it.
This login uses the account's ordinary client ID, not its Federated client ID. The secret
authenticates the account to Scrydon; Claude receives the short-lived bearer issued by that login.
You do not need scrydon router setup for this identity. bunx scrydon router doctor
currently checks the separate router key saved by personal-account setup; use the bearer
model-list request and Claude message below to verify this service account.
Log in from your terminal
Run these commands in Bash or Zsh. Replace the URL with your platform origin, without a trailing slash, and replace the client ID. The prompt reads the secret without echoing it or putting its value in shell history.
export SCRYDON_PLATFORM_URL="https://auth.your-company.com"
export SCRYDON_CLIENT_ID="YOUR_SERVICE_ACCOUNT_CLIENT_ID"
printf 'Client secret: '
IFS= read -rs SCRYDON_CLIENT_SECRET
printf '\n'
bunx scrydon login "$SCRYDON_PLATFORM_URL" \
--service-account "$SCRYDON_CLIENT_ID" \
--service-account-secret "$SCRYDON_CLIENT_SECRET" \
--ttl 2h
unset SCRYDON_CLIENT_SECRETContinue only after login succeeds. If the CLI redirects you from an application URL to its
platform URL, update SCRYDON_PLATFORM_URL to the platform URL it prints before the next step.
The credential file is keyed by that canonical URL. --ttl 2h requests two hours; organization
policy and the deployment ceiling can shorten it, and the CLI reports the issued lifetime.
Configure Claude and choose a model
In the same terminal, load the bearer saved by login and configure Claude's gateway:
TOKEN="$(jq -er --arg url "$SCRYDON_PLATFORM_URL" \
'.[$url].token' \
"${SCRYDON_CREDENTIALS_DIR:-$HOME/.scrydon}/credentials.json")"
unset ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN
export ANTHROPIC_BASE_URL="$SCRYDON_PLATFORM_URL/api/router"
export ANTHROPIC_AUTH_TOKEN="$TOKEN"
export ENABLE_TOOL_SEARCH=true
unset TOKEN
curl --fail --silent --show-error \
"$ANTHROPIC_BASE_URL/v1/models" \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
| jq -r '.data[].id'The base URL ends at /api/router: Claude appends /v1/messages. ANTHROPIC_AUTH_TOKEN sends
the Scrydon bearer in the Authorization header. These exports configure this terminal and the
Claude process started from it. See Claude's gateway setup
if existing Claude settings override your shell configuration.
Copy an enabled Claude model ID from the list, including any provider prefix, into the command below. An empty list means an administrator needs to check the provider and model configuration for the account's environment.
Send your first message
export SCRYDON_CLAUDE_MODEL="PASTE_AN_ENABLED_CLAUDE_MODEL_ID"
claude --model "$SCRYDON_CLAUDE_MODEL" \
-p "Reply with one sentence confirming you received this message."-p prints the response and exits. For an
interactive conversation, run:
claude --model "$SCRYDON_CLAUDE_MODEL"In the interactive session, /status should show your gateway base URL and the environment
token as the credential source. Check Settings → Governance → Cost Insights → Usage, select
AI Gateway and the Principal breakdown: the request belongs to the service account's
client ID, not your personal account. Service accounts currently appear by client ID rather
than friendly name. Listing models alone does not demonstrate completion spend.
When the bearer expires, exit Claude, repeat login and token export, then start Claude again. Do not persist the bearer in your shell profile or commit it to Claude settings. Disabling the service account rejects its bearer on the next authenticated request; rotating its client secret only changes future logins. See credential lifecycle.
For pipelines
A CI pipeline is not a developer at a terminal — there is no browser to approve a device-flow login in, and the credential it holds needs to be revocable and time-bounded on its own terms, not borrowed from a person's session. A service account is the pipeline's own identity.
Create the account
Settings → Members → Service accounts → New service account. Grant it the capabilities:write scope — the same scope every router call declares — and pin its environment.
The environment is a property of the account, not of a flag or the token: a dev pipeline and a prd pipeline are two separate service accounts, each pinned to its own environment, not one account switched between them at run time. See Service accounts for what pinning does and how to change it later.
Store the credentials
For CI providers that issue OIDC tokens, use secretless federation. That recipe registers the issuer, attaches an exact pipeline subject, and exchanges a fresh assertion with the account's Federated client ID for an AI Gateway bearer. It requires no stored Scrydon client secret. The CLI secret-login instructions below are the alternative when your pipeline cannot federate.
Save the client id and secret as your pipeline's repository secrets — for example SCRYDON_CLIENT_ID and SCRYDON_CLIENT_SECRET in GitHub Actions.
Unlike the bearer it is exchanged for, the secret is not meant to be minted per run — but it does expire. Your organization has a maximum lifetime for it (180 days by default, up to a 365-day hard ceiling); Service accounts covers where that number comes from and how rotation works. Rotating replaces the secret immediately — there is no overlap window — so update the pipeline's stored secret in the same change that rotates it.
Authenticate
Login and export belong in the same step. Each - run: step is a fresh
shell, so exporting a variable in one step and reading it in the next reaches
nothing — the export dies with the shell that made it. A separate step
invoking your coding tool needs the variables written somewhere later steps
actually inherit: on GitHub Actions, that is $GITHUB_ENV; other CI systems
have their own equivalent (a shared env file, a pipeline-variable API — check
yours before assuming export alone is enough).
- run: |
scrydon login https://auth.your-company.com \
--service-account "$SCRYDON_CLIENT_ID" \
--service-account-secret "$SCRYDON_CLIENT_SECRET" \
--ttl 2h
TOKEN="$(jq -r '."https://auth.your-company.com".token' ~/.scrydon/credentials.json)"
{
echo "ANTHROPIC_BASE_URL=https://auth.your-company.com/api/router"
echo "OPENAI_BASE_URL=https://auth.your-company.com/api/router/v1"
echo "ANTHROPIC_API_KEY=$TOKEN"
echo "OPENAI_API_KEY=$TOKEN"
} >> "$GITHUB_ENV"Use your platform URL — the same one this whole page uses, covered below in Which URL do I point at?. A pipeline's login resolves it exactly the way a developer's does: type either URL and the CLI asks the deployment which origin owns its auth. If you typed the application URL instead, scrydon login prints "<the url you typed> is the application URL; its platform URL is <the one it found>. Logging in there instead." and continues — that is the CLI correcting a URL you did not have to get exactly right, not a failure.
--ttl is a request, not a guarantee. Your organization's credential policy — and, under that, a hard ceiling your operator sets for the whole deployment — can clamp it shorter, and the server clamps rather than refusing. Ask for 2h (7200s) against a deployment whose ceiling is lower and the command prints, for example, "Requested 7200s; this organization's policy caps it at 3600s. The token is valid for the shorter time." — the login still succeeded. Ask for nothing and you get the organization's default (an hour, until an administrator sets one). An organization admin sets that maximum, default, and the client-secret lifetime at Settings → Governance → Credentials → Policy — the deployment ceiling itself is shown there too, read-only, since only your operator can move it.
The token lands in ~/.scrydon/credentials.json, the same file a developer's login uses. There is no separate router-key mint step for a service account the way there is for a person's router setup — the bearer the login returns is itself a valid router credential, which is why the snippet above reads it straight back out rather than minting anything new.
Log in again at the start of every job rather than caching the exported key across jobs — see the expiry failure mode below.
Choose a model
GET /api/router/v1/models, sent with the bearer above, lists what your organization enabled — the same allowlist a developer's key is bound by:
curl -H "authorization: Bearer $ANTHROPIC_API_KEY" https://auth.your-company.com/api/router/v1/modelsWhen something fails
| What you see | What happened | What to do |
|---|---|---|
A vendor-shaped 401 naming nothing, partway through a long-running job | The bearer expired mid-run. A machine token lives hours, not forever, and a long coding-agent run can outlive it. | Re-run scrydon login at the start of every job rather than caching a token across jobs — a fresh one is minted every time. |
CAPABILITY_NOT_CONFIGURED | The account is pinned to an environment with no matching connection — usually not the one you meant to reach. | Confirm which environment the account is pinned to in Settings → Service accounts, and that the environment has the extension enabled. |
CAPABILITY_NOT_CONFIGURED with "The provider endpoint's TLS certificate could not be verified." | The connection's endpoint address presents a certificate that does not verify, so no request reached the provider and a retry will not help. | Correct the connection's endpoint address. For Azure AI Foundry, see Certificate errors. |
Re-pinning an account's environment takes effect on its next request, including for a bearer already issued — the environment is read from the account on every request, not stamped into the token, so there is nothing to re-mint.
For administrators
Enable models
Settings → Platform → Extensions → the provider → Includes. What you select there is what developers can reach.
The allowlist is enforced at dispatch, not only where models are offered. A tool that names a model directly gets a 409 with your policy message, not a silent substitution.
Three states, and the difference matters:
| Allowlist state | Effect |
|---|---|
| Not configured | Every model the provider exposes is allowed. |
| Some models selected | Exactly those, and nothing else. |
| Nothing selected | Nothing is allowed. This is a policy, not an empty form. |
See what it costs
Settings → Governance → Cost Insights → Usage, with the source set to AI Gateway.
Router traffic is metered with its own source, separate from application API traffic, so the per-principal table answers "who is using this and what is it costing me" — a person or a service account — without an application's workload muddying the numbers. Each row carries calls, tokens, indicative spend, and modelled CO₂.
The breakdown switches between Principal and Model, the tool table lists what was reached through the MCP endpoint, and the budget line at the top of the page shows the organisation cap. The same data is available through billing.summary with filters: { source: "router" }.
Set spend caps
Cost Insights → Limits. Every cap sits beside the scope's spend so far this period, with the highest team or developer named. A cap can be set on a developer, a team, or the whole organisation, measured over a rolling day or month, against either indicative spend or call count.
- Caps are evaluated from the usage ledger, so they hold across every replica.
- Enforce refuses further calls; Audit records the breach and lets the call through.
- A blank field means uncapped.
0means capped at zero — that distinction is real. - When a cap is reached the developer gets a
429whoseRetry-Afteris the actual time until the period resets, so a well-behaved client backs off properly instead of hammering.
Caps govern indicative spend — Scrydon's estimate. Treat them as a guardrail against runaway usage, not as a hard contractual limit against a provider's bill.
Choose a DLP posture
Cost Insights → Limits, under Router guardrails.
DLP runs over every prompt, and for a chat message that is straightforwardly right. A coding agent's prompt is a different shape: it carries the working set of a repository, re-sent every turn, and source code is adversarial input for rules tuned on prose — a credential pattern matches test fixtures, .env.example, and base64 in a lockfile.
So the router's posture is a decision you make rather than one you inherit:
| Posture | Effect |
|---|---|
| Inherit (default) | Whatever your organisation policy says. Unchanged from before the router existed. |
| Enforce | A finding blocks the developer's turn. |
| Audit | A finding is scanned, recorded, and reported — but the turn proceeds. |
Audit is not "DLP off." Every rule still runs, every finding still lands in the audit log, and every dlp.violation an auditor reads is still produced. The only thing it changes is whether a finding refuses the turn. Changing it affects the router only; chat, workflows, and every application surface keep the organisation policy.
Enforce streams too. A streamed answer under an enforce posture is governed
in windows: DLP scans roughly every 512 characters, holds the last 256 back
until the next window confirms them, and releases the rest — so text flows
while the model is still writing instead of arriving all at once when it
finishes. A finding later in the answer still stops it: the stream ends with a
policy error after the clean prefix was delivered, and the text that carried
the finding is never sent. Moderation in enforce mode still judges the whole
answer before releasing it, because its judge is a model call.
Define routes — virtual models that pick a real one
A route is a model id you define. A developer names it (ANTHROPIC_MODEL=team/agent,
or --model team/agent) and an algorithm picks the real, enabled, allowlisted
model for each turn. Nothing is substituted unless the developer asked for the
route by name, and the model a route picks still has to pass your allowlist —
a route cannot reach a model you did not enable.
Routes live in the organisation policy platform.llm_router.routes, written
through the organisation policy API (organization.policy.set). Three route
types:
{
"version": 1,
"routes": {
"team/fast": { "type": "passthrough", "target": "anthropic/claude-sonnet-5" },
"team/ab": {
"type": "random",
"targets": [
{ "model": "anthropic/claude-opus-5", "weight": 1 },
{ "model": "openai/gpt-5", "weight": 3 }
]
},
"team/agent": {
"type": "stage_router",
"capable": "anthropic/claude-opus-5",
"efficient": "anthropic/claude-sonnet-5",
"picker": "efficient_first",
"confidenceThreshold": 0.5,
"recentTurnWindow": 3
}
}
}| Type | What it does |
|---|---|
passthrough | An alias. |
random | A weighted split, for A/B tests and cost experiments. |
stage_router | Reads the recent tool-result history for signs the agent is in trouble (errors, the same call repeated, reading without producing) or settling (edits landing, tests passing), squashes them to a confidence, and picks the capable or efficient model once it clears confidenceThreshold. Below it, the picker default serves (efficient_first or capable_first). A fatal failure escalates regardless. No extra model call is made, so a decision costs microseconds. |
What is recorded: the usage event and audit row carry the model that actually
served the turn and the route that chose it (route.id, route.type, the
decision source and tier), and the Anthropic message_start frame names
the served model, so Cost Insights shows the real split and an auditor can ask
"which turns did team/agent send to the capable tier". Routes are not listed on /v1/models — name
them directly. A policy that fails validation deactivates every route and is
logged; the developer then gets the ordinary "model not enabled" answer.
Revoke a developer's access
Settings → Access Tokens. Router keys appear there by the name the developer gave them. Revoking one takes effect on the next call.
Removing the local copy with scrydon router forget does not revoke the key. If a key may have leaked, revoke it here.
What the router does with a request
- Authenticate. The router key carries its own organisation and environment — the request never asserts a tenant, and the router strips any header that tries to.
- Admit. Scope, deadline, data classification, clearance, and your spend caps.
- Resolve a target. Your enabled provider, your allowlist, the connection bound to that environment, and its credential. A developer's tool never sees a vendor credential.
- Apply guardrails. DLP, then moderation, under the posture you chose.
- Dispatch. Through the same runtime every other capability call uses.
- Record. One audit event and one usage event, attributed to the person or service account.
Steps 2 through 6 are not router code. They are the pipeline your platform already runs; the router's own job is steps 1 and the translation on either side of it.
Capacity and back-pressure
Each router pod accepts a bounded number of open turns at once (the chart value
auth.llmRouter.maxInFlight, default 64; env LLM_ROUTER_MAX_IN_FLIGHT). A
streamed turn counts until its stream ends or you disconnect; a non-streamed
turn counts until its response is returned. Past that bound the router answers
429 with a Retry-After header — rate_limit_error in the Anthropic
dialect, rate_limit_error / rate_limit_exceeded in the OpenAI dialect — and
does not touch the database or start any work for the refused request. The
Anthropic and OpenAI SDKs both honour Retry-After automatically.
This is a per-pod overload guard, not a per-developer quota: it exists so a
saturated pod sheds load instead of slowing every stream and then running out
of memory. Sustained 429s at steady state mean the deployment needs another
replica or a higher CPU limit, not client-side retries. Per-developer and
per-organisation limits are the spend caps above.
Operators can watch scrydon_llm_router_streams_in_flight,
scrydon_llm_router_admission_rejections_total and the TTFT / duration
histograms on the AI Gateway SigNoz dashboard shipped with the operations
chart. Two histograms separate the platform from the provider:
scrydon_llm_router_overhead_ms is a turn's wall time minus the time spent
inside the provider call (queue wait, guardrails and audit settlement
included), and scrydon_llm_router_provider_ttfb_ms is the provider's own
time to first event. scrydon_llm_router_turns_total carries a
response_class (success, retryable_error, other_error) and every
provider attempt, retries included, is counted in
scrydon_extension_outbound_attempts_total with the same classes — the
difference between the two error rates is what the router's retries are worth.
Protocol coverage
Every dialect is a translation in front of the same pipeline; GET /api/router/v1/health lists the ones your deployment speaks.
POST /api/router/v1/messages · GET /api/router/v1/models
| Feature | Supported |
|---|---|
Content blocks (text, image, document) | Yes |
tool_use / tool_result, block order preserved | Yes |
| Extended thinking, including signature round-trip | Yes |
cache_control breakpoints accepted on the request | Yes |
| Cache-read and cache-write reported back separately | Yes |
cache_control forwarded to the provider | Yes |
anthropic-beta forwarded to the provider | Yes, for recognised betas — see below |
Streaming (message_start → content_block_* → message_delta → message_stop) | Yes |
system as a string or as a block array | Yes |
stop_sequences, temperature, top_p, top_k | Yes |
Prompt caching matters more here than anywhere else: a coding agent re-sends its whole context every turn, so without breakpoint passthrough every turn re-pays full input tokens.
Breakpoints travel with the prompt. cache_control markers on system
blocks and content parts are forwarded exactly as sent, a system block array
stays an array, and cache_read_input_tokens / cache_creation_input_tokens
come back on every turn. The router also never rewrites a prompt: on the
router surface a DLP rule configured to mask, redact or hash is recorded as an
audit finding and not applied, because one changed byte early in a 150k-token
working set re-pays the whole prompt on every turn.
Beta features. Claude Code sends an anthropic-beta header naming the
preview features it relies on. The router forwards the names it recognises
(the ones Claude Code sends today plus request-only features such as
context-1m-2025-08-07 and interleaved-thinking-2025-05-14) and drops the
rest rather than rejecting the turn — Anthropic answers an unknown beta with a
400. An administrator can add betas to every request from a connection in
Settings → Integrations → Anthropic → Beta features.
Which URL do I point at?
Your platform URL — the one serving /api/router, usually auth.<your-company>.
Not the application URL you sign in to every day.
The distinction matters more than it should, because the application origin
proxies /api/auth. A login against it therefore succeeds, and nothing looks
wrong until your editor reports a bare 404 from an endpoint that knows nothing
about Scrydon.
The CLI handles this for you: type either URL and scrydon login asks the
deployment which origin owns its auth, tells you if it redirected, and stores the
right one. Every scrydon router subcommand accepts either URL too — when the one
you typed has no stored credentials it follows the same redirect, says which URL
it used, and continues. scrydon router setup then probes /api/router before
minting a key, so a mismatch fails immediately with the cause named rather than
silently later.
If you are configuring a client by hand, use the platform URL.
Related
- Billing and usage metering — the
billing.*API behind Cost Insights - DLP overview — what the guardrails scan for
- Governance usage — the wider Cost Insights surface