REST API
Everything the web UI does goes through these endpoints, so they get exercised by ordinary
use. Base URL: https://pidgin.sabiapps.com
GET /api/csrf in an x-csrf-token header on unsafe methods.
Server-to-server clients send x-api-key instead and are exempt from CSRF.
Rate limits: 300 requests per 15 minutes overall, 40 for
model-backed endpoints.
POST /api/translate
The main endpoint. Returns the translation plus the full derivation.
Request
curl -sX POST https://pidgin.sabiapps.com/api/translate \
-H 'content-type: application/json' \
-H 'x-api-key: YOUR_KEY' \
-d '{
"text": "I am going to the market to buy food.",
"from": "en",
"to": "pcm",
"register": "casual",
"refine": true,
"save": false
}'
Fields
| Field | Type | Default | Notes |
|---|---|---|---|
text | string | — | Required, 1–2000 characters. |
from | en | pcm | jam | en | Must differ from to. |
to | en | pcm | jam | — | Required. |
register | polite | casual | street | casual | Controls slang and how phonetic the spelling is. |
refine | boolean | true | Run the model pass. Ignored when no provider is configured, or when rule confidence is already high. |
save | boolean | true | Record in the session's history. |
Response
{
"id": 41,
"source": { "text": "I am going to the market to buy food.", "lang": "en" },
"target": { "text": "I dey go di market to buy food.", "lang": "pcm" },
"register": "casual",
"confidence": 0.96,
"coverage": 1,
"engine": "rules",
"ruleText": null,
"alternatives": [],
"gloss": [
{ "source": "I", "target": "I", "via": "lexicon:function", "note": null },
{ "source": "am going", "target": "dey go", "via": "rule:prog-present",
"note": "progressive" }
],
"rulesFired": [
{ "id": "prog-present", "desc": "\"am/is/are V-ing\" → progressive",
"gloss": "progressive aspect", "count": 1 }
],
"unknownWords": [],
"pivotVia": null,
"notes": [],
"ai": null
}
confidence is derived, not guessed: it starts from the share of content words
the lexicon could resolve, is penalised for words passed through untouched, and — when the
model pass runs — is blended with the model's own stated confidence. via tells
you exactly which stage produced each output token, so a bad translation can be traced to a
missing lexicon entry or a misfiring rule rather than to a black box.
POST /api/translate/compare
One English text, both creoles, for side-by-side comparison.
{ "text": "This food is very good.", "register": "casual" }
POST /api/translate/registers
One text at all three registers — the clearest way to see what register does.
{ "text": "Please help me.", "to": "pcm" }
Reference data
| Endpoint | Returns |
|---|---|
GET /api/languages | Language codes, endonyms, speech-synthesis locales, registers and valid pairs. |
GET /api/grammar/:lang | The full marker table, pronoun paradigm and rule list for pcm or jam. |
GET /api/dictionary/:lang?q=&pos=&direction=&limit= | Lexicon search, both directions, with fuzzy fallback. |
POST /api/dictionary/:lang/explain | Lexicon entry if known; otherwise a model explanation, clearly labelled as unverified. |
GET /api/phrasebook | The hand-written phrasebook, by category. |
GET /api/engine/stats | Lexicon sizes, rule counts, provider stats and usage. |
Session data
| Endpoint | Purpose |
|---|---|
GET /api/history?page=&perPage=&favorites= | This session's translations. |
POST /api/history/:id/favorite | Toggle saved state. |
DELETE /api/history/:id | Delete one entry. |
DELETE /api/history?keepFavorites= | Clear history. |
GET /api/history/export?format=json|csv | Download your history. |
POST /api/corrections | Submit a better wording for a translation we produced. |
POST /api/contributions | Submit a missing lexicon entry. |
GET /api/contributions?status=&lang= | Browse the review queue. |
POST /api/contributions/:id/vote | Vote for a pending submission. |
GET /api/contributions/:lang/export | Approved entries in lexicon-file shape. |
Errors
Every failure is the same shape, with a stable code.
{
"error": {
"code": "unprocessable",
"message": "Some fields need attention.",
"details": [ { "path": "to", "message": "Pick two different languages." } ],
"requestId": "8f1c…"
}
}
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Malformed request. |
| 403 | forbidden | Missing or stale CSRF token. |
| 404 | not_found | No such route or resource. |
| 409 | conflict | Duplicate submission — the response carries the existing id. |
| 422 | unprocessable | Validation failed; see details. |
| 429 | rate_limited | Slow down; check the RateLimit-* headers. |
| 503 | internal_error | Database unreachable. GET /healthz reports component state. |
A model outage never produces a 5xx: the refinement pass fails soft and you get the
rule-engine translation with a note in notes.