AI API — /v1/ai

A metered, escrowed passthrough to a chat-completion provider (OpenRouter). The app holds no model key; the end user pays their own token cost, escrowed per request so spend can never exceed the caller's balance or the app's cap.

POST /v1/ai/chat#

OpenAI-compatible chat completions. Requires authentication.

Body (a standard chat-completions payload). Only model, messages, and max_tokens are inspected — everything else (temperature, tools, response_format, provider routing, reasoning, prompt caching via cache_control, …) is forwarded to OpenRouter untouched. The gateway is a metering layer on top of OpenRouter's full surface, not a restricted subset of it.

{
  "model": "openai/gpt-5.5",
  "messages": [{ "role": "user", "content": "Hello" }],
  "max_tokens": 1024,
  "stream": false
}
FieldNotes
modelRequired. Provider model id. See GET /v1/ai/models for the catalog.
messagesRequired, non-empty. Serialized prompt is capped at 2,000,000 chars (prompt_too_large).
max_tokensOptional; defaults to 4096. Clamped to the model's own max output when known (unknown models pass through unclamped — the escrow hold, not this clamp, bounds spend).
streamOptional. true streams SSE.

The gateway forces usage accounting on, so the response always carries a cost to settle against.

GET /v1/ai/models#

Lists the OpenRouter model catalog the gateway prices against — the same table used to compute escrow holds, refreshed hourly. Unmetered read.

{
  "models": [
    {
      "id": "openai/gpt-4o",
      "prompt_micro_usd_per_token": 3,
      "completion_micro_usd_per_token": 15,
      "max_completion_tokens": 16384,
      "context_length": 128000
    }
  ]
}

max_completion_tokens / context_length are null for a model OpenRouter doesn't report limits for. 503 if the catalog has never been fetched successfully.

Response#

Billing#

  1. Before calling upstream, the gateway places an escrow hold bounding the cost (prompt size + max_tokens at the model's rate). If the wallet or the app's cap can't cover it, the request fails 402 (insufficient_balance / spend_cap_exceeded) with no spend.
  2. After completion it captures the actual cost (provider-reported cost → reported token counts → the hold, in that order). A stream that dies before usage arrives is settled from observed characters, capped at the hold.
  3. If upstream fails, the hold is released — you owe nothing (502 upstream error).
  4. Streaming only — a well-behaved upstream can't exceed the hold (it covers max_tokens with margin), but if a provider ignores max_tokens and keeps emitting, the gateway tracks accrued cost per chunk and cuts the stream once it would exceed the hold. The client sees a final SSE data event {"error":"cost_limit_reached"} before [DONE]; settlement is capped at the hold either way, so the wallet can never be overdrawn.

Errors#

StatuserrorMeaning
400invalid_request / prompt_too_largeBad body, empty messages, oversized prompt.
402insufficient_balance / spend_cap_exceededCan't cover the hold.
502upstream errorProvider failed; no charge.
503OPENROUTER_API_KEY … not configuredServer missing the provider key.

SDK#

const res = await osgarden.ai.chat({
  model: "openai/gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
});
const reply = res.choices?.[0]?.message?.content;

const { models } = await osgarden.ai.models(); // enumerate ids, rates, output limits