Text-to-Music and Interpretation are asynchronous and poll-based. A create call returns identifiers immediately; you then poll the project or job endpoints until the resource reaches a terminal state. Neither product uses callbacks or webhooks — that mechanism belongs to the AI Cover flows described in Callback.
The shape of an async call
- Create with an
Idempotency-Key. You receive a stable resource identifier (project, job, target, upload, export). - Poll the read endpoints for that resource.
- Act when the resource is terminal: play, download or export — or read the failure and recover.
Terminal states
Text-to-Music
Read the project withGET /sound_clone/api/v1/music/projects/{projectId}.
RECOVERY_REQUIRED is not terminal. It means the outcome still needs reconciliation: keep the identifiers and keep querying. It is neither a failure nor a refund.
Interpretation
Read the project withGET /sound_clone/api/v1/interpretation/projects/{projectId}.
READY and FAILED_RELEASED are the per-target end states. summaryState is a roll-up: PARTIAL_READY means some languages are done and some are not — it is a normal interim state, not an error.
RECONCILING (on a target, a job or a logical generation) means the result is still being reconciled. Treat it as “still working”, never as a failure or as an already-released reservation.
Exports (Interpretation)
An export is its own small async resource:PROCESSING, RETRY or READY. PROCESSING and RETRY are transient; keep polling the same exportId. READY is the only state that has a download URL. In particular, do not treat RETRY as a terminal failure and never start a new paid generation to work around a slow export.
Choosing a poll interval
nextPollAfterMsis the field meant for this. On Interpretation it is currently alwaysnull, because the service does not publish a suggested interval yet. Handlenullexplicitly.- Use a bounded interval with jitter — 2–5 seconds is a reasonable starting point for both products — and an overall bounded wait.
- Do not poll in a tight loop, and do not poll a resource you can no longer act on.
Recovering from a timeout
A timeout is a client-side decision. It does not cancel the server-side work and it does not mean the work failed. When you time out:- Report the resource identifiers (
projectId,jobId,targetId,exportId) to the user or log them. - Stop; keep the identifiers.
- Resume later by querying the same identifiers — the work continues server-side.
- Treat a timeout as a failure and issue a refund or a release. Only the real ledger state can tell you what happened to the reservation.
- Resubmit a paid generation because you lost the response. That is what the identifiers and the idempotency key are for.
Idempotency and replay
Idempotency-Key is required on the create/accept endpoints. Use a unique printable ASCII value of 16–64 characters per distinct operation, and persist it together with the request body summary and the returned identifiers, so a restart can resume instead of starting over.
Important: a replay is not guaranteed to return the first response.
Recovery rule for both: use the stable identifier to re-query (
GET /projects/{id}, GET /jobs/{id}) rather than assuming the replay equals the first response.
Conflicts are different from replays:
- Music: same key with a different body →
code = 47008. - Interpretation: same key with a different body →
code = 47111.
Transient versus terminal failures
Not every failure should end your flow:Expired or consumed quotes
- A quote has a time-to-live (
quoteTtlSeconds, normally 600 seconds). - Text-to-Music consumes a quote when it is used: re-submitting the same
quoteIdwith a different key is rejected with47007. - Interpretation reuses the acceptance for an already-accepted quote instead of charging again.
Retrying a failed Interpretation target
Only a target inFAILED_RELEASED with action = "RETRY" may be retried:
GET /projects/{projectId}/targets/{targetId}/retry-plan— the server decides the amount and the next reservation cycle.POST /projects/{projectId}/targets/{targetId}/retrywith the plan’snextReservationCycle,charactersandrateVersion.
OPERATION_RETRY is replay-safe, so a retried POST with the same key does not create a second charge.
Regenerating a Text-to-Music arrangement
If the arrangement is not what you wanted,POST /projects/{projectId}/arrangement/regenerate with { "expectedVersion": n } requests a new arrangement (idempotent). An expectedVersion mismatch is rejected with 47016; re-read the project and retry with the current version.
A sample polling loop
examples/music/quickstart.py, examples/music/quickstart.mjs, examples/interpretation/quickstart.py, examples/interpretation/quickstart.mjs) implement exactly this loop, including the null handling and the bounded wait.