Documentation
Lahja API
Lahja (لهجة, “dialect”) serves Arabic dialect phrases written and recorded by native-speaking teachers. Nothing is AI-generated: AI is only ever used to match phrases by meaning.
Each phrase has its dialect text with vowel marks, a transliteration, a literal English gloss, a meaning (what the phrase actually does), structured nuance such as register and addressee, and one or more recordings. Recordings note the speaker's gender, age band, and where they grew up and live.
The API is REST over JSON, described by an OpenAPI 3.1 spec. Generate a typed client in any language from it.
Authentication
Every request except GET /health needs an API key, sent as a bearer token. Keys look like lahja_live_… and are shown once when issued. Keep them on your server, never in a shipped app or web page.
Authorization: Bearer lahja_live_XXXXXXXXXXXXXXXX
Quickstart
1. Check your balance
curl https://api.lahjaapi.com/v1/account \
-H "Authorization: Bearer $LAHJA_KEY"
{ "id": "3b7d…", "name": "Acme Learning", "balance": 10 }
2. Ask for what you need
A prompt request is something you want said, in the dialects you want, under your own key. Requests are shared: if another customer already asked for the same meaning, addressee and register, yours joins theirs and anything already recorded is ready.
curl -X PUT https://api.lahjaapi.com/v1/requests \
-H "Authorization: Bearer $LAHJA_KEY" \
-H "Content-Type: application/json" \
-d '{"requests":[{
"key": "game.jump",
"english": "jump",
"meaning": "telling one boy to jump, as a game command",
"addressee": "masculine",
"register": "casual",
"dialects": ["egyptian", "levantine"]
}]}'
3. Check what's published
GET /requests returns each of your requests with the published phrase IDs per dialect.
4. Unlock
curl -X POST https://api.lahjaapi.com/v1/phrases/b41e…/unlock \
-H "Authorization: Bearer $LAHJA_KEY"
{ "phraseId": "b41e…", "charged": true, "creditsCharged": 1, "balance": 9 }
Credits & billing
- Billing is prepaid credits. Every new account starts with 10 credits; there is no other free tier.
- Unlocking a phrase charges its credit cost (1 by default) the first time. After that you own it: unlocking again is free and returns
charged: false. - Unlocked content is yours to use however you want.
- Every credit movement is recorded in an append-only ledger you can read at
GET /account/ledger. - Credit packs: 50 credits for $25, 250 for $100, 1,000 for $350. Credits never expire.
Errors
Errors return a JSON body with a machine-readable error and a human-readable message.
{ "error": "insufficient_credits", "message": "This phrase costs 1 credit; your balance is 0." }
| Status | Meaning |
|---|---|
400 | A field is invalid. Nothing was saved. |
401 | Missing, unknown or revoked API key. |
402 | Not enough credits to unlock. |
404 | No published phrase with this ID. |
API reference
All paths are relative to https://api.lahjaapi.com/v1. The OpenAPI spec is the source of truth.
Health
No key needed. Returns { "status": "ok" } when the service is up.
Account
Your account and credit balance. balance can be negative after a refund or chargeback, which blocks unlocking.
| Field | Type | Description |
|---|---|---|
id | uuid | Your customer ID. |
name | string | Account name. |
balance | integer | Credits available. |
Ledger
Credit movements, most recent first. limit is 1–200 (default 50).
| Field | Type | Description |
|---|---|---|
id | int64 | Entry ID. |
delta | integer | Credits added (positive) or spent (negative). |
reason | enum | signup_grant, purchase, unlock, refund, chargeback, adjustment |
phraseId | uuid? | Set for unlocks. |
createdAt | date-time | When it happened. |
Prompt requests
Creates or updates up to 500 requests at once. Re-submitting a key replaces its meaning and dialects. If any request is invalid, nothing is saved.
| Field | Type | Description |
|---|---|---|
key | string | Your own identifier, unique in your account (≤200), e.g. simon_says_verb.jump. |
english | string | Short label shown to teachers (≤100), e.g. “jump”. |
meaning | string | The full request as a sentence (≤500). With addressee and register, decides whether you join an existing prompt. |
addressee | enum | Who it's said to. See enums. |
register | enum? | How formal. See enums. |
dialects | array | One or more dialects you want. |
fushaText | string? | Optional Modern Standard Arabic form, to help teachers (≤200). |
guidance | string? | Optional notes for teachers (≤1000). |
All your requests by key. Each has the shared promptId and a published list of { dialect, phraseIds } for your requested dialects that have recordings. Unlock those IDs to get the content.
Unlock a phrase
Charges the phrase's credit cost the first time. Repeat calls are free and return charged: false. Returns 402 if your balance is too low.
| Field | Type | Description |
|---|---|---|
phraseId | uuid | The phrase. |
charged | boolean | False when you already owned it. |
creditsCharged | integer | Credits taken by this call. |
balance | integer | Your balance afterwards. |
Dialects & enums
| Enum | Values |
|---|---|
Dialect | fusha, egyptian, levantine, gulf, iraqi, maghrebi, sudanese |
Addressee | masculine, feminine, plural, any (when the form doesn't matter) |
Register | intimate, casual, polite, formal |
Coming soon
- Phrase content and audio on unlock: vowelled text, transliteration, glosses, nuance and signed CDN audio URLs.
- Search: free, rate-limited search across the published catalogue.
- Cross-dialect matching: find the closest phrase in another dialect by meaning, filtered by addressee and register, with teacher-confirmed matches ranked first.
- Self-serve sign-up and credit packs through Stripe Checkout.
For teachers
Lahja's content comes from native-speaking teachers. Teachers sign in with their ArabicCompanion teacher account, are approved for their dialects, and record phrases from a shared queue. Every phrase is confirmed by a second teacher of the same dialect before it's published, and teachers are paid for approved contributions.