# Chat: Scarleta API reference

Page: https://www.scarleta.ai/docs/reference/chat
Base URL: https://api.scarleta.ai. Authentication: `Authorization: Bearer $SCARLETA_API_KEY`.
Operations: 2.

## POST /v1/chat

Operation id: `send_chat_message`

**Send a chat message**

Sends one message to the Scarleta assistant and starts a turn in the background. The call returns `202` at once; it does not contain the reply. Follow the turn with `GET /v1/conversations/{conversation_id}/status` until `active_turn_workflow_id` is null, then read the reply with `GET /v1/conversations/{conversation_id}`. `POST /v1/conversations/{conversation_id}/cancel` stops a turn that is running.

Leave out `conversation_id` to start a new conversation, or send the id of one of your conversations to continue it; the assistant remembers the earlier turns. Attach audio with `audio_url` or with `media`; the assistant decides which analyses to run, and those analyses are charged like any other. Only one turn can run in a conversation at a time: a second message sent while a turn is active returns 409 `turn_in_progress`.

Access: API key required; free and paid accounts. Cost: uses tokens. Free for a message that needs no analysis. When the assistant runs an analysis on your audio, that analysis is charged per second of audio like the equivalent single call..

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `prompt` | string | Your message, at most 32768 characters. Allowed: 1 to 32768 characters. |
| `audio_url` | string | A file to discuss. Shorthand for a single `media` item. Allowed: at most 4096 characters. |
| `media` | array<object> | Files to attach, at most 2. Takes precedence over `audio_url`. |
| `media[].url` | string | Where to fetch the file: a public `http` or `https` URL, or a file in your own library. Allowed: at least 1 character. |
| `media[].type` | "audio" \| "video" | Kind of file. Detected from the URL when absent. |
| `media[].name` | string \| null | Display name shown with the file in the conversation. |
| `media[].region` | object \| null | Seconds to analyze, `{ start, end }` with `end` greater than `start`. The whole file when absent. |
| `media[].region.start` | number | Allowed: at least 0. |
| `media[].region.end` | number |   |
| `media[].mediaType` | string \| null | MIME type of the file, for example `audio/wav`. |
| `media[].durationS` | number \| null | Length in seconds, shown in the conversation. Display only: billing always uses the measured length. Allowed: at least 0. |
| `conversation_id` | string | UUID of one of your conversations to continue. A new conversation is created when absent. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `status` | "IN_PROGRESS" |   |
| `workflow` | "turn-workflow" |   |
| `conversation_id` | string | The conversation the message belongs to. Use it for follow-up messages and to read the reply. |
| `turn_workflow_id` | string | Identifier of this turn. |
| `workflow_instance_id` | string | Same value as `turn_workflow_id`. |
| `prompt` | string | Your message, echoed. |
| `audio_url` | string \| null | The file the turn works on: the one you attached, or the file already in the conversation. |
| `tenant_id` | string \| null | Your account identifier. Absent for free accounts. |
| `created_at` | string | ISO 8601 timestamp. |
| `response_time_ms` | integer | How long accepting the message took, in milliseconds. Allowed: -9007199254740991 to 9007199254740991. |
| `message` | string | Where to poll and how to cancel. |

### Responses

202: Accepted. The turn is running. Carries `X-Conversation-ID` and `X-Turn-Workflow-ID` headers with the same values as the body,.

```json
{
  "data": {
    "status": "IN_PROGRESS",
    "workflow": "turn-workflow",
    "conversation_id": "3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01",
    "turn_workflow_id": "turn_6e0ed523-72f5-464e-974c-9ae95fdc2a99",
    "workflow_instance_id": "turn_6e0ed523-72f5-464e-974c-9ae95fdc2a99",
    "prompt": "What key is this track in, and does it drift?",
    "audio_url": "https://example.com/track-1.wav",
    "tenant_id": "8c1d0b52-3f6a-4e0d-9d7b-2a41c5e90f13",
    "created_at": "2026-10-21T03:41:37.272Z",
    "response_time_ms": 2736,
    "message": "Turn dispatched. Poll GET /v1/conversations/3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01/status for active-turn state. POST /v1/conversations/3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01/cancel to abort."
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

400: Invalid JSON, a missing `prompt`, too many `media` items, an unacceptable URL, or a `conversation_id` that is not a UUID.

```json
{
  "error": {
    "code": "missing_prompt",
    "message": "prompt is required in request body",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#missing_prompt"
  }
}
```

401: Missing, malformed, unknown or revoked API key.

```json
{
  "error": {
    "code": "invalid_key",
    "message": "Invalid or revoked API key",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_key"
  }
}
```

403: A library file in `audio_url` or `media` is owned by another account.

```json
{
  "error": {
    "code": "media_not_owned",
    "message": "That file belongs to a different account.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#media_not_owned"
  }
}
```

404: `conversation_id` belongs to another account.

```json
{
  "error": {
    "code": "conversation_not_found",
    "message": "Conversation 3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01 not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#conversation_not_found"
  }
}
```

409: A turn is already running in this conversation. `error.details.existing_workflow_id` names it.

```json
{
  "error": {
    "code": "turn_in_progress",
    "message": "A turn is already active for conversation 3f9c1d2e-7b44-4c1a-8e0f-5a6b7c8d9e01",
    "details": {
      "existing_workflow_id": "turn_6e0ed523-72f5-464e-974c-9ae95fdc2a99"
    },
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#turn_in_progress"
  }
}
```

413: The prompt is over 32768 characters, or the whole request body is over 1048576 bytes (`payload_too_large`: send a shorter message and attach audio by URL).

```json
{
  "error": {
    "code": "prompt_too_long",
    "message": "prompt must not exceed 32768 characters",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#prompt_too_long"
  }
}
```

429: Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#rate_limit_exceeded"
  }
}
```

500: The message could not be processed. Retry.

```json
{
  "error": {
    "code": "chat_failed",
    "message": "Failed to process chat request",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#chat_failed"
  }
}
```

502: The turn could not be started. Retry.

```json
{
  "error": {
    "code": "turn_workflow_create_failed",
    "message": "Failed to start chat turn. Please retry.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#turn_workflow_create_failed"
  }
}
```

503: Temporary condition; retry shortly.

```json
{
  "error": {
    "code": "conversation_ownership_unavailable",
    "message": "Unable to verify conversation ownership, please retry",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#conversation_ownership_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`chat_failed`](https://www.scarleta.ai/docs/errors#chat_failed), [`conversation_do_error`](https://www.scarleta.ai/docs/errors#conversation_do_error), [`conversation_not_found`](https://www.scarleta.ai/docs/errors#conversation_not_found), [`conversation_ownership_unavailable`](https://www.scarleta.ai/docs/errors#conversation_ownership_unavailable), [`invalid_audio_url`](https://www.scarleta.ai/docs/errors#invalid_audio_url), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_conversation_id`](https://www.scarleta.ai/docs/errors#invalid_conversation_id), [`invalid_json`](https://www.scarleta.ai/docs/errors#invalid_json), [`invalid_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`media_not_owned`](https://www.scarleta.ai/docs/errors#media_not_owned), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`missing_prompt`](https://www.scarleta.ai/docs/errors#missing_prompt), [`payload_too_large`](https://www.scarleta.ai/docs/errors#payload_too_large), [`prompt_too_long`](https://www.scarleta.ai/docs/errors#prompt_too_long), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`service_misconfigured`](https://www.scarleta.ai/docs/errors#service_misconfigured), [`too_many_media`](https://www.scarleta.ai/docs/errors#too_many_media), [`turn_in_progress`](https://www.scarleta.ai/docs/errors#turn_in_progress), [`turn_workflow_create_failed`](https://www.scarleta.ai/docs/errors#turn_workflow_create_failed), [`turn_workflow_precondition_missing`](https://www.scarleta.ai/docs/errors#turn_workflow_precondition_missing), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized)

### Example

```bash
curl -X POST "https://api.scarleta.ai/v1/chat" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "What key is this track in, and does it drift?",
    "audio_url": "https://example.com/track-1.wav"
  }'
```

## GET /v1/chat/{job_id}

Operation id: `get_chat_job`

**Get an assistant job**

Reads a background job run by the assistant service by its `job_id`: a recipe run (see `POST /v1/recipe/execute`) or a job started by an older chat. Chat messages sent with `POST /v1/chat` answer with a conversation, not a job id; read their replies through the conversation routes. For a recipe run prefer `GET /v1/recipe/jobs/{job_id}`, which also reports the steps. `status` is `IN_QUEUE`, `IN_PROGRESS`, `COMPLETED`, `FAILED` or `TIMEOUT`; `response` holds the raw outcome once the job has ended. `prompt` and `model` appear only on jobs that stored them. Only your own jobs are returned.

Access: API key required; free and paid accounts. Cost: no token cost.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | yes | The id of the job, for example `recipe_9f3a1c7e52b4`. Allowed: at least 1 character. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | string | Identifier of the job. |
| `status` | string | `IN_QUEUE`, `IN_PROGRESS`, `COMPLETED`, `FAILED` or `TIMEOUT`. |
| `workflow` | "ai-agent" | Always `ai-agent`: only jobs of the assistant service are returned here. |
| `audio_url` | string \| null | The audio the job used. |
| `prompt` | string | The message the job was started with, when it stored one. |
| `model` | string | A model setting stored with the job, when it had one. |
| `response` | any | The raw outcome of the job once it has ended (an object for a recipe run); null before. |
| `interpretation` | string \| null | A written interpretation of the outcome, or null. |
| `conversation_id` | string \| null | The conversation the job is tied to, or null. |
| `error` | any | Why the job failed, or null. |
| `created_at` | string | ISO 8601 timestamp. |
| `completed_at` | string \| null | ISO 8601 timestamp of the end, or null. |

### Responses

200: `meta.request_id` is present.

```json
{
  "data": {
    "job_id": "recipe_9f3a1c7e52b4",
    "status": "COMPLETED",
    "workflow": "ai-agent",
    "audio_url": "https://example.com/track-1.wav",
    "response": {
      "status": "success",
      "step_count": 1
    },
    "interpretation": null,
    "conversation_id": null,
    "error": null,
    "created_at": "2026-10-21T03:41:37.272Z",
    "completed_at": "2026-10-21T03:41:56.630Z"
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

400: `job_id` has characters outside letters, digits and `_ . : -`, or is too long.

```json
{
  "error": {
    "code": "invalid_job_id",
    "message": "job_id must match ^[A-Za-z0-9_.:-]{1,128}$",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_job_id"
  }
}
```

401: Missing, malformed, unknown or revoked API key.

```json
{
  "error": {
    "code": "invalid_key",
    "message": "Invalid or revoked API key",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_key"
  }
}
```

404: No assistant job with this id belongs to your account.

```json
{
  "error": {
    "code": "job_not_found",
    "message": "AI Agent job recipe_9f3a1c7e52b4 not found or access denied",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#job_not_found"
  }
}
```

429: Over the account request budget for the hour. Wait for the `Retry-After` header, then retry.

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#rate_limit_exceeded"
  }
}
```

503: Temporary platform condition; retry shortly.

```json
{
  "error": {
    "code": "auth_unavailable",
    "message": "Authentication service temporarily unavailable",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#auth_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_job_id`](https://www.scarleta.ai/docs/errors#invalid_job_id), [`invalid_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`job_not_found`](https://www.scarleta.ai/docs/errors#job_not_found), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`missing_job_id`](https://www.scarleta.ai/docs/errors#missing_job_id), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`retrieval_failed`](https://www.scarleta.ai/docs/errors#retrieval_failed), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized)

### Example

```bash
JOB_ID="YOUR_JOB_ID"
curl "https://api.scarleta.ai/v1/chat/$JOB_ID" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```
