API reference
A REST API for Algerian Darija text-to-speech. Send text, get a WAV back. Submit text and receive a job ID immediately. Poll the job until its audio is ready.
https://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.
All keys use live billing. There is no test-key or sandbox mode.
Previously issued test keys have been revoked; create a live key to continue.
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 or Arabic Fusha text, up to 600 characters. Arabic and French may be mixed freely. |
voice required | string | A voice id from GET /voices. |
purpose | string | What the clip is for, which changes how it is performed. The list is editable without a deploy, so don't hardcode it - fetch GET /purposes for the current one. Omit it for the default. Use other to send the script without the director or added style, speed or emotion instructions. |
country | string | Speech language: dz for Algerian Darija (default), or msa for Arabic Fusha. This controls pronunciation, not translation. With other, Fusha retains only its language direction. |
speed | string | How fast to read it: slow, normal or fast. Named rather than numeric because the speech model takes the pace as an instruction, not as a rate - there is no multiplier in between. Omit it for normal. |
/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 |
|---|---|---|---|
leila |
ليلى | female | algerian_arabic |
mourad |
مراد | male | algerian_arabic |
salma |
سلمى | female | algerian_arabic |
kenza |
كنزة | female | algerian_arabic |
walid |
وليد | male | algerian_arabic |
riad |
رياض | male | algerian_arabic |
/purposes
The current purpose list, for the purpose parameter on
POST /speech. Purposes are added and retired from the admin
without a deploy, so a client should read this rather than hardcode the slugs.
| id | Label | Default |
|---|---|---|
commercial_ad |
إعلان تجاري | yes |
social_reel |
ريل لمواقع التواصل | |
story_telling |
Story telling | |
other |
Other |
/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 202 | by length | Tokens are reserved; a failed job is refunded. |
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
Google receives your script unchanged apart from trimming outer whitespace. English, French, numbers and expression tags such as [laughs] are preserved. Billing uses this submitted length.
The application limit is 600 characters. Cloud TTS also limits each script to 4,000 UTF-8 bytes; Arabic letters typically occupy two bytes. Oversized scripts are rejected before any tokens are reserved. Split longer scripts yourself.
POST /speech returns 202 with status pending and a status_url. GET that URL with your API key every three seconds until status is success or failed. While running, status is processing. Only success contains the finished audio URL. A failed job refunds its reserved tokens. Polling counts toward your API rate limit.
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, with a shared cap of
120 requests per minute across all keys belonging to an account.
Creating or rotating keys does not reset the account limit. Successful responses carry
X-RateLimit-Limit and
X-RateLimit-Remaining; a 429 adds
Retry-After in seconds. Reads and polling count too.
An account can have 3 pending or processing clips and submit up to
60 generation attempts per hour. Shared queue capacity and an hourly
generation-work budget also apply. A rejected submission returns 429 without charging tokens;
wait for Retry-After before submitting again.
Short queue-capacity limits return 5 seconds; hourly generation limits return 3600 seconds.
Only retry a submission after a definite rejection: a network timeout may hide an accepted job.
Key creation is limited to 10 attempts per hour per account. Revoking keys does not reset this allowance. There is no test-key environment field in the account response; all issued keys use live billing.