Article to Speech (ATS)
Convert a full article into narrated audio and a transcription, delivered asynchronously to your callback URL.
The Article to Speech (ATS) endpoint accepts the text of an article and generates an MP3 narration together with a synchronized transcription (.srt) file. Generation runs asynchronously: the request returns immediately with 202 Accepted, and the finished files are delivered to the callbackURL you provide. This is the same service that powers the ATS WordPress plugin.
Do not wait on the HTTP response for your audio. The endpoint replies 202 right away and POSTs the result to your callbackURL when generation completes.
Authentication
Include your company API key as a Bearer token in the Authorization header.
Authorization: Bearer your_api_key
Request Body
Send a JSON body with the raw article text and a postData object describing the request.
| Parameter | Required | Type | Description |
|---|---|---|---|
| text | required | string | The full article text to convert into speech. |
| postData | required | object | Request metadata. See the fields below. |
postData Object
| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| name | required | string | — | Title of the article. Used as the title of the generated audio request. |
| articleId | required | string | — | Your unique identifier for the article. Acts as the idempotency key (see Idempotency below) and is echoed back in the callback. |
| voiceId | required | string | — | The voice to narrate with. Use an ID from the voice list. |
| callbackURL | required | string (URL) | — | HTTPS URL that Moknah will POST the finished audio and transcription URLs to. The latest value is stored for the company. |
| preprocessType | optional | string | "0" | Text preparation mode, sent as a string. Use "0" for none (narrate verbatim) or "2" for AI preprocessing (cleaner narration; doubles the character cost). It must be a JSON string — a bare number such as 0 or 2 is rejected during text processing. |
| regenerate | optional | boolean | false | When true, forces a fresh generation even if this articleId was generated before. |
Example Request
curl -X POST "https://api.moknah.io/process-text" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"text": "الذكاء الاصطناعي يتطور بسرعة كبيرة في كل المجالات.",
"postData": {
"name": "AI is evolving fast",
"articleId": "post-123",
"voiceId": "12",
"callbackURL": "https://your-site.com/wp-json/ats/v1/callback",
"preprocessType": "0",
"regenerate": false
}
}'
import requests
response = requests.post(
"https://api.moknah.io/process-text",
headers={
"Authorization": "Bearer your_api_key",
"Content-Type": "application/json"
},
json={
"text": "الذكاء الاصطناعي يتطور بسرعة كبيرة في كل المجالات.",
"postData": {
"name": "AI is evolving fast",
"articleId": "post-123",
"voiceId": "12",
"callbackURL": "https://your-site.com/wp-json/ats/v1/callback",
"preprocessType": "0",
"regenerate": False
}
}
)
# 202 Accepted — the audio is delivered to your callbackURL
print(response.status_code, response.text)
const response = await fetch(
'https://api.moknah.io/process-text',
{
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: 'الذكاء الاصطناعي يتطور بسرعة كبيرة في كل المجالات.',
postData: {
name: 'AI is evolving fast',
articleId: 'post-123',
voiceId: '12',
callbackURL: 'https://your-site.com/wp-json/ats/v1/callback',
preprocessType: '0',
regenerate: false
}
})
}
);
// 202 Accepted — result arrives at your callbackURL
Immediate Response
If the request is accepted, the endpoint responds immediately:
Processing started. Results will be sent to the callback URL.
Callback Payload
When generation finishes, Moknah sends a POST request to your callbackURL with this JSON body:
{
"articleId": "post-123",
"response": {
"audioFile": "https://api-storage.moknah.io/.../******.mp3",
"srtFile": "https://api-storage.moknah.io/.../******.srt",
"signature": "<hmac-sha256-hex>"
}
}
| Field | Description |
|---|---|
| articleId | The same articleId you sent in the request. |
| response.audioFile | Public URL of the generated MP3 narration, or null if generation failed. |
| response.srtFile | Public URL of the synchronized transcription (.srt) file, or null if generation failed. |
| response.signature | HMAC-SHA256 signature you can use to verify the payload is authentic (see below). |
A callback is also sent when generation produces no audio. In that case response.audioFile and response.srtFile are null (the signature still covers articleId|null|null). Treat any callback whose audioFile or srtFile is null as a failure: do not mark the article complete, and resend it with regenerate set to true.
Verifying the Signature
The signature is an HMAC-SHA256 (hex) of the string articleId|audioFile|srtFile, keyed with the SHA-256 hex digest of your API key. Recompute it on your side and compare to confirm the callback came from Moknah and was not tampered with.
import crypto from 'crypto';
const keyHash = crypto.createHash('sha256').update(API_KEY).digest('hex');
const payload = `${articleId}|${audioFile}|${srtFile}`;
const expected = crypto.createHmac('sha256', keyHash).update(payload).digest('hex');
const isValid = expected === signature;
import hashlib, hmac
key_hash = hashlib.sha256(API_KEY.encode()).hexdigest()
payload = f"{article_id}|{audio_file}|{srt_file}"
expected = hmac.new(key_hash.encode(), payload.encode(), hashlib.sha256).hexdigest()
is_valid = hmac.compare_digest(expected, signature)
Idempotency & Regeneration
- Requests are keyed by articleId per company. A new articleId starts a fresh generation.
- If an article is currently being generated, repeat requests are rejected with 409 ALREADY_PROCESSING.
- If an article was already generated and regenerate is false, the existing audio is re-delivered to your callbackURL without re-generating (and without re-charging).
- Set regenerate to true to force a brand-new generation for an existing articleId.
- If a previous attempt failed, the stored result for that articleId may be empty. A later request with regenerate false can then re-deliver that empty result, so resend with regenerate true (or use a new articleId) to recover.
Concurrency & throughput
Each article is narrated by generating its sentences in parallel against the upstream text-to-speech provider, which enforces a concurrency limit shared across your whole account. Submitting many articles at the same time can exceed that limit and cause individual sentences — and therefore whole articles — to fail.
- When narrating a multi-part page, keep only a few articles in flight at once (around three) instead of submitting them all simultaneously.
- Retry any failed article with regenerate set to true; already-generated articles are cached and need not be resent.
- The /process-text endpoint is also rate-limited per client; bursts above the limit receive 429 Too Many Requests, so space out or batch your submissions.
Billing
Character cost is calculated as the number of characters in text multiplied by the preprocessing factor (1 by default, 2 when preprocessType is 2). Credits are deducted only when generation actually runs. Re-delivery of an already-generated article is free, and if generation or delivery fails the deducted credits are refunded automatically.
Errors
| Status | Code | Description |
|---|---|---|
401 |
AUTH_FAILED | Missing Authorization header or invalid API key. |
422 |
VALIDATION_ERROR | Missing text, or invalid/missing postData fields. |
400 |
INVALID_VOICE_ID | The provided voiceId does not exist. |
409 |
ALREADY_PROCESSING | This article is already being generated. Retry after it completes. |
422 |
PROCESSING_ERROR | Generation failed. Any deducted credits are refunded. |
422 |
REFUND_FAILED | Processing failed and the automatic refund did not complete. Contact api@moknah.io. |
For API-related questions or issues, contact us at api@moknah.io.