Skip to main content
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

  1. Create with an Idempotency-Key. You receive a stable resource identifier (project, job, target, upload, export).
  2. Poll the read endpoints for that resource.
  3. Act when the resource is terminal: play, download or export — or read the failure and recover.
Always keep the returned identifiers. They are the only reliable way to recover after a timeout, a crash or a lost response.

Terminal states

Text-to-Music

Read the project with GET /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 with GET /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

  • nextPollAfterMs is the field meant for this. On Interpretation it is currently always null, because the service does not publish a suggested interval yet. Handle null explicitly.
  • 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:
  1. Report the resource identifiers (projectId, jobId, targetId, exportId) to the user or log them.
  2. Stop; keep the identifiers.
  3. Resume later by querying the same identifiers — the work continues server-side.
Do not:
  • 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.
A conflict means something already happened for that key. Never silently switch to a new key to force the call through; re-read the resource to find out what the server already did.

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 quoteId with a different key is rejected with 47007.
  • Interpretation reuses the acceptance for an already-accepted quote instead of charging again.
So when a generation attempt fails before any work starts, re-quote and try again. When it fails after the quote was accepted, query the existing project/job before doing anything else — you may already have a resource in progress.

Retrying a failed Interpretation target

Only a target in FAILED_RELEASED with action = "RETRY" may be retried:
  1. GET /projects/{projectId}/targets/{targetId}/retry-plan — the server decides the amount and the next reservation cycle.
  2. POST /projects/{projectId}/targets/{targetId}/retry with the plan’s nextReservationCycle, characters and rateVersion.
Those amount fields are a confirmation of the server’s plan, not client-side pricing: never compute or adjust the price yourself. 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

Both runnable examples (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.