> Section "Job lifecycle" of the AthanLab Developer API documentation (API version 1.0.0). On the web: https://athanlab.com/docs#jobs. Whole documentation as Markdown: https://athanlab.com/docs.md # Job lifecycle A job starts as `processing` and ends in exactly one final status. It never moves back. ## Statuses | Status | Meaning | Characters | | --- | --- | --- | | `processing` | Accepted and charged. `progress` rises from 0 toward 1. | Charged | | `succeeded` | The audio is ready: `audio.url` is the download path. | Charged | | `failed` | `error.code` says why (see [Job failure codes](https://athanlab.com/docs#job-failures)). | Refunded in full, automatically | | `cancelled` | You cancelled it. | The unprocessed share is refunded | ## Polling Poll `GET /speech/{id}` while the job is `processing`: wait the `Retry-After` of the create response (2 seconds) before the first poll, then back off to about 10 seconds between polls. A successful poll carries no `Retry-After` of its own, so keep your own delay. Polls count against the per-key read limit of 600 requests a minute, and against the [per-network limit](https://athanlab.com/docs#rate-limits). A rate-limited poll returns `429` with its own `Retry-After`. To follow many jobs, list the ones still processing in one request instead of polling each: Request: ```sh curl -sS "https://api.athanlab.com/api/v1/speech?status=processing&limit=50" \ -H "X-API-Key: $ATHANLAB_API_KEY" ``` The same call with `status=failed` finds the jobs that failed. ## Webhooks The API does not send webhooks. Poll for the outcome as described above. ## Safe retries Send an `Idempotency-Key` header with a new UUID on every `POST /speech` and `POST /deliveries`, and reuse it when you retry that request. A network can fail after the server has accepted a job, and the key keeps the retry from creating and charging a second one. Within 24 hours, the same key and body return the original job (or delivery) with `Idempotent-Replayed: true` and no new charge. The same key with a different body returns [409 idempotency_conflict](https://athanlab.com/docs#errors-idempotency_conflict). An `Idempotency-Key` used on `POST /speech` never conflicts with the same value on `POST /deliveries`. ## Quote first with dry_run Add `"dry_run": true` to check a request and see its price. Nothing is created or charged, and the response is a [DryRunQuote](https://athanlab.com/docs#schema-DryRunQuote), as in this example: Request: ```sh curl -sS https://api.athanlab.com/api/v1/speech \ -H "X-API-Key: $ATHANLAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "မင်္ဂလာပါ။ အသံလက်ဘ်မှ ကြိုဆိုပါတယ်။", "dry_run": true}' ``` 200 OK: ```json { "object": "speech.quote", "characters": 35, "dispatches": 1, "spendable": 1950000, "sufficient": true } ``` `characters` is what the job would cost, `dispatches` the number of pieces the text is spoken in, and `sufficient` whether your balance covers it. ## Cancel `POST /speech/{id}/cancel` stops a job that is still processing. The share already generated stays charged and the rest is refunded, as `usage.characters_refunded` shows. Cancel works while the API is in [read-only mode](https://athanlab.com/docs#errors-api_read_only) and after your Max plan ends. A finished job returns [409 job_finished](https://athanlab.com/docs#errors-job_finished). Request: ```sh curl -sS -X POST https://api.athanlab.com/api/v1/speech/$JOB_ID/cancel \ -H "X-API-Key: $ATHANLAB_API_KEY" ``` ## Retention Audio and input text are kept for 30 days after the job is created (`audio.expires_at`), then deleted. Download what you need to keep. After that, a download returns [410 audio_expired](https://athanlab.com/docs#errors-audio_expired), but the job record (status, characters, metadata) stays readable. Audio and text sent to AthanInbox are the exception: they are kept until you delete them in AthanInbox or delete your account (see [AthanInbox](https://athanlab.com/docs#athaninbox)). The API never returns your input text. ## Job failure codes A failed job reports one of these codes in `error.code`. They are not HTTP errors: the poll itself succeeds with `status: "failed"`, and the characters are refunded in full. > [!NOTE] > **capacity_unavailable has two meanings** > > As a job failure, the job was charged and then refunded, so retry later. As an HTTP 503 when you create a job, nothing was charged: wait the `Retry-After`, then retry (see [503 capacity_unavailable](https://athanlab.com/docs#errors-capacity_unavailable)). That 503 can reach you as an HTML 504 (see [Errors](https://athanlab.com/docs#errors)). | Code | Retryable | Meaning | | --- | --- | --- | | `input_rejected` | No | The text could not be turned into speech. Change the input before retrying. The characters were refunded. | | `capacity_unavailable` | Yes | No speech capacity became available before the job deadline. Retry later. The characters were refunded. | | `generation_failed` | Yes | Speech generation failed. Retry the request. The characters were refunded. | | `interrupted` | Yes | The job was interrupted by a server restart. Retry the request. The characters were refunded. |