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.
Required headers
Section titled “Required headers”| Header | Required | Purpose |
|---|---|---|
Authorization: Bearer <token> | yes | A user’s token, bound to its tenant, or a platform service’s token |
X-Customer, X-Env, X-Product, X-Tenant | yes | The request’s tenant, one segment of its key customer:env:product:tenant each. A user token’s tenant claim must name it |
X-Kis-Block | yes | Calling block, for cost attribution |
X-Kis-Purpose | yes | Purpose tag, for cost attribution |
X-Kis-Request-ID | yes | Caller-supplied id, for correlation and replay |
X-Kis-Tenant | no | Restates the tenant: its full key, or the bare tenant. Must name the request’s tenant |
X-Kis-Customer, X-Kis-Product, X-Kis-Environment | no | Restate those parts of the key. Must agree with it |
X-Kis-Session | no | Session id: cumulative cost and per-session throttles |
X-Kis-Idempotency-Key | no | A repeated key returns the first response |
X-Kis-Prompt-Vars | no | String prompt parameters, URL-encoded: team=finance&tone=formal |
X-Kis-Priority | no | critical, 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.1Authorization: Bearer <token>X-Customer: acmeX-Env: prodX-Product: support-portalX-Tenant: mainX-Kis-Block: support-botX-Kis-Purpose: triageX-Kis-Request-ID: 01JB7CY1Q6R0GK09RZAVDQGF11Content-Type: application/jsonCaches, budgets, sessions, batches and the audit trail are partitioned by the tenant’s full key.
Model endpoints
Section titled “Model endpoints”OpenAI dialect
Section titled “OpenAI dialect”POST /v1/chat/completions chat completion, sync or SSEPOST /v1/embeddings embeddingsGET /v1/models the aliases the request's tenant seesPOST /v1/files upload a batch input file (JSONL)GET /v1/files/{id}/content a file's contentPOST /v1/batches submit a batchGET /v1/batches/{id} batch statusPOST /v1/batches/{id}/cancel cancel a batchGET /v1/batches/{id}/results batch resultsPOST /v1/images/generations image generationPOST /v1/audio/speech text to speechPOST /v1/audio/transcriptions speech to textThe three modality routes are also served without the /v1 prefix.
Anthropic dialect
Section titled “Anthropic dialect”POST /v1/messages message completion, sync or SSEPOST /v1/messages/count_tokens token count without generatingmodel 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?" }]}Calling a prompt
Section titled “Calling a prompt”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:
| Field | Type | Meaning |
|---|---|---|
prompt | string | A prompt the tenant sees: the platform’s, or one in its product’s ai/prompts |
parameters | JSON object | The 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.
Streaming
Section titled “Streaming”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.
Admin routes
Section titled “Admin routes”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.
| Route | Returns |
|---|---|
GET /admin/v1/aliases | Every 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/stats | aliases (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.
Errors
Section titled “Errors”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-Code | Status | When |
|---|---|---|
ai-gateway-tenant-missing | 400 | The X-Customer, X-Env, X-Product and X-Tenant headers do not name a tenant |
ai-gateway-tenant-not-served | 400 | The gateway’s configuration does not list the tenant |
ai-gateway-tenant-not-loaded | 503 | The tenant is listed, but its product has not loaded yet. Sets Retry-After |
ai-gateway-tenant-mismatch | 403 | An X-Kis-* tenant header, the token’s tenant claim, or the {tenant} of /admin/v1/budgets/{tenant} names another tenant |
ai-gateway-admin-required | 403 | An admin route, called without the admin or superadmin role |
ai-gateway-prompt-not-found | 404 | prompt (or the alias’s registry_ref) is not a prompt the tenant sees |
ai-gateway-prompt-parameter-missing | 400 | A template uses a variable the call did not give; the message names it |
ai-gateway-prompt-render-failed | 400 | A template failed at render time for another reason |
ai-gateway-prompt-invalid | 400 | prompt is not a name, parameters is not a JSON object, or the prompt renders no user text and the call sends no messages |
| Status | Meaning |
|---|---|
400 | Malformed request, a missing mandatory header, or a prompt refusal |
401 / 403 | Authentication or authorisation failure |
404 | Alias or prompt not resolvable for this tenant |
429 | Budget or rate limit exceeded: sets Retry-After |
503 | The tenant’s product has not loaded yet: sets Retry-After |
5xx | All 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.
See also
Section titled “See also”- Core Concepts: aliases, caching, fallback, budgets
- Authoring Prompts: prompt files and how a call renders them
- Configuration: the service configuration, tenants and alias files
- Operations: accounting, audit and replay