API REFERENCE
Introduction
Apiarium provides a unified REST API to access multiple AI models under a single API key. No need to manage separate accounts for OpenAI, Anthropic, Google or other providers.
Coming from OpenRouter or LiteLLM? See how OpenRouter migration works · or LiteLLM →
Base URL:
Authentication
All requests must include your API key in the Authorization header using the Bearer scheme.
You can find your API key in your dashboard. Keep it secret — never expose it in client-side code.
Credits
Credits are Apiarium's unit of consumption. Each request deducts credits based on the model used and the size of the task. Your balance is always visible in your dashboard.
Your account has two credit balances: plan credits (included with your subscription, reset monthly for paid plans) and extra credits (purchased separately via credit packs — see below). Plan credits are used first; extra credits are used automatically once your plan credits run out.
Text generation cost depends on the number of tokens processed and the model used. Credits are calculated as: model_multiplier × (input_tokens × 0.75 + output_tokens × 4.50) / 1000, with a minimum of 1 credit per request. The multiplier reflects each model's real cost (gpt-4o-mini = 1x, claude-haiku = 7.5x, gemini = 12.5x, gpt-4o = 16.5x, claude-sonnet = 22.5x) — this is why models cost proportionally more, not just a flat token count.
Credit Packs
Need more credits without waiting for your monthly reset? You can buy credit packs at any time — no subscription required. Purchased credits never expire before 12 months and are used automatically once your plan credits run out.
There are two pricing tiers, applied automatically based on your account:
List active packs
/stripe/credits/packsReturns your active (non-expired, non-refunded) credit packs, along with the total extra balance. Requires a Supabase session token, not an API key — this endpoint is used by the dashboard, not for programmatic API usage.
{
"packs": [
{
"id": "74da67c7-...",
"source": "payasyougo",
"credits_original": 1714,
"credits_remaining": 1618,
"expires_at": "2027-07-02T13:14:13.969+00:00",
"created_at": "2026-07-02T13:14:14.382553+00:00",
"refunded": false,
"expired": false
}
],
"total_extra": 1618
}Multi-currency
Apiarium supports billing in 10 currencies: EUR, USD, GBP, CAD, INR, AUD, BRL, MXN, JPY, and SGD. Currency is detected automatically based on your location when you visit the pricing page.
Subscription prices are fixed per currency (not a live conversion) — each plan has a dedicated price for each supported currency. Pay-as-you-go and top-up purchases calculate the exact credit amount at checkout time based on the amount and currency you choose.
All amounts sent to the API (subscription checkout, credit pack purchases) must include a currency field matching one of the supported codes below, in lowercase:
Rate Limits
/llm's rate limit scales with your plan tier, applied per IP address. /image is different: it's a shared capacity pool across every Apiarium user, because it's constrained by our upstream AI providers' own account-wide limits rather than by any single user's traffic. If you exceed a limit, requests return a 429 status until capacity frees up.
Because /image capacity is shared across all users rather than tied to your IP, a busy moment for Apiarium overall — not just your own usage — can occasionally result in a 429 on that endpoint. Retry after a few seconds.
/llm, /tts, and /transcribe responses include standard rate limit headers so you can track your usage:
RateLimit-Limit: 60 RateLimit-Remaining: 57 RateLimit-Reset: 42 // seconds until the window resets
Errors
Apiarium uses standard HTTP status codes. All errors return a JSON object with an error field.
Example error response:
{
"error": "Model \"claude-haiku\" is not available on your plan (starter). Upgrade to access more models.",
"available_models": ["gpt-4o-mini"]
}Provider errors
When a request fails on the AI provider's side (rate limits, overload, downtime), the response includes error_class and provider fields so you can branch your retry logic on the failure type, not on which provider was called. For /image, these fields show up on the failed job (see GET /image/:id below), not on the initial POST — the POST only fails synchronously for request-validation problems (bad params, insufficient credits, plan restrictions).
Example provider error response (from /llm, /tts, /transcribe):
{
"error": "AI provider error. Please try again.",
"error_class": "overloaded",
"provider": "anthropic"
}LLM — Text Generation
/llmGenerate text using GPT, Claude, or Gemini models. Send a list of messages in chat format and receive a completion. Streaming is not supported — the full response is returned at once.
Available models
gemini maps to Google's Gemini 3.5 Flash, their current flagship model — comparable in capability to gpt-4o and claude-sonnet, which is why it sits alongside them as a Pro-only option.
Smart routing
Send "model": "smart" instead of picking one yourself, and Apiarium classifies your request and routes it to whichever of the five models above best fits — complex code to Claude Sonnet, multi-step reasoning and translation to Gemini, everyday chat to GPT-4o mini, and so on. The response always includes model_used and reason, so it's never a black box, and if your plan doesn't include the ideal model for a request, the reason field says so explicitly instead of pretending otherwise.
Combine smart routing with an optional max_cost (in USD) to cap what Apiarium is allowed to spend on the request. It picks the best model that fits within that budget instead of the absolute best model available — and if even the cheapest eligible model exceeds your budget, it says so plainly in the reason rather than silently going over. max_cost is only valid together with "model": "smart" — it has no effect when you've already chosen a specific model, and sending both returns a 400 error rather than being silently ignored.
Smart requests are billed for the (small) classification call and the real answer together, combined into one credits_used total — never billed separately or hidden.
Knowing when a response was cut off
Every response includes finish_reason, normalized to the same four values regardless of which provider actually answered: stop (finished naturally), length (cut off because it hit max_tokens), content_filter (blocked by the provider's safety system — retrying the same input won't help), or tool_calls. Check this field instead of guessing from the content alone whenever it matters whether a response is complete.
Parameters
Example
Response
{
"content": "Brew the moment.",
"model": "claude-haiku",
"credits_used": 2,
"credits_remaining": 9998,
"tokens": { "input": 12, "output": 9 },
"finish_reason": "stop"
}Example — smart routing
Response — smart routing:
{
"content": "Here's the refactored function...",
"model": "claude-sonnet",
"model_used": "claude-sonnet",
"reason": "Best model for complex coding tasks",
"credits_used": 14,
"credits_remaining": 9986,
"tokens": { "input": 21, "output": 187 },
"finish_reason": "stop"
}Example — smart routing with a budget
Same request as above, but capped at $0.002 — too tight for claude-sonnet, so it falls back to the best model that does fit:
Response — budget forced a cheaper model:
{
"content": "Here's the refactored function...",
"model": "gpt-4o-mini",
"model_used": "gpt-4o-mini",
"reason": "Best model for complex coding tasks — best option within your max_cost budget and current plan",
"credits_used": 1,
"credits_remaining": 9999,
"tokens": { "input": 21, "output": 143 },
"finish_reason": "stop"
}Image Generation
/imageGET/image/:idGenerate images from a text prompt using OpenAI or Google's image models. All plans use gpt-image-2 by default. Starter also unlocks Gemini's gemini-flash-image. Pro plans additionally get gemini-pro-image, Google's highest-quality option. Images are stored privately and served via a short-lived signed URL — see the url_expires_in_seconds field below.
This endpoint is asynchronous. POST /image doesn't wait for the image to finish generating — high-quality generations can legitimately take over a minute, longer than any HTTP proxy should reasonably stay open. Instead, POST /image responds immediately (202) with a job_id, and you poll GET /image/:job_id until the job's status is done (with the final url) or failed (credits are refunded automatically in that case).
Note: /image has a shared capacity limit across all Apiarium users (see Rate Limits above), since it's bound by our upstream providers' account-wide throughput rather than per-user traffic. The POST call may occasionally return a 429 here even under normal usage — retry after a few seconds.
Available models
gemini-flash-image (Nano Banana 2) is optimized for speed and high-volume generation. gemini-pro-image (Nano Banana Pro) is Google's highest-quality model, better suited for complex prompts, accurate text rendering, and multi-turn edits. gpt-image-2 sits in between, with three quality tiers (low/medium/high) covering everything from cheap icons to near-photorealistic output.
Smart routing
Send "model": "smart" and Apiarium classifies your prompt and routes to whichever of the three models above fits best — simple icons/graphics to gpt-image-2 at low quality (cheap), photorealistic or text-heavy prompts to gemini-pro-image (or gpt-image-2 at high quality if your plan doesn't include it), fast/high-volume needs to gemini-flash-image, and so on. Since the response to POST /image is just the job-started acknowledgment, model_used and reason are included there, immediately — you don't need to wait for the job to finish to see which model was picked or why. If your plan doesn't include the ideal model, smart routing picks the best one your plan does allow and says so in the reason field.
Like /llm's smart routing, the classification call's cost is folded into credits_reserved (and later credits_used on the finished job) along with the real image generation cost — never hidden or billed separately.
Combine with an optional max_cost (in USD) to cap what smart routing is allowed to spend — useful since the price gap between models here is large (5 to 220 credits). If the ideal model for your prompt is over budget, Apiarium picks the best one that fits and says so in the reason. If even the cheapest eligible model exceeds your budget, it uses that one anyway and says so plainly rather than pretending it stayed under budget. Same rule as /llm: max_cost only works with "model": "smart" — sending it with a specific model returns a 400 error.
Parameters — POST /image
Start a job
Response — 202 Accepted
{
"job_id": "b131832b-f32e-4be2-ab56-8afa2df2e768",
"status": "processing",
"model": "gemini-flash-image",
"output_format": "png",
"credits_reserved": 90,
"poll_url": "/image/b131832b-f32e-4be2-ab56-8afa2df2e768"
}Credits are reserved immediately (credits_reserved) so you can't accidentally over-spend by starting many jobs at once — they're adjusted to the exact final cost (usually identical) once the job completes, or fully refunded if it fails.
Some parameters only apply to specific models. If you send a parameter unsupported by the model you chose (e.g. quality with a Gemini model), the job still starts — the 202 response includes a warnings field so you know it was ignored, instead of failing silently:
{
"job_id": "b131832b-f32e-4be2-ab56-8afa2df2e768",
"status": "processing",
"model": "gemini-flash-image",
"output_format": "png",
"credits_reserved": 90,
"poll_url": "/image/b131832b-f32e-4be2-ab56-8afa2df2e768",
"warnings": ["The following parameters are not supported by gemini-flash-image and were ignored: quality"]
}Poll for the result
Use the job_id from the 202 response (or the poll_url, appended to the base URL) to check status. Same API key, no request body. We recommend polling every 2–5 seconds with light backoff.
Response — while processing
{
"job_id": "b131832b-f32e-4be2-ab56-8afa2df2e768",
"status": "processing",
"model": "gemini-flash-image",
"output_format": "png"
}Response — done
url is a signed URL, not a permanent public link — it expires after url_expires_in_seconds. Re-poll the same job_id to get a fresh URL if it expires; the image itself doesn't disappear, only that particular link.
{
"job_id": "b131832b-f32e-4be2-ab56-8afa2df2e768",
"status": "done",
"model": "gemini-flash-image",
"output_format": "png",
"url": "https://saduxychstwvxqgevqxw.supabase.co/storage/v1/object/sign/images/abc123.png?token=...",
"url_expires_in_seconds": 3600,
"credits_used": 90,
"credits_remaining": 9910
}Response — failed
If generation fails on the provider's side or our own storage upload fails, the job is marked failed and the reserved credits are refunded automatically — you're never charged for a job that didn't deliver an image.
{
"job_id": "b131832b-f32e-4be2-ab56-8afa2df2e768",
"status": "failed",
"model": "gemini-flash-image",
"output_format": "png",
"error": "AI provider error. Please try again.",
"error_class": "overloaded"
}Example — smart routing
Response — job started, smart routing already resolved:
{
"job_id": "5e2f9c3a-1b7d-4a6e-9c2f-8a3b1d4e5f6a",
"status": "processing",
"model": "gemini-pro-image",
"output_format": "png",
"credits_reserved": 221,
"poll_url": "/image/5e2f9c3a-1b7d-4a6e-9c2f-8a3b1d4e5f6a",
"model_used": "gemini-pro-image",
"reason": "Best model for accurate text rendering inside the image"
}Poll GET /image/5e2f9c3a-1b7d-4a6e-9c2f-8a3b1d4e5f6a as shown above to get the final url once status is done.
Example — smart routing with a budget
Same prompt, capped at $0.05 (≈ 21 credits) — too tight for gemini-pro-image (220 credits) or gpt-image-2 at high quality (175 credits), the two models that handle in-image text well. Smart routing falls back to the cheapest of those two and says so honestly instead of pretending it stayed within budget:
Response — job started, budget was too low even for the cheapest eligible option:
{
"job_id": "9d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
"status": "processing",
"model": "gpt-image-2",
"output_format": "png",
"credits_reserved": 175,
"poll_url": "/image/9d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
"model_used": "gpt-image-2",
"reason": "max_cost was too low for any eligible model on your plan — used the cheapest option instead, which still exceeds your budget"
}Text to Speech
/ttsConvert text to natural-sounding audio using OpenAI or Gemini voices. Maximum input: 4,096 characters per request.
Available models
gemini-tts (Gemini 3.1 Flash TTS) is a premium option with natural-language style control — you can direct tone, pace, accent, and emotion (e.g. [whispers], [excited]) directly in the input text, and it supports 70+ languages and up to 2 distinct speakers in a single request. tts-1 (OpenAI) is the faster, lower-cost default with 6 fixed voices.
Note: gemini-tts only supports wav and pcm as response_format — it returns raw audio natively and doesn't go through OpenAI's format conversion. Use tts-1 if you need mp3, opus, aac, or flac.
Smart routing
Send "model": "smart" and Apiarium classifies your text and routes to whichever of the two models above fits best — plain narration to tts-1 (cheaper), and anything needing emotion/style control, non-English/accented speech, or multi-speaker dialogue to gemini-tts. Text containing inline style tags like [excitedly] is always routed to gemini-tts, since tts-1 would read the brackets out loud literally.
Because /tts returns raw audio rather than JSON, smart routing's model_used and reason are returned as response headers instead of a JSON field — see Response below. An explicitly-passed voice is ignored when model is smart, since a voice name valid for one provider isn't valid for the other; each provider's own default voice is used instead.
Combine with an optional max_cost (in USD) to cap what smart routing is allowed to spend — useful since gemini-tts costs roughly 2.4x tts-1. If your text would ideally use gemini-tts but that's over budget, Apiarium falls back to tts-1 and says so in X-Routing-Reason. Note that if your text relies on style tags like [excitedly], tts-1 will read them out loud literally rather than interpreting them — the reason field doesn't warn about this specifically, so avoid combining style-tag text with a tight max_cost. Same rule as the other endpoints: max_cost only works with "model": "smart".
Parameters
Example
Example — gemini-tts with style control
Example — smart routing
Response
Returns a binary audio stream. The Content-Type header reflects the requested format. Cost scales with character count — Math.ceil((chars / 1000) × rate) — so a short request like the "Hello from Apiarium" example above costs 1 credit, not the full per-1,000-char rate. Headers include:
Content-Type: audio/mpeg X-Credits-Used: 1 X-Credits-Remaining: 9999
When model is smart, two additional headers are included:
X-Model-Used: gemini-tts X-Routing-Reason: Text contains inline style tags, which only gemini-tts understands
Transcription
/transcribeTranscribe audio to text using Whisper. Send an audio file as multipart/form-data. Costs 10 credits per minute of audio. Supports 50+ languages including English, Spanish, French, German, Portuguese, Italian, Japanese, Chinese, and more.
Parameters
Example
Response
{
"text": "Hello, this is a transcription of the audio file.",
"credits_used": 10,
"credits_remaining": 9990
}