Skip to content

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.

Point your existing OpenAI SDK at 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.
POST/v1/chat/completions
curl 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

Every request carries your secret API key as a bearer token in the Authorization header. Keys are stateless and verified without a database round-trip.
Header
Authorization: Bearer phi_live_…
Authorizationstringrequired

Bearer token. Issue and rotate keys in the dashboard.

Routing

Request headers

Two optional request headers steer the router. There is no per-account region: omit X-Region and the request routes as WORLD; omit X-PHI and it is treated as general traffic. (The model tier hint is a request-body field, not a header — see Chat completions.)
X-PHItrue | falseoptional

Declare 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 | WORLDoptional

Override the routing region to pin where the request is processed. Defaults to WORLD.

Endpoint

Chat completions

POST/v1/chat/completions
Standard OpenAI chat schema. Set model to auto to let the router choose, or pin a specific model id from /v1/models.
modelstringrequired

Model id, or auto to let the router pick the cheapest eligible model for your region and tier.

messagesarrayrequired

The conversation, as OpenAI-style { role, content } objects.

temperaturenumberoptional

Sampling temperature. Passed through to the upstream model.

max_tokensnumberoptional

Maximum number of tokens to generate in the completion.

reasoning_effortnone | minimal | low | medium | highoptional

How 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 | qualityoptional

Body 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.

POST/v1/chat/completions
curl 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)

POST/v1/chat/completions
Vision is not a separate endpoint — send a user 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[]arrayrequired

Parts of type text or image_url. A data URI must stay under 200 KB.

POST/v1/chat/completions
curl 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

POST/v1/embeddings
Generate embedding vectors for one or more inputs. The same residency rules and X-PHI gate apply.
modelstringrequired

Embedding model id, or auto.

inputstring | string[]required

A single string or an array of strings to embed.

POST/v1/embeddings
curl 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)

POST/v1/fim/completions
Fill-in-the-middle completion for an editor cursor, served by Mercury Edit 2 — a diffusion model, so the suggestion arrives in one piece rather than streaming token by token. Native Inception shape (OpenAI has no FIM endpoint), so an existing IDE integration only changes its base_url. US only, general traffic only — X-PHI: true returns phi_blocked.
promptstringrequired

The code before the cursor.

suffixstringoptional

The code after the cursor. Omit it and you get a plain completion rather than a fill-in-the-middle.

max_tokensnumberoptional

Cap on the suggestion length.

modelstringoptional

auto, or pin mercury-edit.

POST/v1/fim/completions
curl 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

POST/v1/edit/completions
Predicts the next change from the file, the region under the cursor and the recent diff history — the “you renamed it there, you probably want it renamed here” suggestion. Same model, region and PHI posture as autocomplete above.
messagesarrayrequired

A 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_tokensnumberoptional

Cap on the predicted edit length.

POST/v1/edit/completions
curl 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

POST/v1/audio/transcriptions
OpenAI transcription shape — multipart/form-data with a file field. Available in CH, EU and US (residency-separate, no cross-border failover). Max 25 MB.
filebinaryrequired

The audio file (multipart field file).

modelstringoptional

auto, or pin whisper.

response_formatstringoptional

json (default), text, srt, vtt, or verbose_json.

POST/v1/audio/transcriptions
curl 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

POST/v1/audio/speech
OpenAI speech shape — JSON in, binary audio out. Served in every region with a voice host (GET /v1/models lists them per X-Region). voice is optional — omit it and the regional host picks an appropriate neural voice.
inputstringrequired

The text to synthesize (max 4096 characters).

voicestringoptional

Provider voice id. Omit for a region-appropriate default.

response_formatstringoptional

mp3 (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.

languagestringoptional

ISO language hint (fr, de-CH) — picks a native voice when voice is omitted. Also accepted as the X-Language header.

POST/v1/audio/speech
curl 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.mp3

Endpoint

Voice catalogue

GET/v1/audio/voices
Which voices can actually serve this request. Same {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 stringoptional

Filter 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.

GET/v1/audio/voices
curl "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

GET/v1/realtime
A voice agent in one WebSocket: speech in, speech out, with server-side voice activity detection, transcription and function calling. The event protocol is OpenAI Realtime-compatible, so the OpenAI Realtime SDKs work by changing the URL. Served in US and WORLD; general data only (X-PHI: true is refused).
modelquery stringoptional

inworld-realtime, or auto / flash (cheapest) / pro (fastest).

regionquery stringoptional

Same as the X-Region header (browsers cannot set headers on a WebSocket).

authheader or subprotocolrequired

Authorization: Bearer phi_live_…, or — from a browser — the subprotocol openai-insecure-api-key.phi_live_… alongside realtime.

GET/v1/realtime
import WebSocket from 'ws'const ws = new WebSocket(  'wss://cloudsitus.com/api/v1/realtime?model=inworld-realtime&region=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

POST/v1/ocr
Document OCR. This is the one endpoint that is not OpenAI-shaped (OpenAI has no OCR equivalent) — it takes a Mistral-native document 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[]optional

Restrict OCR to specific page indices.

annotatebooleanoptional

Request structured annotations (billed at the higher annotation rate).

POST/v1/ocr
curl 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

POST/v1/decisions
Typed decisions from a non-autoregressive model (Laya): you send a state 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.
questionsobjectrequired

A 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 | JSONoptional

What the questions are about — free text, or any JSON value. Max 65,536 characters. Optional: a question can be answered about nothing.

modelstringoptional

auto (or omitted) resolves to the multilingual checkpoint. Pin laya, laya-multilingual, laya-english or laya-typed-decisions.

requestsarrayoptional

Batch 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 · taskstringoptional

Hints 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 labels

criteria 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 level

criteria 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 probability

A 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?"}.

POST/v1/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",    "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?"      }    }  }'
Response
< 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…"}
POST/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.
answersobject

One 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_tokensnumber

Encoder input tokens — what the call is billed on. output_tokens is always 0: a decision model generates nothing.

routing · idobject · string

routing names the checkpoint that answered; id echoes your x-request-id when you send one.

resultsarray

Batch 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-tokensnumber

The encoder input tokens the charge was computed from — the billing unit.

x-phi-questionsnumber

How 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-backendstring

Which 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_overloaded

The 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.

400validation

Malformed 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

GET/v1/models
Returns the catalog visible to your account. The list is scoped to your X-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_eligibleboolean

true = 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 | high

A 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_toolsboolean

Whether 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 | no

Measured reasoning behaviour: thinks on every call, thinks when reasoning_effort asks, or never. Omitted when unmeasured.

context_window / max_output_tokensinteger

Tokens 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_atoptional

The 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.

GET/v1/models
curl https://cloudsitus.com/api/v1/models \  -H "Authorization: Bearer phi_live_…" \  -H "X-Region: CH"
GET/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

Every 200 response echoes the routing decision so you can audit residency and cost per request.
x-phi-routedmodel/region/class

The resolved route: modelId/regionCode/dataClass — authoritative record of what served the request.

x-phi-tierphi | non_phi

Whether the chosen route was PHI-eligible.

x-phi-attemptsnumber

How many routes were tried before one succeeded (fail-over stays inside the PHI tier for PHI requests).

x-phi-cost-micronumber

Metered 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 | absent

Whether 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-iduuid

Unique id for the request — quote it in support tickets.

Response
< 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

Routing is per region: set 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 region

The 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 / EU

Production 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: true

Served 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

Two more endpoints round out the surface.
/v1/math/chat/completionsPOST

Identical 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 / POST

Free 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.

POST/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

POST/v1/images/generations · /v1/videos/generations
Text-to-image, text/image-to-video and audio-driven talking avatars. General (non-PHI) content only — an X-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.
modelstringoptional

krea-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.

qualitystringoptional

Video 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.

promptstringrequired

The text prompt. For image-to-video or a talking avatar, also pass an image (and audio for the avatar).

durationintegeroptional

Premium 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.

POST/v1/images/generations
curl 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"  }'
POST/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

POST/v1/etl · /v1/etl/query · /v1/search
Extract, embed and store your sources into a searchable collection, query it semantically, and run live web or async deep-research search — a stateless white-label of Twin Browser. General (non-PHI) content only (an X-PHI: true request returns phi_blocked) and paid keys only.
/v1/etlPOST

Ingest 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/queryPOST

Semantic 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/searchPOST

Live 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.

POST/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

POST/v1/sandbox/run
Deterministic, model-free code execution — run Python, R or SQL over your own data in a region-pinned, network-isolated sandbox and get a structured result back. No LLM in the loop (that is /v1/math/chat/completions). General traffic only (an X-PHI: true request returns phi_blocked until the sandbox BAA lands) and paid keys only.
languagestringrequired

python, r or sql (DuckDB).

codestringrequired

The source to execute. Returns a structured SandboxRunResult: ok, stdout, stderr, resultText, error (and any captured SVG artifacts).

inputJSON value

Optional 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_msnumber

Optional per-run timeout (capped at 120000). Requires X-Region to be CH, EU, UK or US — other regions return no_sandbox_region.

POST/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

Errors return a JSON body of the shape { "error": { "code", "message" } } with a matching HTTP status.
401missing_authorization

No Bearer token on the request.

401invalid_api_key

The token is invalid or revoked.

401api_key_expired

The 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_required

Out of credits. Top up your prepaid credit balance in the dashboard → Billing (or enable auto-recharge).

403phi_blocked

X-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_route

No route serves this task / region / data-class combination.

400unknown_model

The 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_region

The model exists, but does not serve this task in this X-Region. The error body lists the regions and tasks it does serve.

400invalid_json

The request body was not valid JSON, or failed schema validation.

502upstream_error

Every 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.

phi_blocked
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

Where to go from here once your first call is working.
/networkpage

Live region map and which providers are PHI-eligible where.

/compliancepage

nFADP / GDPR posture, sub-processors, and the audit trail you ship by default.

/pricingpage

Prepaid credits (1 USD = 300 credits; $10 / 3,000-credit minimum) and every model's per-token price.

/platformpage

Architecture, statelessness contract, and how routing decisions are made.