Developer documentation
API reference
OpenAI-compatible — point your SDK at our base URL and keep your code. Add a header to pin data residency, declare PHI, or pick a model tier.
https://phi-cloud.com /api/v1Get started- Per-request residency headers
- PHI-aware routing
- Drop-in OpenAI SDK
On this page
Quickstart
From zero to a first call in under a minute.
https://cloudsitus.com/api/v1, pass your phi_live_ key, and add X-Region to pin where the request is processed. That is the whole integration./v1/chat/completionscurl https://cloudsitus.com/api/v1/chat/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-PHI: false" \ -H "X-Region: CH" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [ {"role": "user", "content": "Hello"} ] }'No code? Use the hosted chat
Prefer to talk to it directly? /dashboard/chat is the same router behind a multi-turn chat UI — pick a region, pick a model, send messages. Conversation history stays in your browser; the gateway stores nothing.
Heads up
Live keys are prefixed phi_live_. Keep them server-side — never ship a key to a browser or mobile client.
Auth
Authentication
Authorization header. Keys are stateless and verified without a database round-trip.Authorization: Bearer phi_live_…AuthorizationstringrequiredBearer token. Issue and rotate keys in the dashboard.
Routing
Request headers
X-PHItrue | falseoptionalDeclare the payload class. Set true when the request contains protected health information — the router then restricts to PHI-eligible, in-jurisdiction providers. Defaults to false.
X-RegionCH | EU | US | UK | ASIA | ANZ | INDIA | LATAM | MENA | WORLDoptionalOverride the routing region to pin where the request is processed. Defaults to WORLD.
Endpoint
Chat completions
/v1/chat/completionsmodel to auto to let the router choose, or pin a specific model id from /v1/models.modelstringrequiredModel id, or auto to let the router pick the cheapest eligible model for your region and tier.
messagesarrayrequiredThe conversation, as OpenAI-style { role, content } objects.
temperaturenumberoptionalSampling temperature. Passed through to the upstream model.
max_tokensnumberoptionalMaximum number of tokens to generate in the completion.
reasoning_effortnone | minimal | low | medium | highoptionalHow hard a reasoning model should think. Translated into each upstream's own dialect (OpenAI reasoning_effort, Anthropic thinking.budget_tokens, Z.ai thinking.type); on a model with no such control it is a no-op — reasoning models still think by default. When the model returns a trace it comes back as choices[0].message.reasoning_content (or reasoning, whichever spelling the upstream used), never mixed into content. Streaming sends it as delta.reasoning_content chunks. The Anthropic { thinking: { type, budget_tokens } } form is accepted as an alias.
tierfast | baseline | thinking | code | qualityoptionalBody field (not a header) that biases auto routing toward a class of model — a soft preference the router honours when an eligible model in that tier exists, otherwise it falls back to the cheapest eligible model. Distinct from the descriptive quality_tier (low/medium/high) that /v1/models reports per model.
/v1/chat/completionscurl https://cloudsitus.com/api/v1/chat/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-PHI: false" \ -H "X-Region: CH" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [ {"role": "system", "content": "You are a careful assistant."}, {"role": "user", "content": "Hello"} ] }'Endpoint
Vision (images)
/v1/chat/completionsuser message whose content is an array of parts, mixing text and image_url (an https or data: URI). The router biases to a vision-capable model. Available in CH and EU.content[]arrayrequiredParts of type text or image_url. A data URI must stay under 200 KB.
/v1/chat/completionscurl https://cloudsitus.com/api/v1/chat/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: CH" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [{ "role": "user", "content": [ {"type": "text", "text": "What is in this image?"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ…"}} ] }] }'Endpoint
Embeddings
/v1/embeddingsX-PHI gate apply.modelstringrequiredEmbedding model id, or auto.
inputstring | string[]requiredA single string or an array of strings to embed.
/v1/embeddingscurl https://cloudsitus.com/api/v1/embeddings \ -H "Authorization: Bearer phi_live_…" \ -H "X-PHI: false" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "input": ["text to embed"] }'Endpoint
Code autocomplete (FIM)
/v1/fim/completionsbase_url. US only, general traffic only — X-PHI: true returns phi_blocked.promptstringrequiredThe code before the cursor.
suffixstringoptionalThe code after the cursor. Omit it and you get a plain completion rather than a fill-in-the-middle.
max_tokensnumberoptionalCap on the suggestion length.
modelstringoptionalauto, or pin mercury-edit.
/v1/fim/completionscurl https://cloudsitus.com/api/v1/fim/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: US" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "prompt": "def fibonacci(n):\n a, b = 0, 1\n for _ in range(n):\n ", "suffix": " return a\n", "max_tokens": 80 }'Endpoint
Next edit
/v1/edit/completionsmessagesarrayrequiredA single user message in the tagged next-edit format. It must contain <|current_file_content|>, <|code_to_edit|> with a <|cursor|> marker, <|edit_diff_history|> and <|recently_viewed_code_snippets|> (each with its closing tag). A request missing any of them is rejected with missing_next_edit_tags, listing exactly which.
max_tokensnumberoptionalCap on the predicted edit length.
/v1/edit/completionscurl https://cloudsitus.com/api/v1/edit/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: US" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [{ "role": "user", "content": "<|recently_viewed_code_snippets|>\n<|/recently_viewed_code_snippets|>\n<|current_file_content|>\ndef sub(a, b):\n return a - b\n<|/current_file_content|>\n<|edit_diff_history|>\n-def add(a, b):\n+def add(total, b):\n<|/edit_diff_history|>\n<|code_to_edit|>\ndef sub(a, b):<|cursor|>\n return a - b\n<|/code_to_edit|>" }] }'Endpoint
Speech-to-text
/v1/audio/transcriptionsmultipart/form-data with a file field. Available in CH, EU and US (residency-separate, no cross-border failover). Max 25 MB.filebinaryrequiredThe audio file (multipart field file).
modelstringoptionalauto, or pin whisper.
response_formatstringoptionaljson (default), text, srt, vtt, or verbose_json.
/v1/audio/transcriptionscurl https://cloudsitus.com/api/v1/audio/transcriptions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: EU" \ -F "file=@meeting.mp3" \ -F "model=auto"Endpoint
Text-to-speech
/v1/audio/speechGET /v1/models lists them per X-Region). voice is optional — omit it and the regional host picks an appropriate neural voice.inputstringrequiredThe text to synthesize (max 4096 characters).
voicestringoptionalProvider voice id. Omit for a region-appropriate default.
response_formatstringoptionalmp3 (default), wav, opus, aac, flac, pcm. pcm streams where the host can (24 kHz, 16-bit little-endian, mono; response header x-phi-stream: true) — first audio in roughly 150 ms instead of after the whole clip.
languagestringoptionalISO language hint (fr, de-CH) — picks a native voice when voice is omitted. Also accepted as the X-Language header.
/v1/audio/speechcurl https://cloudsitus.com/api/v1/audio/speech \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: EU" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "input": "Hello from CloudSitus." }' --output speech.mp3Endpoint
Voice catalogue
/v1/audio/voices{object:"list", data:[…]} shape as /v1/models, scoped by X-Region and X-PHI — a PHI caller only sees voices on PHI-eligible hosts. Free and unbilled (auth is still required).languagequery stringoptionalFilter across every provider. Matches a bare language or any locale under it — fr matches fr-CH and fr-FR. When set, each voice carries native: true|false and natives sort first: a multilingual voice whose own locale is de-DE can read French, but it will sound German.
/v1/audio/voicescurl "https://cloudsitus.com/api/v1/audio/voices?language=fr" \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: CH"Partial results are normal
Each entry carries its provider, model and phi_eligible. If one upstream catalogue is unreachable it is reported in unavailable and the remaining providers still return — the endpoint never blanks on a single failure.
Endpoint
Realtime speech-to-speech
/v1/realtimeUS and WORLD; general data only (X-PHI: true is refused).modelquery stringoptionalinworld-realtime, or auto / flash (cheapest) / pro (fastest).
regionquery stringoptionalSame as the X-Region header (browsers cannot set headers on a WebSocket).
authheader or subprotocolrequiredAuthorization: Bearer phi_live_…, or — from a browser — the subprotocol openai-insecure-api-key.phi_live_… alongside realtime.
/v1/realtimeimport WebSocket from 'ws'const ws = new WebSocket( 'wss://cloudsitus.com/api/v1/realtime?model=inworld-realtime®ion=WORLD', { headers: { Authorization: 'Bearer phi_live_…' } },)ws.on('open', () => { ws.send(JSON.stringify({ type: 'session.update', session: { type: 'realtime', instructions: 'You are a concise assistant.', audio: { output: { voice: 'Ashley', model: 'inworld-tts-2-flash' } }, } }))})ws.on('message', (raw) => { const e = JSON.parse(raw) if (e.type === 'response.output_audio.delta') play(Buffer.from(e.delta, 'base64'))})// mic audio: ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: pcm16Base64 }))// server VAD answers when the speaker stops — no response.create needed.Billing
Each response.done is billed as its parts: the LLM's tokens, the characters spoken, and the minutes of audio you sent. Choose the LLM with session.model (a provider/model id such as google-ai-studio/gemini-3.1-flash-lite, the default) and the voice engine with audio.output.model; an unsupported choice gets an error event and is not applied.
Endpoint
OCR
/v1/ocrdocument object and returns per-page markdown. Available in CH, EU and US.documentobjectrequired{ type: "document_url" | "image_url", url } — an https or data: URI.
pagesnumber[]optionalRestrict OCR to specific page indices.
annotatebooleanoptionalRequest structured annotations (billed at the higher annotation rate).
/v1/ocrcurl https://cloudsitus.com/api/v1/ocr \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: EU" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "document": {"type": "document_url", "url": "https://…/report.pdf"} }'Endpoint
Decisions
/v1/decisionsstate plus a map of typed questions, and every question comes back with a label or a number and a calibrated probability, out of one encoder forward pass. Nothing is generated — no completion, no streaming, and no OpenAI equivalent to mirror, so this is the model’s own wire (the same posture as /v1/ocr and /v1/fim). CH only, general traffic only.questionsobjectrequiredA map of your own question id → { type, instructions, criteria? }. Up to 32 questions per decision. Every answer is keyed by the id you chose.
statestring | JSONoptionalWhat the questions are about — free text, or any JSON value. Max 65,536 characters. Optional: a question can be answered about nothing.
modelstringoptionalauto (or omitted) resolves to the multilingual checkpoint. Pin laya, laya-multilingual, laya-english or laya-typed-decisions.
requestsarrayoptionalBatch form — an array of { state?, questions } objects, up to 64 per call and 512 questions across the whole request. Send either a single decision or a requests batch, never both.
lang · taskstringoptionalHints forwarded to the upstream’s own checkpoint router. The gateway always pins the resolved model, so these are pass-through niceties rather than routing controls.
type: "choice"one of N labelscriteria is a non-empty { label: description } object (or an array of labels), max 64 options. The answer carries choice (the winning label) and probabilities over all of them — e.g. {"type":"choice","instructions":"What does the customer want?","criteria":{"refund":"wants money back","exchange":"wants a different item"}}.
type: "score"ordinal levelcriteria is a non-empty array of level descriptions, index 0 first. The answer carries score (the probability-weighted level, so it is fractional), the legend you passed and the per-level probabilities — e.g. {"type":"score","instructions":"How urgent is this ticket?","criteria":["routine","reply today","escalate now"]}.
type: "noul"calibrated probabilityA yes/no question answered as a probability. criteria is optional — pass an object with true / false descriptions to steer it, or omit it. The answer carries noul, the probability that the statement holds (0–1) — e.g. {"type":"noul","instructions":"Is the customer angry?"}.
/v1/decisionscurl https://cloudsitus.com/api/v1/decisions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: CH" \ -H "Content-Type: application/json" \ -d '{ "model": "laya", "state": "Ticket 8812 — I ordered the wrong size and nobody has called me back. Third time this week.", "questions": { "intent": { "type": "choice", "instructions": "What does the customer want?", "criteria": { "refund": "wants money back", "exchange": "wants a different size or item", "status": "asking where the order is" } }, "urgency": { "type": "score", "instructions": "How urgent is this ticket?", "criteria": ["routine", "reply today", "escalate now"] }, "angry": { "type": "noul", "instructions": "Is the customer angry?" } } }'< HTTP/2 200< x-phi-routed: laya-multilingual/CH/general< x-phi-input-tokens: 214< x-phi-questions: 3< x-phi-upstream-backend: local< x-phi-cost-micro: 9{ "answers": { "intent": { "type": "choice", "choice": "exchange", "probabilities": {"refund": 0.2913, "exchange": 0.6482, "status": 0.0605}, "confidence": 0.7742, "action": {"act_probability": 1.0} }, "urgency": { "type": "score", "score": 1.4271, "legend": {"0": "routine", "1": "reply today", "2": "escalate now"}, "probabilities": {"0": 0.1204, "1": 0.3321, "2": 0.5475}, "confidence": 0.6218, "action": {"act_probability": 1.0} }, "angry": { "type": "noul", "noul": 0.8734, "confidence": 0.8734, "action": {"act_probability": 1.0} } }, "usage": {"input_tokens": 214, "output_tokens": 0}, "routing": {"model": "multilingual", "reason": "…"}, "id": "9f1c…"}/v1/decisions · batch# One round trip, one forward pass, up to 64 decisions.curl https://cloudsitus.com/api/v1/decisions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: CH" \ -H "Content-Type: application/json" \ -d '{ "model": "laya", "requests": [ { "state": "Ticket 8812 — wrong size, nobody called back.", "questions": {"angry": {"type": "noul", "instructions": "Is the customer angry?"}} }, { "state": "Ticket 8813 — thanks, the replacement arrived today.", "questions": {"angry": {"type": "noul", "instructions": "Is the customer angry?"}} } ] }'# -> { "results": [ {answers, usage, routing, id}, {answers, usage, routing, id} ] }# An entry the upstream could not answer comes back as {"error": …} and is not billed.answersobjectOne entry per question id you sent, each carrying its type, the typed verdict (choice / score / noul), a confidence and, for choice and score, the full probabilities distribution.
usage.input_tokensnumberEncoder input tokens — what the call is billed on. output_tokens is always 0: a decision model generates nothing.
routing · idobject · stringrouting names the checkpoint that answered; id echoes your x-request-id when you send one.
resultsarrayBatch only — one entry per requests element, in order, each the same { answers, usage, routing, id } body. An entry the upstream could not answer is { error: … } and carries no usage, so it is never billed.
x-phi-input-tokensnumberThe encoder input tokens the charge was computed from — the billing unit.
x-phi-questionsnumberHow many questions were actually answered (summed over a batch). Informational, not the billing unit — it is here because a batch’s answered count can’t be read off the body without walking every result.
x-phi-upstream-backendstringWhich backend answered. For a self-hosted Swiss row this is residency evidence: anything but the local backend would mean the request left the box.
Billed per encoder input token
Decisions bill on the upstream’s own usage.input_tokens — the live per-1M rate is on /pricing and GET /v1/pricing — with no output tokens to pay for. The state is re-encoded once per question, so a 3-question decision over a long state costs roughly 3× that state’s tokens — which is exactly how the model meters, and why the price is per token rather than per question.
One question, one window
The window is the whole budget for a single question (instructions + options + state): 1,024 tokens on laya-multilingual and laya-typed-decisions, 512 on laya-english (the laya alias publishes the smallest, 512). A state longer than the room left is truncated, not rejected — so keep the state to what the question actually needs. The 65,536-character cap is a request guard, not the encoder budget.
Switzerland only, general traffic only
The decision host is self-hosted in Switzerland and reached over a tunnel, which is not a PHI boundary — so X-PHI: true returns 403 phi_blocked, and there is no PHI-eligible decision host to fail over to. Every region other than CH returns 422 no_route. Both are by construction, not a special case: do not send protected health information here.
503upstream_overloadedThe decision host had no capacity and shed the request — nothing was computed and nothing is billed. The response carries Retry-After; retry shortly. Distinct from 502 upstream_error, which means the call was attempted and failed.
400validationMalformed questions are rejected here rather than upstream, naming the exact question id and field: invalid_question_type, invalid_instructions, invalid_criteria, too_many_questions, too_many_options, state_too_large, batch_too_large.
Endpoint
List models
/v1/modelsX-Region / X-PHI — there is no per-entry region field; the catalog is filtered to the routes that would succeed for the region and data-class you request. Use it to render a model picker.phi_eligiblebooleantrue = the model may carry PHI on a DPA/BAA-verified host in that region (health data is OK). false = general traffic only (standard DPA, no health data).
quality_tierlow | medium | highA coarse quality / capability band derived from the model class — most meaningful for chat / LLM models — letting a caller pick a tier. Distinct from the routing tier body field on /v1/chat/completions.
supports_toolsbooleanWhether the model accepts OpenAI function calling.
kind / tasksstring / string[]kind is the primary task; tasks lists every one it serves (chat, code, vision, embed, tts, stt, speech_to_speech, ocr, image, video, …).
input_modalities / output_modalitiesstring[]What the model accepts and returns: text, image, audio, video, embedding. ["text","image"] in = it can see images (send them as image_url parts). Measured live, not guessed.
reasoningdefault | on-request | noMeasured reasoning behaviour: thinks on every call, thinks when reasoning_effort asks, or never. Omitted when unmeasured.
context_window / max_output_tokensintegerTokens the model can read, and the separate (usually much smaller) ceiling on one completion. Omitted when unknown, never guessed.
canonical · resolves_to · sizes · archived · retires_atoptionalThe provider-agnostic alias a row belongs to; for flash (cheapest) / pro (fastest) / think, today's model; allowed output sizes (image/video); older generation; announced shutdown date.
/v1/modelscurl https://cloudsitus.com/api/v1/models \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: CH"/v1/models/{id}curl https://cloudsitus.com/api/v1/models/gemini-3-8-flash \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: WORLD"{ "id": "gemini-3-8-flash", "object": "model", "owned_by": "google", "kind": "chat", "tasks": ["chat", "vision"], "input_modalities": ["text", "image"], "output_modalities": ["text"], "supports_tools": true, "reasoning": "default", "context_window": 1048576, "max_output_tokens": 65536, "phi_eligible": false, "quality_tier": "low"}One model
GET /v1/models/{id} returns the same object for a model id, a canonical alias or a class (flash / pro / think), as your X-Region / X-PHI sees it. A model that exists but is not served there returns 404 model_not_found naming the regions that do serve it. Both endpoints need your API key (free, like /v1/ping); /v1/pricing carries the same capability fields next to each price.
Residency
Response headers
200 response echoes the routing decision so you can audit residency and cost per request.x-phi-routedmodel/region/classThe resolved route: modelId/regionCode/dataClass — authoritative record of what served the request.
x-phi-tierphi | non_phiWhether the chosen route was PHI-eligible.
x-phi-attemptsnumberHow many routes were tried before one succeeded (fail-over stays inside the PHI tier for PHI requests).
x-phi-cost-micronumberMetered cost of the request, in USD micro-units (1 = $0.000001). The API always reports cost in USD micro; your wallet balance is shown in prepaid credits (1 USD = 300 credits).
x-phi-reasoningpresent | absentWhether the answering model returned a reasoning trace on this call (chat only; omitted on passthrough-streamed responses, where headers are sent before the first upstream byte). Lets you tell “this model doesn't emit one” from “something dropped it”.
x-request-iduuidUnique id for the request — quote it in support tickets.
< HTTP/2 200< x-phi-routed: gemma-4/CH/general< x-phi-tier: non_phi< x-phi-attempts: 1< x-phi-cost-micro: 4200< x-request-id: 9f1c…Routing
Models & routing
X-Region and the request goes to that region's resident provider first where one exists (CH, EU, US); other regions are served by global clouds, which process globally. You can also pin a canonical model name (e.g. gemma-4, qwen3-embedding-8b) and the router serves it from whichever provider hosts it in your region. PHI requests are filtered to providers verified for that jurisdiction and never spill to an unverified host. See /network for the live region map and /vendors for the full provider & model registry.resident providerper regionThe first host for auto chat: CH → Infomaniak, EU → Scaleway, US → Together AI (each resident in its region); the other named regions → Google (processed globally); WORLD → price order across global clouds.
embeddingsCH / EUProduction embeddings run in CH (Infomaniak) and EU (Scaleway) — the same model, Qwen3-Embedding-8B (4096-d).US and other regions return no_route for embeddings until the staged Fireworks (US) / Azure / Bedrock (other regions) rows are enabled.
PHI tierX-PHI: trueServed only by providers whose data-processing agreement is verified for the region: CH → Infomaniak + Azure, EU → Scaleway + Azure, US → Azure (OCR / TTS / STT only). A region or modality with no verified host returns 403.
Compliance posture
Infomaniak and Scaleway operate under nFADP / GDPR (DPA); Azure additionally carries a HIPAA BAA. In the US, PHI covers OCR, speech-to-text and text-to-speech only — US chat, vision and embeddings PHI are not available. Other providers become PHI-eligible per-region as each agreement lands.
Want the full picture?
The routing guide walks through region residency, PHI gating, canonical aliases, failover, and which modalities are available where — in prose, with the coverage tables.
Endpoints
Math & ping
/v1/math/chat/completionsPOSTIdentical to /v1/chat/completions — same auth, shape and routing — with a math-oriented system prompt and a code-execution sandbox enabled by default (override with code_execution: false). The model can run Python, R, or SQL in a region-pinned, network-isolated sandbox to compute and verify; charts come back as inspectable SVG (fenced ```svg blocks, never raster) and diagrams as ```mermaid. The response carries x-phi-sandbox / x-phi-sandbox-figures. General traffic only.
/v1/pingGET / POSTFree setup diagnostic: validates your key, dry-runs routing, and reports billing_active (true once you have prepaid credits; 1 USD = 300 credits). No upstream call, no usage — exempt from the payment gate so you can verify a key before adding credits.
/v1/math/chat/completions# The math agent picks the tool for the job. Ask a# relational question and it runs SQL in the DuckDB sandbox.curl https://cloudsitus.com/api/v1/math/chat/completions \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: EU" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5", "messages": [{ "role": "user", "content": "Here is monthly revenue as CSV: month,usd\n2026-01,1200\n2026-02,1810\n2026-03,1540. Load it into DuckDB, compute the month-over-month growth rate, and return the fastest-growing month." }] }'# The agent writes SQL, runs it, and answers from the real result:# CREATE TABLE rev AS SELECT * FROM read_csv_auto('/workspace/rev.csv');# SELECT month, usd / lag(usd) OVER (ORDER BY month) - 1 AS mom FROM rev;# -> tables persist across tool calls, so it can build up and re-query.Endpoints
Image & video generation
/v1/images/generations · /v1/videos/generationsX-PHI: true request returns phi_blocked. Images settle synchronously; video is asynchronous — enqueue, then poll /v1/videos/{id} until status is completed. Treat the job id as opaque: it is signed and bound to your account, so poll it with a key from the same account and pass it back unchanged — an edited id returns 404.modelstringoptionalkrea-2 (image), wan or seedance (video), or avatar (talking avatar). Generation routes to fal.ai (paid, general / non-PHI only). On video you can pin the model or just pass quality.
qualitystringoptionalVideo only — standard (default, Krea-Wan 14B, $0.04/sec) or premium (Seedance 2.0, up to 720p with native audio, $0.51/sec). The 202 response echoes price_micro_per_second so you know the rate before the job settles. Doesn’t apply to avatar mode.
promptstringrequiredThe text prompt. For image-to-video or a talking avatar, also pass an image (and audio for the avatar).
durationintegeroptionalPremium video only — clip length in seconds (4–15, default 5). Also accepts resolution (480p | 720p) and aspect_ratio. Billing is per generated second, settled on the first completed poll.
/v1/images/generationscurl https://cloudsitus.com/api/v1/images/generations \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "model": "krea-2", "prompt": "a calm clinic waiting room, soft morning light" }'/v1/videos/generations# 1 — enqueue (async): returns a job id + the per-second ratecurl https://cloudsitus.com/api/v1/videos/generations \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "quality": "standard", "prompt": "a slow drone shot over a green valley" }'# → 202 {"id":"…","status":"queued","quality":"standard","price_micro_per_second":88000}# premium tier (Seedance 2.0) — same call, higher fidelity + native audiocurl https://cloudsitus.com/api/v1/videos/generations \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "quality": "premium", "prompt": "a slow drone shot over a green valley", "duration": 8, "aspect_ratio": "16:9" }'# 2 — poll until status is completedcurl https://cloudsitus.com/api/v1/videos/JOB_ID \ -H "Authorization: Bearer phi_live_…"Keep generated assets private
fal stores request I/O and serves public URLs by default; the adapter disables upstream retention. For confidential content, enable the optional R2 re-host — outputs are copied to our own bucket and served from short-lived signed /v1/media/{key} URLs (the HMAC URL is the capability; no auth header) instead of the public fal URL.
Endpoints
ETL & search
/v1/etl · /v1/etl/query · /v1/searchX-PHI: true request returns phi_blocked) and paid keys only./v1/etlPOSTIngest sources (url, urls, html or text): the upstream extracts, chunks, embeds and stores them. Pass an optional collection — it is namespaced to your account server-side, so it can never collide with another customer’s.
/v1/etl/queryPOSTSemantic retrieval over your collection. query is required; returns the best-matching chunks with scores. Scoped to your namespace — a query can only reach your own content.
/v1/searchPOSTLive web search. query is required. depth: "deep" runs an asynchronous research pass and returns a customer-bound job token to poll at /v1/jobs/{token}; shallow search is synchronous.
/v1/etl · /v1/etl/query · /v1/search# 1 — ingest sources into your (namespaced) collectioncurl https://cloudsitus.com/api/v1/etl \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "sources": ["https://example.com/handbook"], "collection": "handbook" }'# 2 — semantic query over that collectioncurl https://cloudsitus.com/api/v1/etl/query \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "query": "what is the refund window?", "collection": "handbook" }'# 3 — web search; depth:"deep" returns a job token to poll at /v1/jobs/{token}curl https://cloudsitus.com/api/v1/search \ -H "Authorization: Bearer phi_live_…" \ -H "Content-Type: application/json" \ -d '{ "query": "latest nFADP guidance", "depth": "deep" }'Stateless, billed per use
CloudSitus stores nothing here — the vector store, collections and jobs all live in Twin Browser, isolated per customer. Each call is billed on the credits it reports, at 1 USD = 1000 credits; async jobs settle once, on the first terminal poll.
Endpoints
Sandbox run
/v1/sandbox/runX-PHI: true request returns phi_blocked until the sandbox BAA lands) and paid keys only.languagestringrequiredpython, r or sql (DuckDB).
codestringrequiredThe source to execute. Returns a structured SandboxRunResult: ok, stdout, stderr, resultText, error (and any captured SVG artifacts).
inputJSON valueOptional data payload. Written to /workspace/input.json and, for Python, pre-bound as the variable phi_input. Read it in SQL with read_json_auto('/workspace/input.json').
timeout_msnumberOptional per-run timeout (capped at 120000). Requires X-Region to be CH, EU, UK or US — other regions return no_sandbox_region.
/v1/sandbox/run# Deterministic exec — no model in the loop. `input` is written to# /workspace/input.json and, for Python, pre-bound as `phi_input`.curl https://cloudsitus.com/api/v1/sandbox/run \ -H "Authorization: Bearer phi_live_…" \ -H "X-Region: EU" \ -H "Content-Type: application/json" \ -d '{ "language": "python", "code": "import statistics; print(statistics.mean(r[\"usd\"] for r in phi_input))", "input": [{ "month": "2026-01", "usd": 1200 }, { "month": "2026-02", "usd": 1810 }] }'# -> { "ok": true, "stdout": "1505\n", "stderr": "", "resultText": "", "error": null }# SQL: read the same payload with read_json_auto('/workspace/input.json').Billed on wall-clock seconds
Each run is metered on its wall-clock duration (rounded up, minimum one second) and carries the usual x-phi-cost-micro header, plus x-phi-sandbox-seconds and x-phi-sandbox (the placement boundary). One ephemeral container per request, torn down after — nothing persists.
Reference
Errors
{ "error": { "code", "message" } } with a matching HTTP status.401missing_authorizationNo Bearer token on the request.
401invalid_api_keyThe token is invalid or revoked.
401api_key_expiredThe key has passed its expiry. Only the short-lived keys the dashboard chat and playground create expire; keys you create on the Keys page do not.
402payment_requiredOut of credits. Top up your prepaid credit balance in the dashboard → Billing (or enable auto-recharge).
403phi_blockedX-PHI: true but no PHI-verified route exists for the requested region and modality. This is correct behaviour — the gateway refuses to silently cross a border.
422no_routeNo route serves this task / region / data-class combination.
400unknown_modelThe model you sent is not a model id or canonical alias this gateway serves — usually a typo or a stale id. A pinned model is never quietly swapped for another one, so an unrecognised id is rejected rather than served by something else. Call GET /v1/models for the list.
422model_not_available_in_regionThe model exists, but does not serve this task in this X-Region. The error body lists the regions and tasks it does serve.
400invalid_jsonThe request body was not valid JSON, or failed schema validation.
502upstream_errorEvery candidate route failed at the upstream provider. For a pinned model this means every host of that model failed — the gateway will not answer a pinned request with a different model. Send model: "auto" (or a canonical alias) if you want it to fail over across models.
HTTP/2 403content-type: application/json{ "error": { "code": "phi_blocked", "message": "No PHI-verified route for region=US, modality=chat." }}A pinned model is never substituted
If you send an explicit model, you get that model or an error — never a different one. It fails over only across hosts of the same model (which is what a canonical alias like glm-5.3 buys you: the same model on either cloud). Use model: "auto" when you want the gateway to pick, and keep picking, across the whole regional catalogue.
Stable model names: flash and think
Two names never go stale, in any X-Region: model: "flash" is the cheapest current model (the same as auto), and model: "think" is the cheapest current high-quality model measured to reason. Both follow new releases, price changes and provider shutdowns automatically, keep residency and X-PHI rules, and fail over within their class. GET /v1/models shows what each resolves to right now (resolves_to); the x-phi-routed response header names the model that answered.
403 phi_blocked is by design
When X-PHI: true is set in a region or modality with no verified provider, the gateway refuses the request rather than failing over to an unverified route. Pin an X-Region with a verified PHI host (e.g. CH or EU) for that modality.
Keep going
Next steps
/networkpageLive region map and which providers are PHI-eligible where.
/compliancepagenFADP / GDPR posture, sub-processors, and the audit trail you ship by default.
/pricingpagePrepaid credits (1 USD = 300 credits; $10 / 3,000-credit minimum) and every model's per-token price.
/platformpageArchitecture, statelessness contract, and how routing decisions are made.