REST API

Everything the web UI does goes through these endpoints, so they get exercised by ordinary use. Base URL: https://patois.sabiapps.com

Auth and CSRF. Browser clients are session-based and must send the token from 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://patois.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

FieldTypeDefaultNotes
textstring—Required, 1–2000 characters.
fromen | pcm | jamenMust differ from to.
toen | pcm | jam—Required.
registerpolite | casual | streetcasualControls slang and how phonetic the spelling is.
refinebooleantrueRun the model pass. Ignored when no provider is configured, or when rule confidence is already high.
savebooleantrueRecord 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

EndpointReturns
GET /api/languagesLanguage codes, endonyms, speech-synthesis locales, registers and valid pairs.
GET /api/grammar/:langThe 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/explainLexicon entry if known; otherwise a model explanation, clearly labelled as unverified.
GET /api/phrasebookThe hand-written phrasebook, by category.
GET /api/engine/statsLexicon sizes, rule counts, provider stats and usage.

Session data

EndpointPurpose
GET /api/history?page=&perPage=&favorites=This session's translations.
POST /api/history/:id/favoriteToggle saved state.
DELETE /api/history/:idDelete one entry.
DELETE /api/history?keepFavorites=Clear history.
GET /api/history/export?format=json|csvDownload your history.
POST /api/correctionsSubmit a better wording for a translation we produced.
POST /api/contributionsSubmit a missing lexicon entry.
GET /api/contributions?status=&lang=Browse the review queue.
POST /api/contributions/:id/voteVote for a pending submission.
GET /api/contributions/:lang/exportApproved 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…"
  }
}
StatusCodeMeaning
400bad_requestMalformed request.
403forbiddenMissing or stale CSRF token.
404not_foundNo such route or resource.
409conflictDuplicate submission — the response carries the existing id.
422unprocessableValidation failed; see details.
429rate_limitedSlow down; check the RateLimit-* headers.
503internal_errorDatabase 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.