API reference
Recipes
11 operations, 1 webhook. Base URL https://api.scarleta.ai. Select an operation to see its parameters, samples and errors.
Jump to an operation
DELETE/v1/recipe/{recipe_id}Delete a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Permanently deletes a recipe saved by your account or team, together with its comments. This cannot be undone. A catalog recipe, another account's recipe, or one already deleted answers 404 not_found.
Cost. Free of charge.
RECIPE_ID="YOUR_RECIPE_ID"
curl -X DELETE "https://api.scarleta.ai/v1/recipe/$RECIPE_ID" \
-H "Authorization: Bearer $SCARLETA_API_KEY"Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idpath | string | Required | The recipe id, for example `rec_4821_mix_check`. Ids are case-insensitive and are read in lower case. Allowed: at least 1 character. |
Response fields
| Field | Type | Description |
|---|---|---|
| recipe_id | string | |
| deleted | true |
Errors
| Status | Code | Description |
|---|---|---|
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe of yours has this id (a catalog recipe, another account's recipe or one already deleted is answered the same way). |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"recipe_id": "rec_4821_mix_check",
"deleted": true
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe of yours has this id (a catalog recipe, another account's recipe or one already deleted is answered the same way).
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
GET/v1/recipe/{recipe_id}/compareCompare a fork with its source
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Shows which top-level fields of a fork differ from the recipe it was forked from, and how many steps each has. The recipe in the path must be a fork you can see, and its source must still be visible to you.
Cost. Free of charge.
RECIPE_ID="YOUR_RECIPE_ID"
curl "https://api.scarleta.ai/v1/recipe/$RECIPE_ID/compare" \
-H "Authorization: Bearer $SCARLETA_API_KEY"Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idpath | string | Required | The recipe id, for example `rec_4821_mix_check`. Ids are case-insensitive and are read in lower case. Allowed: at least 1 character. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| fork_id | string | The fork you asked about. |
| source_id | string | The recipe it was forked from. |
| diff | object | |
| diff.changed | array<object> | Fields that differ. Empty when the fork is identical. |
| diff.changed[].field | string | A recipe field: `recipe_name`, `user_intent`, `domain`, `ai_description`, `steps`, `budget` or `execution`. |
| diff.changed[].status | "added" | "removed" | "changed" | Whether the fork added the field, removed it or changed it. |
| diff.changed[].source | any | The value in the source. |
| diff.changed[].fork | any | The value in the fork. |
| diff.step_count | object | Steps in each recipe. |
| diff.step_count.source | integer | Allowed: -9007199254740991 to 9007199254740991. |
| diff.step_count.fork | integer | Allowed: -9007199254740991 to 9007199254740991. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | not_a_fork | The recipe in the path was not made by forking another. |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe with this id is visible to you: it does not exist, or it belongs to another account. Both are answered the same way. When the fork is visible but its source is not, the code is `source_not_found` instead. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"fork_id": "rec_3791_17925845289883",
"source_id": "rec_4821_mix_check",
"diff": {
"changed": [
{
"field": "recipe_name",
"status": "changed",
"source": "Tempo check",
"fork": "Tempo check (fork)"
}
],
"step_count": {
"source": 1,
"fork": 1
}
}
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 400 The recipe in the path was not made by forking another.
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe with this id is visible to you: it does not exist, or it belongs to another account. Both are answered the same way. When the fork is visible but its source is not, the code is `source_not_found` instead.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, source_not_found, unauthorized.
POST/v1/recipe/{recipe_id}/forkFork a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Copies a recipe you can see (a catalog recipe or one of yours) into a new draft that belongs to you, and returns it with 201. The copy keeps a pointer to the source in forked_from; use GET /v1/recipe/{recipe_id}/compare to see how a fork has drifted. Without recipe_name the copy is named after the source with " (fork)" added. The response also carries internal bookkeeping fields; do not rely on them.
Cost. Free of charge.
RECIPE_ID="YOUR_RECIPE_ID"
curl -X POST "https://api.scarleta.ai/v1/recipe/$RECIPE_ID/fork" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe_name": "Tempo check (fork)"
}'Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idpath | string | Required | The recipe id, for example `rec_4821_mix_check`. Ids are case-insensitive and are read in lower case. Allowed: at least 1 character. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_namebody | string | Optional | A name for the copy, at most 200 characters. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| recipe_id | string | The id of the new copy. |
| forked_from | string | The id of the source. |
| forked_recipe | object | |
| forked_recipe.recipe_id | string | Identifier of the recipe. |
| forked_recipe.recipe_name | string | null | The recipe name. |
| forked_recipe.user_intent | string | What the recipe is for. |
| forked_recipe.steps | array<object> | The ordered steps as stored. |
| forked_recipe.budget | any | Budget settings, or null. |
| forked_recipe.execution | any | Execution settings, or null. |
| forked_recipe.tags | array<string> | Tags on the recipe. |
| forked_recipe.is_template | boolean | True for a catalog template recipe. |
| forked_recipe.usage_count | integer | How many times the recipe has been run. Allowed: -9007199254740991 to 9007199254740991. |
| forked_recipe.scope | string | Who can see the recipe: `org-private` (your team) or `user-custom` (you). |
| forked_recipe.catalog_status | string | null | `draft`, `published`, or null for a recipe saved without a status. |
| forked_recipe.forked_from | string | null | The recipe this one was forked from, or null. |
| forked_recipe.ai_description | string | null | A short description, or null. |
| forked_recipe.domain | string | null | The subject area, or null. |
| forked_recipe.author_id | string | null | Author id for catalog recipes; null for yours. |
| forked_recipe.author_kind | string | null | Author kind for catalog recipes; null for yours. |
| forked_recipe.created_at | integer | When the recipe was saved, in milliseconds since the Unix epoch. Allowed: -9007199254740991 to 9007199254740991. |
| forked_recipe.updated_at | number | null | When it was last updated, or null. |
| forked_recipe.last_used_at | number | null | When it was last run, or null. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | name_too_long | `recipe_name` is too long. |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe with this id is visible to you: it does not exist, or it belongs to another account. Both are answered the same way. |
| 413 | recipe_json_too_large | The source recipe is over the size limit. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"recipe_id": "rec_3791_17925845289883",
"forked_from": "rec_4821_mix_check",
"forked_recipe": {
"recipe_id": "rec_3791_17925845289883",
"recipe_name": "Tempo check (fork)",
"user_intent": "Estimate the tempo of a track",
"steps": [
{
"step": 1,
"tool": "tempo_analysis",
"parameters": {}
}
],
"budget": null,
"execution": null,
"tags": [],
"is_template": false,
"usage_count": 0,
"scope": "user-custom",
"catalog_status": "draft",
"forked_from": "rec_4821_mix_check",
"ai_description": null,
"domain": null,
"author_id": null,
"author_kind": null,
"created_at": 1792586336723,
"updated_at": null,
"last_used_at": null
}
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 201 The new draft.
- 400 `recipe_name` is too long.
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe with this id is visible to you: it does not exist, or it belongs to another account. Both are answered the same way.
- 413 The source recipe is over the size limit.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
POST/v1/recipe/{recipe_id}/unpublishUnpublish a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Moves one of your recipes back to a draft. Only the status changes. You can unpublish recipes saved by your account or team; a catalog recipe or another account's recipe answers 404 not_found.
Cost. Free of charge.
RECIPE_ID="YOUR_RECIPE_ID"
curl -X POST "https://api.scarleta.ai/v1/recipe/$RECIPE_ID/unpublish" \
-H "Authorization: Bearer $SCARLETA_API_KEY"Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idpath | string | Required | The recipe id, for example `rec_4821_mix_check`. Ids are case-insensitive and are read in lower case. Allowed: at least 1 character. |
Response fields
| Field | Type | Description |
|---|---|---|
| recipe_id | string | |
| catalog_status | "draft" |
Errors
| Status | Code | Description |
|---|---|---|
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe of yours has this id (a catalog recipe or another account's recipe is answered the same way). |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"recipe_id": "rec_4821_mix_check",
"catalog_status": "draft"
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe of yours has this id (a catalog recipe or another account's recipe is answered the same way).
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
POST/v1/recipe/approveRecord an approval or a rejection of a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Records that you approved or rejected a saved recipe. It changes nothing on the recipe itself and does not run it; it leaves a record that your own tools can use. With approved: true you may list modifications (fields you changed); their names are echoed in modifications_applied. With approved: false you may give a rejection_reason. modifications may be at most 102400 bytes once serialized.
Cost. Free of charge.
curl -X POST "https://api.scarleta.ai/v1/recipe/approve" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe_id": "rec_4821_mix_check",
"approved": true
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idbody | string | Required | The id of a recipe in your account, for example `rec_4821_mix_check`. Allowed: at least 1 character. |
| approvedbody | boolean | Required | True to approve, false to reject. Must be a JSON boolean. |
| modificationsbody | object | Optional | Fields you changed before approving. Only used when `approved` is true. |
| rejection_reasonbody | string | Optional | Why you rejected it. Only kept when `approved` is false. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| recipe_id | string | The recipe the decision is about. |
| request_id | string | Identifier of this decision record. |
| approved | boolean | The decision. |
| modifications_applied | array<string> | Names of the fields in `modifications`. Present when approved. |
| rejection_reason | string | null | The reason given, or null. Present when rejected. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `recipe_id` is missing, or `approved` is not a JSON boolean, or the body is not valid JSON (`invalid_json`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe with this id is visible to you. |
| 413 | recipe_json_too_large | `modifications` is over the size limit. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"recipe_id": "rec_4821_mix_check",
"request_id": "0cf07e8d-6f86-47b3-8a35-35499a651571",
"approved": true,
"modifications_applied": []
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 400 `recipe_id` is missing, or `approved` is not a JSON boolean, or the body is not valid JSON (`invalid_json`).
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe with this id is visible to you.
- 413 `modifications` is over the size limit.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_json, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
POST/v1/recipe/cancelSet a proposed recipe aside in a conversation
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Declines a recipe that the assistant proposed in one of your conversations; a recipe that was never proposed in the conversation answers 404 recipe_not_found. The decline is saved durably in the conversation, and a run of that recipe that is still queued or in progress is stopped. Cancel is silent: it sends no reply and costs nothing. Calling it again for the same conversation and recipe answers 200 too and records nothing new. reason is optional. Leading and trailing spaces are removed, a reason that is only spaces counts as none, and only the first 500 characters of the rest are kept.
Cost. Free of charge. Cancel sends no reply and analyzes no audio.
curl -X POST "https://api.scarleta.ai/v1/recipe/cancel" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe_id": "rec_4821_mix_check",
"conversation_id": "3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01",
"reason": "Too slow."
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| recipe_idbody | string | Required | The id of the proposed recipe to set aside, at most 200 characters. Only letters, digits, `_`, `.`, `:`, `@` and `-` are allowed; other characters are rejected with 400 `invalid_request`. Allowed: 1 to 200 characters. |
| conversation_idbody | string | Required | The conversation the recipe was proposed in. Must be a UUID of a conversation of yours. |
| reasonbody | string | Optional | Why you set it aside, in your own words. Only the first 500 characters are kept. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| recipe_id | string | The recipe set aside. |
| conversation_id | string | The conversation. |
| declined | true | Always true. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `recipe_id` is missing, empty, longer than 200 characters or contains characters outside letters, digits, `_`, `.`, `:`, `@` and `-`, `conversation_id` is missing or not a UUID, or the body is not valid JSON (`invalid_json`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | The conversation is not yours or does not exist (`not_found`), or the recipe was never proposed in this conversation (`recipe_not_found`). |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 502 | decline_record_failed | The decline could not be saved. Nothing was cancelled; retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"recipe_id": "rec_4821_mix_check",
"conversation_id": "3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01",
"declined": true
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 400 `recipe_id` is missing, empty, longer than 200 characters or contains characters outside letters, digits, `_`, `.`, `:`, `@` and `-`, `conversation_id` is missing or not a UUID, or the body is not valid JSON (`invalid_json`).
- 401 Missing, malformed, unknown or revoked API key.
- 404 The conversation is not yours or does not exist (`not_found`), or the recipe was never proposed in this conversation (`recipe_not_found`).
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 502 The decline could not be saved. Nothing was cancelled; retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_json, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, recipe_not_found, unauthorized.
POST/v1/recipe/executeRun a recipe on an audio file
Free and paid accountsUses tokens
- API key required
- Free and paid accounts
- Uses tokens
Starts a recipe on one audio file and answers 200 at once with a job_id; read progress with GET /v1/recipe/jobs/{job_id} or receive a recipe_completed webhook. Name the recipe with recipe_id (a saved recipe of yours or a catalog recipe) or send the whole recipe in recipe, never both. The cost is estimated before anything starts (a rough run time is shown in estimated_seconds, summed from typical timings of each step's tool; a tool with no measured timing counts as 45 seconds); an account whose balance cannot cover it gets 402 insufficient_balance with the estimate, the balance and the shortfall. audio_url must be a public http or https URL or a file in your own library; audio longer than 60 minutes is refused with 400 audio_too_long. Send conversation_id of one of your conversations to tie the run to it; repeating the same run for that conversation returns the existing job with idempotent: true, and a recipe you set aside with POST /v1/recipe/cancel answers status: declined with no job. Refusals use the standard error body; insufficient_balance (402) carries estimated_cost, balance, shortfall and audio_duration in error.details, and audio_too_long (400) carries audio_duration and max_duration. The top-level field budget is refused; set budgets in the recipe.
Cost. Each step is billed like the matching analysis of the audio: 1 token is 1 second of audio, at the rate of its capability. The estimate is checked against your balance before the run starts.
curl -X POST "https://api.scarleta.ai/v1/recipe/execute" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe_id": "rec_4821_mix_check",
"audio_url": "https://example.com/track-1.wav"
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| audio_urlbody | string | Required | The audio file to run the recipe on. Allowed: at least 1 character. |
| recipe_idbody | string | Optional | The id of a saved recipe, for example `rec_4821_mix_check`. Send this or `recipe`. |
| recipebody | object | Optional | The whole recipe, as accepted by `POST /v1/recipe/validate`, at most 102400 bytes once serialized. Send this or `recipe_id`. |
| conversation_idbody | string | Optional | A conversation of yours to tie the run to. Must be a UUID; a value that is not one is ignored. |
| webhook_urlbody | string | Optional | Where to POST the `recipe_completed` event. An https URL that resolves to a public address, at most 4096 bytes. |
Response fields
| Field | Type | Description |
|---|---|---|
| job_id | string | Identifier of the run. Absent only when `status` is `declined`. |
| status | string | `IN_QUEUE` for a new run; the current status for an existing one; `declined` for a recipe you set aside. |
| mode | "recipe" | Always `recipe`. |
| recipe_id | string | The recipe that runs. |
| audio_url | string | The audio the run uses. |
| num_steps | integer | Steps in the recipe. Allowed: -9007199254740991 to 9007199254740991. |
| estimated_seconds | integer | A rough run time in seconds. Allowed: -9007199254740991 to 9007199254740991. |
| idempotent | boolean | True when this answer repeats an earlier identical run for the same conversation. |
| conversation_id | string | Present when `status` is `declined`. |
| declined | boolean | True when the recipe was set aside in the conversation; nothing ran. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | The body is malformed or incomplete (`invalid_request`, `invalid_json`), the recipe fails validation (`invalid_recipe`, `invalid_recipe_parameters`), a URL is not acceptable (`invalid_audio_url`, `invalid_webhook_url`), or the audio cannot be used (`audio_too_long`, `unsupported_format`, `audio_processing_failed`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 402 | insufficient_balance | The balance cannot cover the estimate. `error.details` carries `estimated_cost`, `balance`, `shortfall` and `audio_duration`. Nothing was charged. |
| 403 | media_not_owned | The audio file belongs to another account. |
| 404 | not_found | No recipe with this id is visible to you, or `conversation_id` names a conversation that is not yours. A file at `audio_url` that cannot be downloaded is 404 `audio_not_found`. |
| 413 | recipe_json_too_large | `recipe` (`recipe_json_too_large`) or `webhook_url` (`webhook_url_too_large`) is over its size limit. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 500 | workflow_binding_missing | The recipe runner is not configured in this environment. This is a deployment fault, not something to retry. |
| 502 | workflow_start_failed | The run could not be started. Nothing was charged; retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"job_id": "recipe_9f3a1c7e52b4",
"status": "IN_QUEUE",
"mode": "recipe",
"recipe_id": "rec_4821_mix_check",
"audio_url": "https://example.com/track-1.wav",
"num_steps": 1,
"estimated_seconds": 45
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 The run was accepted (or an identical earlier run was found).
- 400 The body is malformed or incomplete (`invalid_request`, `invalid_json`), the recipe fails validation (`invalid_recipe`, `invalid_recipe_parameters`), a URL is not acceptable (`invalid_audio_url`, `invalid_webhook_url`), or the audio cannot be used (`audio_too_long`, `unsupported_format`, `audio_processing_failed`).
- 401 Missing, malformed, unknown or revoked API key.
- 402 The balance cannot cover the estimate. `error.details` carries `estimated_cost`, `balance`, `shortfall` and `audio_duration`. Nothing was charged.
- 403 The audio file belongs to another account.
- 404 No recipe with this id is visible to you, or `conversation_id` names a conversation that is not yours. A file at `audio_url` that cannot be downloaded is 404 `audio_not_found`.
- 413 `recipe` (`recipe_json_too_large`) or `webhook_url` (`webhook_url_too_large`) is over its size limit.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 500 The recipe runner is not configured in this environment. This is a deployment fault, not something to retry.
- 502 The run could not be started. Nothing was charged; retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: audio_not_found, audio_processing_failed, audio_too_long, invalid_audio_url, invalid_auth_format, invalid_json, invalid_key_format, invalid_recipe, invalid_recipe_parameters, invalid_webhook_url, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized, unsupported_format, webhook_url_too_large, webhook_validation_unavailable.
POST/v1/recipe/generateGenerate a recipe from a request
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Describes in words what you want to find out and returns a recipe the planner built for it. Nothing is saved or run: review the recipe, change it if you like, then save it with POST /v1/recipe/save and run it. The recipe_id in the answer is only a suggestion and is not unique, so save the recipe under your own rec_ id. The call needs a token balance of at least 1 and does not debit tokens. It calls the planner, so it takes several seconds and can answer 503 planner_unavailable when the planner times out (the same status as auth_unavailable; read the code); retry later.
Cost. No tokens are debited, but the account needs a balance of at least 1 token.
curl -X POST "https://api.scarleta.ai/v1/recipe/generate" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_intent": "Estimate the tempo of my track",
"budget_mode": "fast"
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| user_intentbody | string | Required | What you want to find out, in words, at most 2000 characters. Allowed: 1 to 2000 characters. |
| budget_modebody | "fast" | "balanced" | "quality" | Optional | How much work the plan may use: `fast` for the fewest, cheapest steps, `quality` for the most thorough. |
| audio_typebody | "music" | "speech" | "mixed" | "unknown" | Optional | What kind of audio the recipe is for. |
| max_costbody | number | Optional | A cost ceiling in tokens that the planner aims to stay under. |
| conversation_idbody | string | Optional | A conversation of yours to attribute the planning to. Must be a UUID. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| recipe | object | The recipe. The planner may add fields beyond the ones listed. |
| recipe.recipe_id | string | A suggested id. Not unique. |
| recipe.user_intent | string | Your request. |
| recipe.steps | array<object> | The planned steps. Each has a `step` number, a `tool` name and its `parameters`, and may carry `features`, `label` and `reason`. |
| recipe.estimated_cost | number | The planner's estimate of the cost in tokens. |
| recipe.budget | object | The budget the plan was made under. |
| recipe.rationale | string | A short plain explanation of the plan. |
| steps_count | integer | Steps in the recipe. Allowed: -9007199254740991 to 9007199254740991. |
| estimated_cost | number | The estimated cost in tokens; 0 when the planner gave none. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `user_intent` is missing, empty or too long, `conversation_id` is not a UUID, the planner could not produce a valid recipe (the message says why), or the body is not valid JSON (`invalid_json`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 402 | insufficient_balance | The account has no token balance, or its tenant is frozen. Nothing was charged. |
| 404 | not_found | `conversation_id` names a conversation that is not yours or does not exist. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"recipe": {
"recipe_id": "auto_generated_1712700000",
"user_intent": "Estimate the tempo of my track",
"steps": [
{
"step": 1,
"tool": "tempo_analysis",
"parameters": {
"include_tempo": true
},
"features": [
"tempo.bpm",
"tempo.beat_count"
],
"label": "Find the tempo"
}
],
"estimated_cost": 1,
"budget": {
"mode": "fast",
"audio_type": "music",
"max_cost": 5
},
"rationale": "One quick pass to hear the beat and give you the tempo."
},
"steps_count": 1,
"estimated_cost": 1
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 400 `user_intent` is missing, empty or too long, `conversation_id` is not a UUID, the planner could not produce a valid recipe (the message says why), or the body is not valid JSON (`invalid_json`).
- 401 Missing, malformed, unknown or revoked API key.
- 402 The account has no token balance, or its tenant is frozen. Nothing was charged.
- 404 `conversation_id` names a conversation that is not yours or does not exist.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_json, invalid_key_format, missing_api_key, missing_auth, planner_unavailable, rate_limit_unavailable, unauthorized.
GET/v1/recipe/jobs/{job_id}Get a recipe run
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Returns the state of a run started with POST /v1/recipe/execute. Poll it until status is COMPLETED, FAILED, CANCELLED or TIMEOUT; results is filled in only once the run has ended. Only recipe runs (ids that start with recipe_) are read here; use GET /v1/analyze/{job_id} for analyses. completed_steps and num_steps count the steps that finished and the steps in the recipe (num_steps is 0 until the run has ended). When you gave a webhook_url, webhook_delivery_status shows how the recipe_completed delivery went: in_progress, delivered, failed, or unconfirmed; it is null when there is no webhook or the run has not ended. It describes only that per-request webhook_url: addresses registered with POST /v1/webhooks are retried the same way, but their outcome is not shown in this field.
Cost. Free of charge.
JOB_ID="YOUR_JOB_ID"
curl "https://api.scarleta.ai/v1/recipe/jobs/$JOB_ID" \
-H "Authorization: Bearer $SCARLETA_API_KEY"Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| job_idpath | string | Required | The `job_id` from the execute answer, for example `recipe_9f3a1c7e52b4`. Allowed: at least 1 character. |
Response fields
| Field | Type | Description |
|---|---|---|
| job_id | string | Identifier of the run. |
| status | string | null | `IN_QUEUE`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, `CANCELLED` or `TIMEOUT`. |
| mode | "recipe" | Always `recipe`. |
| recipe_id | string | null | The recipe that ran; null until the run has ended. |
| audio_url | string | null | The audio the run used. |
| num_steps | integer | Steps in the recipe; 0 until the run has ended. Allowed: -9007199254740991 to 9007199254740991. |
| completed_steps | integer | Steps that finished. Allowed: -9007199254740991 to 9007199254740991. |
| results | object | null | The outcome of the run, once it has ended: the recipe, its steps and their results. Null while the run is queued or in progress, and for `TIMEOUT`. |
| error | string | null | Why the run failed, or null. |
| error_code | string | null | A short code for the failure, or null. |
| started_at | number | null | When the run started, in milliseconds since the Unix epoch, or null. |
| completed_at | number | null | When the run ended, in milliseconds since the Unix epoch, or null. |
| webhook_delivery_status | string | null | State of the `recipe_completed` delivery to the per-request `webhook_url` only (registered addresses are not reflected here), or null. |
| webhook_last_attempt_at | number | null | When the last delivery attempt started, in milliseconds since the Unix epoch, or null. |
Errors
| Status | Code | Description |
|---|---|---|
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 404 | not_found | No recipe run with this id belongs to your account, or the id does not start with `recipe_`. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"job_id": "recipe_9f3a1c7e52b4",
"status": "COMPLETED",
"mode": "recipe",
"recipe_id": "rec_4821_mix_check",
"audio_url": "https://example.com/track-1.wav",
"num_steps": 1,
"completed_steps": 1,
"results": {
"status": "success",
"recipe_id": "rec_4821_mix_check",
"step_count": 1,
"response": null,
"conversation_id": null
},
"error": null,
"error_code": null,
"started_at": 1792586336723,
"completed_at": 1792586348723,
"webhook_delivery_status": null,
"webhook_last_attempt_at": null
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 401 Missing, malformed, unknown or revoked API key.
- 404 No recipe run with this id belongs to your account, or the id does not start with `recipe_`.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
POST/v1/recipe/saveSave a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Saves a recipe to your account and returns it. A recipe saved with recipe_id of an existing recipe of yours replaces it; one saved under another account's id answers 404 not_found. Without catalog_status or with published, the recipe must pass validation and recipe.recipe_id is required. With catalog_status: draft validation is skipped so you can save work in progress, and a recipe with no recipe_id gets one made for you. catalog_status can only be draft or published, and cannot be set on a template recipe (403 forbidden). The response also carries internal bookkeeping fields; do not rely on them.
Cost. Free of charge.
curl -X POST "https://api.scarleta.ai/v1/recipe/save" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe": {
"recipe_id": "rec_4821_mix_check",
"user_intent": "Estimate the tempo of a track",
"steps": [
{
"step": 1,
"tool": "tempo_analysis",
"parameters": {}
}
]
},
"recipe_name": "Tempo check",
"catalog_status": "published"
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| recipebody | object | Required | The recipe. At most 102400 bytes once serialized. |
| recipe.recipe_idbody | string | Optional | The recipe id: letters, digits, underscore and hyphen, at most 128 characters. Required to validate. To save it for later use it must look like `rec_` + four digits + `_` + a name (for example `rec_0001_speech_quality`); a draft save may leave it out. |
| recipe.user_intentbody | string | Optional | What the recipe is for, in words. |
| recipe.stepsbody | array<object> | Optional | The ordered steps, from 1 to 10. |
| recipe.steps[].stepbody | integer | Required | The step number, starting at 1. Numbers must be unique. Allowed: -9007199254740991 to 9007199254740991. |
| recipe.steps[].toolbody | string | Required | The capability to run, for example `tempo_analysis`. An unknown name is refused and the error lists the valid ones. |
| recipe.steps[].parametersbody | object | Optional | Settings for the step. A plain object; leave it out for none. |
| recipe.steps[].featuresbody | array<string> | Optional | Feature names to compute, where the capability takes them. |
| recipe.steps[].depends_onbody | array<integer> | Optional | Earlier step numbers this step needs the output of. |
| recipe.steps[].timeout_msbody | integer | Optional | How long the step may run, in milliseconds, from 1000 to 300000. |
| recipe.budgetbody | object | Optional | Optional budget settings: `mode` (`fast`, `balanced` or `quality`) and `compute` (`cpu_only` or `gpu_allowed`). |
| recipe.executionbody | object | Optional | Optional execution settings. |
| recipe_namebody | string | Optional | A name, at most 200 characters. |
| tagsbody | array<string> | Optional | Tags to keep with the recipe. |
| is_templatebody | boolean | Optional | Save as a template recipe. Defaults to false. |
| catalog_statusbody | "draft" | "published" | Optional | Whether this is a work-in-progress draft or a published recipe. Leave it out for neither. |
Response fields
| Field | Type | Description |
|---|---|---|
| success | true | Always true. |
| recipe_id | string | The id the recipe was saved under. |
| saved_recipe | object | |
| saved_recipe.recipe_id | string | Identifier of the recipe. |
| saved_recipe.recipe_name | string | null | The recipe name. |
| saved_recipe.user_intent | string | What the recipe is for. |
| saved_recipe.steps | array<object> | The ordered steps as stored. |
| saved_recipe.budget | any | Budget settings, or null. |
| saved_recipe.execution | any | Execution settings, or null. |
| saved_recipe.tags | array<string> | Tags on the recipe. |
| saved_recipe.is_template | boolean | True for a catalog template recipe. |
| saved_recipe.usage_count | integer | How many times the recipe has been run. Allowed: -9007199254740991 to 9007199254740991. |
| saved_recipe.scope | string | Who can see the recipe: `org-private` (your team) or `user-custom` (you). |
| saved_recipe.catalog_status | string | null | `draft`, `published`, or null for a recipe saved without a status. |
| saved_recipe.forked_from | string | null | The recipe this one was forked from, or null. |
| saved_recipe.ai_description | string | null | A short description, or null. |
| saved_recipe.domain | string | null | The subject area, or null. |
| saved_recipe.author_id | string | null | Author id for catalog recipes; null for yours. |
| saved_recipe.author_kind | string | null | Author kind for catalog recipes; null for yours. |
| saved_recipe.created_at | integer | When the recipe was saved, in milliseconds since the Unix epoch. Allowed: -9007199254740991 to 9007199254740991. |
| saved_recipe.updated_at | number | null | When it was last updated, or null. |
| saved_recipe.last_used_at | number | null | When it was last run, or null. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | The recipe is missing or empty, failed validation, or `catalog_status` is not allowed (`invalid_request`); `recipe_id` has the wrong format (`invalid_recipe_id`); or `recipe_name` is too long (`name_too_long`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 403 | forbidden | `catalog_status` was set on a template recipe. |
| 404 | not_found | `recipe.recipe_id` names a recipe that belongs to another account. |
| 413 | recipe_json_too_large | The recipe is over the size limit. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"success": true,
"recipe_id": "rec_4821_mix_check",
"saved_recipe": {
"recipe_id": "rec_4821_mix_check",
"recipe_name": "Tempo check",
"user_intent": "Estimate the tempo of a track",
"steps": [
{
"step": 1,
"tool": "tempo_analysis",
"parameters": {}
}
],
"budget": null,
"execution": null,
"tags": [],
"is_template": false,
"usage_count": 0,
"scope": "user-custom",
"catalog_status": "published",
"forked_from": null,
"ai_description": null,
"domain": null,
"author_id": null,
"author_kind": null,
"created_at": 1792586336723,
"updated_at": null,
"last_used_at": null
}
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Saved.
- 400 The recipe is missing or empty, failed validation, or `catalog_status` is not allowed (`invalid_request`); `recipe_id` has the wrong format (`invalid_recipe_id`); or `recipe_name` is too long (`name_too_long`).
- 401 Missing, malformed, unknown or revoked API key.
- 403 `catalog_status` was set on a template recipe.
- 404 `recipe.recipe_id` names a recipe that belongs to another account.
- 413 The recipe is over the size limit.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_json, invalid_key_format, invalid_recipe_id, missing_api_key, missing_auth, name_too_long, rate_limit_unavailable, save_failed, unauthorized.
POST/v1/recipe/validateValidate a recipe
Free and paid accounts
- API key required
- Free and paid accounts
- No token cost
Checks a recipe without saving or running it. A valid recipe answers { valid: true, steps_count }. An invalid one answers 400 invalid_request and the message lists every problem, one per line. Run it before saving or running a recipe you built by hand. The recipe may be at most 102400 bytes once serialized.
Cost. Free of charge.
curl -X POST "https://api.scarleta.ai/v1/recipe/validate" \
-H "Authorization: Bearer $SCARLETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipe": {
"recipe_id": "rec_4821_mix_check",
"user_intent": "Estimate the tempo of a track",
"steps": [
{
"step": 1,
"tool": "tempo_analysis",
"parameters": {}
}
]
}
}'Request body
| Name | Type | Required | Description |
|---|---|---|---|
| recipebody | object | Required | |
| recipe.recipe_idbody | string | Required | The recipe id: letters, digits, underscore and hyphen, at most 128 characters. Required to validate. To save it for later use it must look like `rec_` + four digits + `_` + a name (for example `rec_0001_speech_quality`); a draft save may leave it out. |
| recipe.user_intentbody | string | Optional | What the recipe is for, in words. |
| recipe.stepsbody | array<object> | Required | The ordered steps, from 1 to 10. |
| recipe.steps[].stepbody | integer | Required | The step number, starting at 1. Numbers must be unique. Allowed: -9007199254740991 to 9007199254740991. |
| recipe.steps[].toolbody | string | Required | The capability to run, for example `tempo_analysis`. An unknown name is refused and the error lists the valid ones. |
| recipe.steps[].parametersbody | object | Optional | Settings for the step. A plain object; leave it out for none. |
| recipe.steps[].featuresbody | array<string> | Optional | Feature names to compute, where the capability takes them. |
| recipe.steps[].depends_onbody | array<integer> | Optional | Earlier step numbers this step needs the output of. |
| recipe.steps[].timeout_msbody | integer | Optional | How long the step may run, in milliseconds, from 1000 to 300000. |
| recipe.budgetbody | object | Optional | Optional budget settings: `mode` (`fast`, `balanced` or `quality`) and `compute` (`cpu_only` or `gpu_allowed`). |
| recipe.executionbody | object | Optional | Optional execution settings. |
Response fields
| Field | Type | Description |
|---|---|---|
| valid | true | |
| steps_count | integer | Steps in the recipe. Allowed: -9007199254740991 to 9007199254740991. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | The recipe is missing or not an object (`Error: recipe is required`), or it failed validation (the message lists the problems), or the body is not valid JSON (`invalid_json`). |
| 401 | invalid_key | Missing, malformed, unknown or revoked API key. |
| 413 | recipe_json_too_large | The recipe is over the size limit. |
| 429 | rate_limit_exceeded | Over the account request budget for the hour. Wait for the `Retry-After` header, then retry. |
| 503 | auth_unavailable | Temporary platform condition; retry shortly. |
Example responses
{
"data": {
"valid": true,
"steps_count": 1
},
"meta": {
"request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
}
}- 200 Success
- 400 The recipe is missing or not an object (`Error: recipe is required`), or it failed validation (the message lists the problems), or the body is not valid JSON (`invalid_json`).
- 401 Missing, malformed, unknown or revoked API key.
- 413 The recipe is over the size limit.
- 429 Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.
- 503 Temporary platform condition; retry shortly.
Try it
Sends this request from your browser to https://api.scarleta.ai with your own key. The key stays in this page only (never saved or put in the address) and is cleared when you leave. Revoke any key you have shared.
Other error codes this operation can return: invalid_auth_format, invalid_json, invalid_key_format, missing_api_key, missing_auth, rate_limit_unavailable, unauthorized.
EVENTrecipe_completedWebhook: Recipe run completed
Sent by Scarleta
Sent when a run finishes, whether it completed or failed: to the webhook_url of the run, if it has one, and to every address registered with POST /v1/webhooks. A run that is cancelled, or that stalls and is stopped by Scarleta's automatic recovery, sends no recipe_completed; read GET /v1/recipe/jobs/{job_id} for its final status. The body is a JSON object with exactly the fields below. Each delivery carries X-Scarleta-Timestamp (Unix seconds) and X-Scarleta-Signature (v1= followed by the lowercase hex HMAC-SHA256 of <timestamp>.<raw body>, keyed with your signing secret from GET /v1/webhooks/signing-secret). Reject deliveries whose timestamp is more than 300 seconds from your clock. For one day after a rotation a delivery also carries X-Scarleta-Signature-Previous, signed with the previous secret; a receiver that has not switched to the new secret yet can verify that header with its old secret. Addresses registered with POST /v1/webhooks receive this event too, signed the same way; an address identical to the webhook_url is delivered once.
Your endpoint must answer with a 2xx status within 10 seconds. Scarleta makes up to 3 attempts, waiting 2 s and 4 s before the second and third; redirects are not followed. Targets that are not public https endpoints are never called. A run starts one delivery sequence to its webhook_url and never a second one, but inside that sequence an attempt that timed out or got a non-2xx answer is sent again with the same body, and the earlier attempt may already have reached you: de-duplicate by job_id. Registered addresses are delivered at least once, on a best-effort basis: a failed delivery to a registered address can also be sent again when the same completion is replayed, so de-duplicate by job_id there too. webhook_delivery_status on the job shows the outcome of the delivery to its webhook_url only. Registered addresses are retried the same way, but their outcome is not shown in that field.
Set by the webhook_url field of Run a recipe on an audio file.
Payload
| Field | Type | Description |
|---|---|---|
| event | "recipe_completed" | |
| job_id | string | The run that ended. |
| recipe_id | string | null | The recipe that ran; null if the run ended before the recipe was read. |
| status | "completed" | "failed" | How the run ended. |
| audio_url | string | The audio the run used. |
| num_steps | integer | Steps in the recipe. Allowed: -9007199254740991 to 9007199254740991. |
| completed_steps | integer | Steps that finished. Allowed: -9007199254740991 to 9007199254740991. |
| error | string | null | Why the run failed, or null. |
| completed_at | number | When the run ended, in milliseconds since the Unix epoch. |
Example payload
{
"event": "recipe_completed",
"job_id": "recipe_9f3a1c7e52b4",
"recipe_id": "rec_4821_mix_check",
"status": "completed",
"audio_url": "https://example.com/track-1.wav",
"num_steps": 1,
"completed_steps": 1,
"error": null,
"completed_at": 1792586348723
}