DocsHTTP API6 min read
HTTP API
Four endpoints, JSON in and JSON out. Every SDK is a thin wrapper over this, so anything a framework cannot do you can do yourself.
POST /v1/translate- One string. The one you will call most.
POST /v1/translate/batch- Up to 20 strings, in one language or up to eight, answered by index.
GET /v1/strings?target=ar- The whole project as one map, by source text or by your own key.
GET /v1/languages- What the project is configured to speak.
GET /health- No key. Reports which backends are wired, never a secret.
curl
# $SIYAK_HOST is the host printed on your project's API keys screen.
curl -s "$SIYAK_HOST/v1/translate" \
-H "authorization: Bearer $SIYAK_KEY" \
-H "content-type: application/json" \
-d '{"text":"Your card expires soon","target":"ar","context":"screen:checkout"}'
# → {"text":"بطاقتك تنتهي قريبًا","origin":"model","status":"pending",
# "chars":20,"badge":"Translated with Siyak",
# "usage":{"used":1320,"limit":15000,"month":"2026-10"}}The alternative header x-siyak-key works too; nothing else about the request changes.
Request fields
| field | required | notes |
|---|---|---|
text | yes | 1–500 characters after sanitizing: control characters stripped, ends trimmed. Longer is a 400, not a truncation. |
target | yes | BCP-47, e.g. ar, ar-EG, zh-Hans. Stored in one canonical spelling: AR-eg becomes ar-EG, and both name the same string. |
targets[] | batch | Up to 8 tags instead of one target. Each language bills its own characters. |
source | no | Falls back to the project's configured source language. |
context | no | ≤160 characters, no < or >. Part of the stored identity — see Concepts. |
key | no | Your own name for the string, e.g. checkout.title. Dotted, ≤80 characters. Optional, and never required to translate. |
items[] | batch | 1–20 entries of {text, context?, key?}. The language envelope applies to all of them. |
Response fields
| field | meaning |
|---|---|
text | The translation. |
target | The language you asked for, in its canonical spelling. Match on this, never on what you sent. |
origin | cache | approved | pending | model — where this answer came from. |
status | approved | pending | rejected — whether anyone has signed off on it. |
chars | What this call billed, when it billed. |
badge | Present when attribution is required. Render it — the free tier is paid for with it. |
usage | {used, limit, month} — the project's character meter. |
results | In a targets[] batch only: {tag: {output, origin, status}}, one entry per language, the tags canonicalised. |
glossary_drift | Set on a model answer that ignored a pinned term. |
Batch semantics
- Results are matched by index, never by the echoed input — the server sanitizes before echoing, so the text you sent is not always the text it returns.
- A row with output: null carries its own error string; the envelope is still 200.
- If the month runs out mid-batch the server stops there: fewer rows than you sent means nothing further was attempted, and the remaining items stay untranslated rather than charged.
- With targets[] the answers move to results, keyed by the canonical tag — the targets array in the reply tells you which spelling was used. output stays null so a row never changes shape.
Status codes
| code | error | what to do |
|---|---|---|
400 | invalid_request | text too long, bad target tag, context with angle brackets. Fix the call; it is deterministic. |
401 | invalid_api_key | Revoked, disabled project, wrong environment, or a typo — deliberately indistinguishable so the endpoint cannot be probed for live keys. |
402 | quota_exceeded | The month is spent. usage is in the body. Stop, tell the developer, and do not retry in a loop. |
403 | origin_not_allowed | Your browser origin is not on the project's allowed-domain list. Add it in the dashboard. |
404 | not_found | Wrong path. Everything lives under /v1. |
409 | key_conflict | The key you sent already names a different string in that language. Nothing was stored and nothing was billed — rename your key or drop it, and the call is a plain translate again. |
429 | rate_limited | Honor retry_after. The limit is per key, per minute. |
503 | backend_unavailable | The model is down. The string was still filed in the Inbox, so someone can answer it by hand — serve your source text and move on. |
500 | internal_error | Ours. The message is intentionally empty; open a ticket with the month and project. |