API reference
A REST API for Algerian Darija text-to-speech. Send text, get a WAV back. JSON in, JSON out, one synchronous call. There is no polling and no job queue to babysit.
https://www.natiq.studio/api/v1
per clip, by text length
maximum per request
default per key
Authentication
Pass your key as a bearer token. Keys look like
natiq_live_a1b2c3d4_... and are stored hashed. If you lose
one, revoke it and issue another.
A test key behaves identically but is meant for your staging
environment; a read-scoped key can list things but never spends tokens.
/speech
Synthesize one clip. Returns when the audio is ready.
| Parameter | Type | Description |
|---|---|---|
text required | string | Darija text, up to 600 characters. Arabic and French may be mixed freely. |
voice required | string | A voice id from GET /voices. |
/speech
and
/speech/{id}
List your clips (newest first) or fetch one. Filter with
status; page with
limit (max 100) and offset.
/voices
Every active reference voice. Filter by gender or accent.
| id | Name | Gender | Accent |
|---|---|---|---|
alg_female_yasmine |
ياسمين (Yasmine) | female | algerian_arabic |
dz_fr_female_amira |
أميرة (Amira) | female | french_algerian |
fr_female_celine |
سيلين (Céline) | female | french |
dz_female_sofia |
صوفيا (Sofia) | female | algerian_arabic |
alg_male_mahmoud |
محمود (Mahmoud) | male | algerian_arabic |
alg_male_riad |
رياض (Riad) | male | french_algerian |
/account
Your balance, this key's usage, and the pricing formula. Poll it to alert yourself before you run dry, and read the formula from here rather than hard-coding a price. See token usage.
How token usage is calculated
Clips are billed by length. Inference runs for as long as the text is, so a 600 character script costs more than a one line prompt.
The base covers the fixed cost of a request. Characters are counted after leading and trailing whitespace is trimmed, which is the same text the engine receives. Every character counts, including spaces inside the text, Arabic letters, Latin letters and punctuation.
| Text length | Tokens |
|---|---|
| 40 characters | 2 |
| 100 characters | 3 |
| 200 characters (a typical clip) | 5 |
| 400 characters | 9 |
| 600 characters (the limit) | 13 |
The cheapest possible request is 2 tokens and the most
expensive is 13. Text longer than 600
characters is rejected with invalid_request, so you split it yourself and
each part is billed on its own length.
| Call | Cost | Why |
|---|---|---|
POST /speech returning 201 | by length | The clip was produced and stored. |
POST /speech returning 502 | 0 | Synthesis failed. The debit is refunded in the same transaction that records the failure. |
POST /speech returning 4xx | 0 | Rejected before the engine ran. |
GET /speech, /voices, /account | 0 | Reads are free. They still count against the per-minute rate limit. |
Worked example
A catalogue of 40 product descriptions averaging 220 characters each is 40 requests of 1 + ceil(220 / 50) tokens. One 1,500 character article splits into 3 requests of 500 characters, each costing 1 + ceil(500 / 50).
Splitting on sentence boundaries costs slightly more than one long request, because each part pays the base again. Splitting also gives you better control over pacing, so the trade is usually worth it.
Reading your usage
Every successful synthesis returns tokens_used and
tokens_remaining, so you can log the real cost of each
call. GET /account returns the balance and the formula
itself, which is what you should price a batch against rather than hard-coding a number.
estimated_clips_remaining is a convenience, not a
promise: it says how many clips of 200 characters the balance buys, and
names that length so you can redo the sum for your own text.
The per-key totals on your keys page and the line-by-line ledger on your account page record the same numbers server-side, including refunds.
When you run out
Synthesis returns 402 insufficient_tokens and nothing is
charged. The message states how many tokens that request needed, so you can decide whether
to shorten the text or buy more. Balances do not expire and packs stack.
New accounts start with 100 free tokens once the email address is confirmed.
How your text is read
The model was trained on Arabic script only, so digits and Latin letters are rewritten into words before synthesis. You do not have to do anything: send the text as you have it and the service spells it out.
| You send | The model reads |
|---|---|
| السعر 3750 دج | السعر ثلاثة آلاف وسبعمية وخمسين دينار |
| راه disponible في le stock | راه ديسبونيبل في لو ستوك |
| خصم 50% | خصم خمسين بالمية |
| 0555123456 | صفر خمسة خمسة خمسة واحد زوج ثلاثة أربعة خمسة ستة |
Every response carries both strings. text is what you
sent, byte for byte. text_spoken is what the engine read.
Compare them when a clip does not sound the way you expected: the answer is usually in
the second one.
text_spoken.
Four characters of "3750" become twenty five characters of speech, and the engine runs on
the twenty five. Price a batch on the spelled-out length, not the digits.
Rewriting is idempotent, so text you have already spelled out yourself passes through
untouched. A text whose expansion runs past 1200 characters is
rejected with invalid_request before anything is billed; split it and send
the parts.
Numbers follow Algerian Darija, so 11 is حداش rather than أحد عشر. A long digit run starting with a zero is read one digit at a time, because a ten digit number is not a quantity anyone says out loud. French and English words are transliterated, using the spellings the training data uses where they exist.
Formats are not interpreted. 14:30 is read as two
numbers, not as a clock time, because a colon could equally be a score or a ratio and
guessing at that is not ours to do. Write out what you want said.
Errors
Every failure returns the same envelope, so you can branch on error.code.
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed. Check error.param. |
| 401 | missing_credentials | No key was sent. |
| 401 | invalid_api_key | Unknown, revoked, or expired key. |
| 402 | insufficient_tokens | Buy more at pricing. |
| 403 | insufficient_scope | Read-only key tried to synthesize. |
| 403 | email_unverified | Confirm the account email address before synthesizing. |
| 404 | not_found | No such voice or speech object. |
| 429 | rate_limit_exceeded | Back off; see Retry-After. |
| 502 | engine_error | Synthesis failed. Your tokens were refunded automatically. |
A failed generation never costs you anything. The tokens are refunded in the same transaction that records the failure.
Rate limits
60 requests per minute per key by default. Every response carries
X-RateLimit-Limit and
X-RateLimit-Remaining; a 429 adds
Retry-After. Need more? Ask and we will raise it on your key.