AI proxy
Call Anthropic and OpenAI via amba so provider keys stay server-side. Prompts are managed in the console.
Amba.ai.* proxies LLM requests through amba so your provider keys never ship to a client. You write the prompt once in the console, reference it by prompt_slug from any SDK, and the server fills in the system prompt + variables + model + rate-limits + usage tracking.
Providers wired today: Anthropic (messages.create), OpenAI (chat.completions.create), Mistral (mistral.chat.completions.create, OpenAI-compatible), and Google Gemini (gemini.generateContent). All return the upstream response shape verbatim, plus a usage event so you can attribute cost per user. Register a prompt against any of them by setting its provider — including Gemini, whose distinct request shape (system instruction + multi-turn content + variables) is built for you — bring your own key per provider (the key stays server-side).
Beyond text, the managed AI surface also proxies image generation, text-to-speech, and audio transcription with your key kept server-side and per-call usage metered. OpenAI transcription is duration-priced; Gemini 3.5 Transcribe is priced from its provider-reported audio-input and text-output tokens.
Quick start
Operations
ai.anthropic.messages.create({ prompt_slug, variables, max_tokens })
Sends a prompt to Anthropic via amba.
| Field | Required | Notes |
|---|---|---|
prompt_slug | yes | Reference to a prompt defined in the console. Server resolves the system prompt + model. |
variables | optional | Object whose keys substitute into the prompt's {{variable}} placeholders. |
max_tokens | optional | Cap output length. Server enforces a project-wide ceiling. |
temperature | optional | Float; default per-prompt in the console. |
enable_prompt_cache | optional | Pass true to opt into Anthropic's prompt caching for that slug. |
stream | optional | Opt into a streamed response (see Streaming). Buffered SDK methods reject it. |
The response shape mirrors Anthropic's Message object, plus a cost_usd
field amba adds:
cost_usd is the dollar cost of the call (input + output + cache token
rates), so you can show per-call spend or sum it per user without a separate
metrics call. It's null for a model amba doesn't have a price for — the
call still succeeds; the cost is just unattributed.
ai.openai.chat.completions.create({ prompt_slug, variables, max_tokens })
Same shape, but routes to OpenAI. Response mirrors OpenAI's ChatCompletion,
plus the same cost_usd field.
Gemini-registered prompts
The vendor path on these SDK methods is just a stable wire surface — the
server resolves the real provider from the prompt's registered
configuration. So a prompt you registered against gemini is invoked through
the same SDK call (e.g. ai.anthropic.messages.create({ prompt_slug })); amba
builds Gemini's distinct request body for you (system instruction, multi-turn
contents, and variables — multimodal image input included) and parses the
response back to the common result shape, with the same cost_usd field. You
never touch provider-specific shapes. (To pick the path that matches how you
registered the prompt, see the client API reference.)
Per-prompt budgets
Cap a prompt's spend with a per-period budget. Once the period's spend
reaches the budget, invocations are denied with an ai_budget_exceeded error
(HTTP 429) until the period resets — so a prompt you expose to your app
can't run away with spend. Budgets are off by default (unlimited).
Set one from the admin API
or an agent (MCP amba_ai_prompts_set_budget):
When a call is denied, the error carries the budget, the spend so far, and when the period resets, so you can decide whether to raise the budget or wait. This is the fine-grained sibling of the project-wide spend ceiling.
Patterns
Prompt slugs
Prompts live in the console — system prompt, variable schema, default model, default temperature, default max_tokens. The client passes the slug; the server fills the rest. This means you can:
- Change the model behind a slug without redeploying.
- A/B test prompt variants by routing a slug through a feature flag.
- Audit which user sent which prompt via the per-call usage event.
Slug naming: lowercase, underscore-separated, up to 64 characters. Group by feature: support_assistant, summarize_review, onboarding_recommend.
Variable substitution
The prompt template uses {{name}} placeholders. The client's variables object fills them:
Variables that aren't in the template are silently ignored. Missing required variables return a 400 from the server with the offending key listed.
Structured JSON output
To get parseable JSON back instead of free-form text, set the provider's response_format on the prompt (or pass it through the admin invoke extra_body):
{ "type": "json_object" }— the model returns a syntactically valid JSON object.{ "type": "json_schema", "json_schema": { "name": "…", "schema": { … } } }— the model returns JSON conforming to your schema (OpenAI / Mistral).
The response is still the provider's native shape — the JSON your model produced lives in the first content block's text, which you then JSON.parse:
Vision (image input)
Send images to a vision-capable prompt by passing images alongside your variables. Each image is either a remote url or base64 data (with an optional mime). Amba attaches them to your message and maps them to the model's native image format for you — you never touch provider-specific shapes:
The images attach to the last user message (a string message is kept as text and the images sit beside it). You can also build the multimodal message by hand — pass content as an array of { type: 'text', text } and { type: 'image', url | data, mime } blocks instead of using images; both produce the same request.
The prompt's model must be vision-capable (e.g. claude-sonnet-4-5, gpt-4o, gemini-2.5-pro). Sending an image to a text-only model returns ai_model_not_multimodal (422) rather than silently dropping the image — switch the prompt to a vision model and retry. Image tokens are metered into cost_usd like any other input.
Streaming
The AI proxy supports a streamed (Server-Sent Events) response — pass stream: true to receive provider frames as they arrive, with usage still metered server-side. Today this is a wire-level capability: the buffered SDK methods (Amba.ai.anthropic.messages.create(...)) decode a single JSON envelope and reject stream: true. Until a streaming SDK method lands, consume the SSE stream over the client REST endpoint directly (or from a server function). The client API — ai reference documents the stream shape.
Cost attribution
Every ai.*.create call emits a ai_usage event automatically — same events namespace as everything else, but with usage.input_tokens and usage.output_tokens attached. Query the event stream per-user to attribute cost without instrumenting your own counter.
Managed media endpoints
Beyond text, the managed AI surface proxies image generation and editing, text-to-speech, and audio transcription with your provider key kept server-side and per-call usage metered.
These media endpoints are invoked from a deployed function, not from the client SDK. A function is deployed with the AI gateway already wired up — the runtime injects AMBA_AI_GATEWAY_URL and the credential — so your function calls the gateway directly and returns the result to your app:
| Endpoint | Method | Body | Returns |
|---|---|---|---|
/images/generations | POST | JSON { model, prompt, n?, quality?, size? } | JSON { data: [...] } (one entry per generated image) |
/images/edits | POST | JSON image references or multipart image file(s) plus prompt | JSON { data: [...] } |
/images/variations | POST | Multipart image file; DALL-E 2 only | JSON { data: [...] } |
/audio/speech | POST | JSON { model, input, voice? } | Binary audio (e.g. audio/mpeg) |
/audio/transcriptions | POST | multipart/form-data with a file audio part and a model field | The transcript (JSON, plain text, or verbose JSON) |
Every call records a usage event (ai.image / ai.speech / ai.transcription) so spend is attributed per project without you instrumenting a counter.
When a buffered call can be priced, the response also includes an
X-Amba-Cost-Usd header. The header is the common cost surface for JSON,
binary audio, and schema-constrained responses without changing their bodies.
It is omitted when the model is unknown or the required meter is unavailable.
Image cost is variant-aware: DALL-E uses its exact model/quality/size price
cell, while GPT Image uses the provider's returned text-input, image-input,
and image-output token counts. If that GPT usage envelope is missing or
incomplete, Amba records a null cost and omits the header rather than
returning a flat per-image estimate.
For OpenAI transcription, request response_format=verbose_json so the provider
returns the duration needed for per-minute pricing; its transcript body is
passed through verbatim. Gemini 3.5 Transcribe instead uses the exact audio-input
and text-output token counts in Google's Interaction usage, so every successful
Gemini response has a numeric X-Amba-Cost-Usd header. Amba rejects a successful
provider response whose usage is incomplete rather than recording a guessed or
null cost. audio_seconds analytics use the final WordInfo offset for verbose
responses, or Google's documented 25-audio-tokens/second estimate otherwise;
audio_seconds_source distinguishes the two.
Gemini 3.5 Transcribe
Register a Gemini key first:
Then send model=gemini-3.5-transcribe. Supported audio types are WAV, MP3,
AIFF, AAC, OGG, FLAC, MPEG, M4A (including the common audio/mp4 MIME type),
L16, Opus, A-law, and mu-law. Amba waits for the resumable Files upload to reach
Google's ACTIVE state before invoking the model, fails under a bounded
processing deadline, and deletes the temporary file on every terminal path.
The default language is automatic detection; omit language or set it to
auto. When the language is known, use one of Google's
documented BCP-47 hints.
Tags are case-insensitive. For compatibility with OpenAI-style transcription
clients, Amba also accepts language=en and sends en-US; other bare language
subtags are accepted when Google's table has exactly one matching locale.
Ambiguous bare hints such as es, pt, bn, and pa require an explicit
documented locale or script instead of silently choosing one.
custom_vocabulary is a JSON array of up to 1,000 terms (or use repeated
custom_vocabulary[] fields). mode is verbatim by default and may be
smart; smart mode cannot be combined with diarization or timestamps. Google's
live API also rejects custom vocabulary with timestamp annotations, so Amba
accepts custom vocabulary on json or text requests and rejects its
combination with verbose_json or timestamp_granularities[] before upload.
response_format=verbose_json selects verbatim mode with speaker diarization
and word timestamps. diarize=true is the explicit diarization flag
(diarization is also accepted as an alias). Existing OpenAI-compatible clients
may send both timestamp_granularities[]=segment and
timestamp_granularities[]=word; Amba synthesizes segments by grouping adjacent
Gemini WordInfo annotations with the same speaker. Its normalized response is:
Speaker labels are scoped to one request. If you split a long recording into
chunks, stitch speaker identities in your application; Amba does not imply that
Speaker 1 in two chunks is the same person. Gemini unary transcription is
limited by Google to one hour, and diarization/timestamps to 30 minutes. This
surface does not implement the live WebSocket transcription model.
Amba returns words and synthesized segments in chronological order, preserving Google's source order when multiple annotations have the same start time. It preserves valid overlaps and provider timestamps rather than clamping them to the approximate 25-audio-tokens/second duration estimate. The gateway rejects ambiguous offset overruns and omits only unmistakably catastrophic provider outliers; it never presents the token estimate as a decoded-media duration.
Calling from a function
The runtime injects AMBA_AI_GATEWAY_URL and the gateway credential into every deployed function. Authenticate with Authorization: Bearer ${env.AMBA_INTERNAL_TOKEN} and send a request-id header (X-Amba-Request-Id, required for tracing; pass through X-Amba-User-Id to attribute usage to an end user):
Image generation and text-to-speech use a JSON body on the same gateway base URL:
Register a provider key for the model you reference (see AI providers); a missing key returns 424 and an unconfigured gateway returns 503. Transcription bills per audio minute — duration is only known when you request a verbose response, so without it the transcript is still returned but the call records a null cost.
Limits
- Prompt slug must exist: the server returns
404 prompt_slug_not_foundfor unknown slugs. Define the prompt in the console before referencing it. max_tokensceiling: per-project hard cap (default 4096); the prompt's console default applies if you don't pass one.- Rate limits: per-prompt-slug rate limits configured in the console. Defaults are conservative; raise per-slug as your usage grows.
- No client-side keys: client SDKs cannot pass an
api_key. The server's provider key is the only credential in play. - No tool-use roundtrip from clients: tool calls (Anthropic) and function-calling (OpenAI) are accepted in the response but the client SDK doesn't auto-execute tools. Run tool dispatch in a server function and only return the final text to the client.
Reference
- Client API — ai — endpoint reference.
- CLI:
amba ai prompts— manage prompt slugs. - Auth feature — prerequisite.
- Per-platform quickstarts: Web, Node, iOS, Android, Flutter, Unity.