POST /v1/ai/completeThe gateway's one AI completion endpoint. Requires the x-api-key header
(see Authentication).
{
"prompt": "Say hello in exactly three words.",
"max_tokens": 4096,
"effort": "low"
}
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | yes | Must be non-empty (after trimming whitespace). |
max_tokens | integer | no | The most output tokens the reply may use, 1–8192. Default 4096. The model's own reasoning counts toward it, so leave room for long answers. You're billed for the tokens actually used, not this cap. |
effort | string | no | How much the model reasons before answering: "none", "low", "medium" or "high". Lower is faster and uses fewer output tokens, so it costs less; higher is more thorough. "none" turns the reasoning step off. Leave it out to get the gateway's default (normally the provider's own, which equals "high"). Any other value is a 400. |
service | string | no | Which AI service runs the request, by id (see GET /v1/ai/services below). Leave it out to use your default service. It must be one of the services enabled for your account, spelled exactly; anything else is a 400 that lists the allowed ids. |
fallback | bool | no | false forbids sending this request to a different service if yours is unavailable, e.g. for data that must stay with one vendor. Leave it out to follow your account's setting, which is off unless your operator has switched it on for you (see GET /v1/ai/services). |
Reasoning is billed as output tokens, so effort changes what a request
costs, and max_tokens caps reasoning and reply together. For short or
routine requests, "low" usually returns noticeably faster; keep the
default (or "high") where answer quality matters most.
200 OK{
"response": "Hello there, friend!",
"truncated": false,
"tenant_name": "Vaultwave Financial",
"used_byok_key": false,
"cost": "0.00044",
"balance_after": "2459.99934",
"effort": "low",
"service": "anthropic-claude-sonnet-5",
"usage": {
"input_tokens": 14,
"output_tokens": 6,
"thinking_tokens": 0
}
}
| Field | Type | Notes |
|---|---|---|
response | string | The completion text. |
truncated | bool | true if the reply hit max_tokens and was cut off: its end is missing. Retry with a larger max_tokens or a shorter request. |
tenant_name | string | The name of the tenant that authenticated this request. |
used_byok_key | bool | true if your own provider key was used instead of the platform's — see Billing. |
cost | decimal string | What this request was billed. Always "0" when used_byok_key is true. |
balance_after | decimal string | Your balance immediately after this request. For a BYOK request, this is simply your current balance — unaffected by the request. |
effort | string | Only present when an effort level was applied: the one you sent, or the gateway's default. |
service | string | The id of the service that handled the request: the one you named, or your default, or (see fallback_from) a fallback. |
fallback_from | string | Only present when your requested service was unavailable and a fallback service answered instead: the service you asked for. |
usage | object | The tokens this request used: input_tokens, output_tokens (everything generated, reasoning included; this is what cost is computed from) and, when the provider reports it, thinking_tokens (the reasoning part of output_tokens). Rarely, estimated: true: the provider reported no usage, so these counts (and cost) are a conservative estimate; only services set up to allow that do this. |
New fields may be added to this response over time; ignore any you don't use.
Every error from this gateway — on every route, not just this one — is a
JSON body shaped {"error": "<message>"}, paired with an HTTP status. The
cases below are the ones you can actually hit calling this endpoint, with
real example bodies captured from a live gateway (not invented):
| Status | Cause | Example body |
|---|---|---|
400 Bad Request | Empty or whitespace-only prompt | {"error":"prompt must not be empty"} |
400 Bad Request | max_tokens outside 1–8192 | {"error":"max_tokens must be between 1 and 8192"} |
400 Bad Request | effort is not one of the allowed values | {"error":"effort must be one of: none, low, medium, high"} |
400 Bad Request | service is not one of your enabled services | {"error":"service must be one of: anthropic-claude-sonnet-5"} |
400 Bad Request | The service can't be used right now (no provider key configured for it) | {"error":"service anthropic-claude-sonnet-5 is not available"} |
401 Unauthorized | Missing or unrecognized x-api-key | {"error":"Missing or invalid API key"} |
402 Payment Required | Your balance can't cover even a minimal request (metered billing only — never returned for a BYOK-configured tenant) | {"error":"Insufficient balance"} |
403 Forbidden | Your tenant has been suspended by an operator | {"error":"This tenant's access has been suspended"} |
429 Too Many Requests | You've exceeded your rate limit — see Rate Limits | {"error":"Rate limit exceeded — slow down and try again shortly"} |
502 Bad Gateway | The gateway couldn't reach (or got an unexpected response from) the upstream AI provider — not something you did wrong | {"error":"Upstream AI provider request failed"} or {"error":"Upstream AI provider returned an unexpected response"} |
503 Service Unavailable | The AI provider already has as many requests in flight as the gateway allows (in total, or for your account); retry shortly | {"error":"Upstream AI provider is busy — try again shortly"} |
503 Service Unavailable | The provider has failed repeatedly, so the gateway is pausing requests to it for a short while; retry shortly | {"error":"Upstream AI provider is temporarily unavailable — try again shortly"} |
A couple of things worth knowing about how these are checked, in order:
rate limit first (before anything else runs, so a rate-limited request
never touches the database or your balance), then the request's fields
(prompt, max_tokens, effort, service), then — for metered (non-BYOK) tenants only — the balance check, before the
AI provider is ever called at all. A BYOK-configured tenant skips the
balance check entirely, since the platform isn't fronting the cost of that
request.
GET /v1/ai/servicesThe AI services your account may use, with what each supports and costs.
Requires the x-api-key header and counts toward your rate limit, like
/v1/ai/complete.
200 OK{
"default_service": "anthropic-claude-sonnet-5",
"services": [
{
"id": "anthropic-claude-sonnet-5",
"display_name": "Claude Sonnet 5",
"provider": "Anthropic",
"default": true,
"available": true,
"billing": "metered",
"capabilities": {
"effort": ["none", "low", "medium", "high"],
"max_output_tokens": 8192
},
"price": {
"currency": "KES",
"input_per_1000_tokens": "1.0000",
"output_per_1000_tokens": "5.0000"
}
}
],
"fallback": {"enabled": false, "chain": []}
}
| Field | Type | Notes |
|---|---|---|
default_service | string | The service used when a request has no service field. |
services[].id | string | What to send as service. Ids are permanent. |
services[].available | bool | false when the service can't be used right now. |
services[].billing | string | "metered": charged to your balance at price. "own_key": your own provider key is used and nothing is charged. |
services[].capabilities.effort | array | The effort values this service applies. An empty list means it ignores effort (the response then has no effort field). |
services[].capabilities.max_output_tokens | integer | The highest max_tokens this service accepts. |
fallback | object | Whether requests to an unavailable service may be sent to another of your services instead, and which, in order. Off unless your operator switched it on; a fallback is only ever used when the first service is unavailable (never because it refused your request), and the response says so in fallback_from. |
services[].price | object | Per 1,000 tokens, in currency (KES unless that service is priced otherwise). For a price in another currency, kes_equivalent gives the same price in KES at today's exchange rate. |
Your balance, cost and balance_after are always in KES. A service priced
in another currency is converted to KES at the exchange rate in effect when
your request starts, and the charge is rounded to 6 decimal places. If that
service can't be priced right now (for example, no current exchange rate),
it shows "available": false and requests to it get a 400 saying it is
not available; nothing is charged.
Errors: 401, 403 and 429 as for /v1/ai/complete.
There is no API endpoint for deposits: you top up from your dashboard (Deposits), and the gateway credits your balance automatically once the payment settles.