Skip to content
Talk to our solutions team

AI Gateway API

The gateway serves two API dialects on the same port. The dialect is chosen by the route; everything behind it, alias resolution, prompts, budgets, cache, the upstream call, metering, audit, is dialect-agnostic and operates on one canonical representation.

Client dialect is independent of upstream dialect. An Anthropic-SDK caller can be served by a vLLM upstream and an OpenAI-SDK caller by Anthropic.

HeaderRequiredPurpose
Authorization: Bearer <token>yesA user’s token, bound to its tenant, or a platform service’s token
X-Customer, X-Env, X-Product, X-TenantyesThe request’s tenant, one segment of its key customer:env:product:tenant each. A user token’s tenant claim must name it
X-Kis-BlockyesCalling block, for cost attribution
X-Kis-PurposeyesPurpose tag, for cost attribution
X-Kis-Request-IDyesCaller-supplied id, for correlation and replay
X-Kis-TenantnoRestates the tenant: its full key, or the bare tenant. Must name the request’s tenant
X-Kis-Customer, X-Kis-Product, X-Kis-EnvironmentnoRestate those parts of the key. Must agree with it
X-Kis-SessionnoSession id: cumulative cost and per-session throttles
X-Kis-Idempotency-KeynoA repeated key returns the first response
X-Kis-Prompt-VarsnoString prompt parameters, URL-encoded: team=finance&tone=formal
X-Kis-Prioritynocritical, normal (default) or background

The tenant must be one the gateway’s configuration lists. A request missing a mandatory header is rejected rather than served against a default tenant, and a restating header that names another tenant is a 403. A call with the mandatory headers:

POST /v1/chat/completions HTTP/1.1
Authorization: Bearer <token>
X-Customer: acme
X-Env: prod
X-Product: support-portal
X-Tenant: main
X-Kis-Block: support-bot
X-Kis-Purpose: triage
X-Kis-Request-ID: 01JB7CY1Q6R0GK09RZAVDQGF11
Content-Type: application/json

Caches, budgets, sessions, batches and the audit trail are partitioned by the tenant’s full key.

POST /v1/chat/completions chat completion, sync or SSE
POST /v1/embeddings embeddings
GET /v1/models the aliases the request's tenant sees
POST /v1/files upload a batch input file (JSONL)
GET /v1/files/{id}/content a file's content
POST /v1/batches submit a batch
GET /v1/batches/{id} batch status
POST /v1/batches/{id}/cancel cancel a batch
GET /v1/batches/{id}/results batch results
POST /v1/images/generations image generation
POST /v1/audio/speech text to speech
POST /v1/audio/transcriptions speech to text

The three modality routes are also served without the /v1 prefix.

POST /v1/messages message completion, sync or SSE
POST /v1/messages/count_tokens token count without generating

model names a gateway alias, not a provider model, support.classifier.v3, not claude-haiku-4-5. Which upstream that resolves to is configuration. GET /v1/models lists the platform’s aliases and the tenant’s product’s.

{
"model": "support.classifier.v3",
"messages": [{ "role": "user", "content": "where is my order?" }]
}

POST /v1/chat/completions, POST /v1/messages, POST /v1/messages/count_tokens and each line of a batch file take two fields beside the dialect’s own:

FieldTypeMeaning
promptstringA prompt the tenant sees: the platform’s, or one in its product’s ai/prompts
parametersJSON objectThe prompt’s template variables. Values keep their JSON types
{
"model": "chat.default",
"prompt": "email-template-gen",
"parameters": { "templatecount": 5, "your-brand": "kis.ai", "charactercount": 150 }
}

model stays required. messages may be left out when the prompt renders a user message. A prompt in the body wins over the alias’s own (spec.prompt.registry_ref); parameters win over X-Kis-Prompt-Vars key by key. count_tokens counts the request with the prompt rendered in. See Authoring Prompts for what is sent upstream.

Both dialects stream over SSE when the request asks for it. Mid-stream events are recorded, so a replayed streamed call reproduces the stream rather than only the final text. Cancelling mid-stream is supported and recorded as such in the audit trail.

Read-only routes, on the same port as the model endpoints. Each runs the same tenant checks as a model call and answers for the request’s tenant only. Each needs the tenant’s administrator: a user token with the admin (the tenant’s own administrator) or superadmin role, or a platform service’s token. Any other caller gets 403 with ai-gateway-admin-required. GET /admin/v1/health needs a token only.

RouteReturns
GET /admin/v1/aliasesEvery alias the tenant sees: {"object": "list", "data": [...]}, each with name, owner, source, version, upstream (provider/model)
GET /admin/v1/aliases/{name}One alias: the list fields, and fallback_count, cache (enabled, mode), budget (rate_limit_rpm, cost_ceiling_usd_per_tenant_per_day), prompt_ref, ab_routing
GET /admin/v1/prompts/{name}One prompt: name, owner, source, version, type, description, tags, system_prompt, user_prompt (the template text) and samples (their count)
GET /admin/v1/budgets/{tenant}The tenant’s configured ceilings: tenant, soft_threshold, tenant_block_rpm, tenant_block_ceiling, tenant_global_rpm, tenant_global_ceiling. {tenant} must be the request’s own full key, else 403 with ai-gateway-tenant-mismatch
GET /admin/v1/statsaliases (the count the tenant sees), cache_enabled, budget_enabled, dialects, and product: the tenant’s loaded stamp and its aliases, prompts and providers counts
GET /admin/v1/audit/{request_id}The tenant’s audit entry for that request, {"entry": {...}}. With ?dialect=openai or ?dialect=anthropic, also the stored response rendered in that dialect (rendered, rendered_dialect)
GET /admin/v1/health{"status": "ok"}, or not_ready

owner is kis-platform for the platform’s aliases and prompts, and the tenant’s key for its product’s. source is the file that declares it (ai/prompts/sales.yaml for a product prompt). A prompt’s version is the stamp of the folder it was read from, the value accounting records as prompt_version.

{
"name": "email-template-gen",
"owner": "acme:prod:shop:main",
"source": "ai/prompts/sales.yaml",
"version": "bd8f2315f72f",
"type": "zero-shot",
"description": "",
"tags": ["sales", "email"],
"system_prompt": "You are a helpful executive assistant.",
"user_prompt": "Write {{ templatecount }} email templates to introduce {{ your-brand }} in\n{{ charactercount }} characters or less, on {{ topic }}.\n",
"samples": 0
}

Aliases and prompts are not created or changed through this API: they are files in the product’s ai/models and ai/prompts, and the platform’s directories. See Managing aliases and prompts.

The error body is rendered in the caller’s dialect.

OpenAI envelope:

{
"error": {
"message": "human-readable, no customer data",
"type": "invalid_request_error",
"code": "canonical_error_code"
}
}

Anthropic envelope:

{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "human-readable, no customer data"
}
}

A refusal by the gateway itself also carries a stable code in the X-Kis-Error-Code response header, in both dialects, and as the OpenAI envelope’s code. The Anthropic envelope has no code field, so match on the header.

X-Kis-Error-CodeStatusWhen
ai-gateway-tenant-missing400The X-Customer, X-Env, X-Product and X-Tenant headers do not name a tenant
ai-gateway-tenant-not-served400The gateway’s configuration does not list the tenant
ai-gateway-tenant-not-loaded503The tenant is listed, but its product has not loaded yet. Sets Retry-After
ai-gateway-tenant-mismatch403An X-Kis-* tenant header, the token’s tenant claim, or the {tenant} of /admin/v1/budgets/{tenant} names another tenant
ai-gateway-admin-required403An admin route, called without the admin or superadmin role
ai-gateway-prompt-not-found404prompt (or the alias’s registry_ref) is not a prompt the tenant sees
ai-gateway-prompt-parameter-missing400A template uses a variable the call did not give; the message names it
ai-gateway-prompt-render-failed400A template failed at render time for another reason
ai-gateway-prompt-invalid400prompt is not a name, parameters is not a JSON object, or the prompt renders no user text and the call sends no messages
StatusMeaning
400Malformed request, a missing mandatory header, or a prompt refusal
401 / 403Authentication or authorisation failure
404Alias or prompt not resolvable for this tenant
429Budget or rate limit exceeded: sets Retry-After
503The tenant’s product has not loaded yet: sets Retry-After
5xxAll upstreams in the fallback chain failed

429 and 503 responses set the Retry-After header in seconds. Messages never contain customer data, so they are safe to log and surface.