# Upload sessions: Scarleta API reference

Page: https://www.scarleta.ai/docs/reference/upload-sessions
Base URL: https://api.scarleta.ai. Authentication: `Authorization: Bearer $SCARLETA_API_KEY`.
Operations: 7.

## GET /v1/audio/uploads

Operation id: `list_upload_sessions`

**List unfinished upload sessions**

Returns your upload sessions that are still open, newest first (up to 200), so an interrupted upload can be found and resumed. Finished and cancelled sessions are not listed. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `status` | query | "uploading" | no | Only `uploading` is supported, and it is the default. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `sessions` | array<object> |   |
| `sessions[].session_id` | string | Identifier of the session. |
| `sessions[].file_id` | string | Identifier the file will have once completed. |
| `sessions[].audio_id` | string |   |
| `sessions[].filename` | string \| null |   |
| `sessions[].content_type` | string |   |
| `sessions[].declared_bytes` | integer | Size declared when the session was created. Allowed: -9007199254740991 to 9007199254740991. |
| `sessions[].part_size_bytes` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `sessions[].part_count` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `sessions[].tags` | array<string> |   |
| `sessions[].version_of` | string \| null | `audio_id` this upload will be attached to, if any. |
| `sessions[].created_at` | string | ISO 8601 timestamp. |
| `sessions[].expires_at` | string | ISO 8601 timestamp. |

### Responses

200: Open sessions. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "sessions": [
      {
        "session_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
        "file_id": "9e8d7c6b-5a49-4382-b170-6f5e4d3c2b1a",
        "audio_id": "audio_bbb14949dc19",
        "filename": "morning-session.wav",
        "content_type": "audio/wav",
        "declared_bytes": 1920044,
        "part_size_bytes": 16777216,
        "part_count": 1,
        "tags": [
          "morning"
        ],
        "version_of": null,
        "created_at": "2026-10-20T11:53:45.938Z",
        "expires_at": "2026-10-27T11:53:45.938Z"
      }
    ]
  }
}
```

400: A `status` other than `uploading`.

```json
{
  "error": {
    "code": "invalid_status",
    "message": "Only status=uploading is supported",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_status"
  }
}
```

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"
  }
}
```

429: Over the account request budget. 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: The database is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_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_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`invalid_status`](https://www.scarleta.ai/docs/errors#invalid_status), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
curl "https://api.scarleta.ai/v1/audio/uploads" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## POST /v1/audio/uploads

Operation id: `create_upload_session`

**Start an upload session**

Starts a resumable upload for a file of any size up to 10 GiB (1 GiB for MP4 or MOV video). The file is sent in parts of `part_size_bytes` (16 MiB; the last part is smaller), at most 10000 parts. The flow is: create the session, ask for part URLs, `PUT` each part to its URL (parts can go in parallel and be retried), then complete the session. A session you do not finish can be resumed by reading its status, and is cleaned up 7 days after it started.

Creating a session reserves the declared size in your storage at once, so a file that would not fit is refused here with 413 `storage_quota_exceeded`. At most 50 sessions started in the last 60 minutes can be unfinished at the same time. If you send `sha256`, repeating the same request (same hash, filename, size and `version_of`) within 5 minutes returns the open session instead of starting a second one, which makes a lost response safe to retry. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `filename` | string | Name to show for the file, at most 255 characters. Required. Allowed: 1 to 255 characters. |
| `content_type` | string | Content type of the file. Required. One of: `audio/wav`, `audio/wave`, `audio/x-wav`, `audio/mpeg`, `audio/mp3`, `audio/ogg`, `audio/flac`, `audio/aac`, `audio/m4a`, `audio/mp4`, `audio/x-m4a`, `audio/aiff`, `audio/x-aiff`, `audio/webm`, `audio/opus`, `audio/x-opus+ogg`, `video/mp4`, `video/quicktime`. |
| `size_bytes` | integer | Size of the whole file in bytes. Required. At most 10 GiB for audio and 1 GiB for video. Allowed: at most 10737418240. |
| `sha256` | string | SHA-256 of the file bytes as 64 hexadecimal characters. Optional; enables safe retry of this call. |
| `tags` | array<string> | Tags to put on the file, at most 50 of at most 64 characters each. Blank tags are dropped and duplicates merged. |
| `version_of` | string | `audio_id` of one of your ready files to attach this upload to as a new version, once it completes. Allowed: at most 200 characters. |
| `client_duration_seconds` | number | Length of the audio in seconds, if you know it. Display only; never used for billing. Allowed: at least 0. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `session_id` | string | Identifier of the session. |
| `file_id` | string | Identifier the file will have once completed. |
| `audio_id` | string | Library identifier of the file. |
| `part_size_bytes` | integer | Size of every part except the last. Allowed: -9007199254740991 to 9007199254740991. |
| `part_count` | integer | Number of parts to send. Allowed: -9007199254740991 to 9007199254740991. |
| `parts_done` | array<integer> | Part numbers already uploaded (empty for a new session). |
| `expires_at` | string | When an unfinished session is cleaned up, as an ISO 8601 timestamp. |

### Responses

201: Session created. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "session_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "file_id": "9e8d7c6b-5a49-4382-b170-6f5e4d3c2b1a",
    "audio_id": "audio_bbb14949dc19",
    "part_size_bytes": 16777216,
    "part_count": 1,
    "parts_done": [],
    "expires_at": "2026-10-27T11:53:45.938Z"
  }
}
```

400: A required field is missing or malformed, `tags`, `sha256` or the duration is invalid, or `version_of` is not one of your ready files.

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

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"
  }
}
```

413: The declared size is over the per-file limit, or the file does not fit in your storage (`storage_quota_exceeded`).

```json
{
  "error": {
    "code": "file_too_large",
    "message": "This file is larger than 10 GB, the most a single file can be.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#file_too_large"
  }
}
```

429: Too many unfinished sessions, or over the per-minute cap of these routes.

```json
{
  "error": {
    "code": "too_many_active_uploads",
    "message": "Too many uploads in progress. Finish or cancel some before starting more.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#too_many_active_uploads"
  }
}
```

503: The session could not be registered; nothing was reserved. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`file_too_large`](https://www.scarleta.ai/docs/errors#file_too_large), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_content_type`](https://www.scarleta.ai/docs/errors#invalid_content_type), [`invalid_duration`](https://www.scarleta.ai/docs/errors#invalid_duration), [`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), [`invalid_sha256`](https://www.scarleta.ai/docs/errors#invalid_sha256), [`invalid_tags`](https://www.scarleta.ai/docs/errors#invalid_tags), [`invalid_version_of`](https://www.scarleta.ai/docs/errors#invalid_version_of), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`missing_filename`](https://www.scarleta.ai/docs/errors#missing_filename), [`missing_size`](https://www.scarleta.ai/docs/errors#missing_size), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`storage_quota_exceeded`](https://www.scarleta.ai/docs/errors#storage_quota_exceeded), [`tag_too_long`](https://www.scarleta.ai/docs/errors#tag_too_long), [`too_many_active_uploads`](https://www.scarleta.ai/docs/errors#too_many_active_uploads), [`too_many_tags`](https://www.scarleta.ai/docs/errors#too_many_tags), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
curl -X POST "https://api.scarleta.ai/v1/audio/uploads" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "morning-session.wav",
    "content_type": "audio/wav",
    "size_bytes": 1920044,
    "tags": [
      "morning"
    ],
    "client_duration_seconds": 60
  }'
```

## GET /v1/audio/uploads/{sessionId}

Operation id: `get_upload_session`

**Get an upload session**

Returns the state of one session, with the parts storage has actually received, so an interrupted upload resumes by sending only the missing parts. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sessionId` | path | string | yes | The `session_id` returned when the session was created. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `session_id` | string | Identifier of the session. |
| `file_id` | string | Identifier the file has once completed. |
| `audio_id` | string \| null |   |
| `status` | "uploading" \| "completed" \| "aborted" \| "expired" | `uploading` until completed or cancelled. |
| `parts_done` | array<integer> | Part numbers storage has received. |
| `bytes_done` | integer | Total size of those parts in bytes. Allowed: -9007199254740991 to 9007199254740991. |
| `declared_bytes` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `part_size_bytes` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `part_count` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `filename` | string \| null |   |
| `expires_at` | string | ISO 8601 timestamp. |

### Responses

200: Session state. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "session_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "file_id": "9e8d7c6b-5a49-4382-b170-6f5e4d3c2b1a",
    "audio_id": "audio_bbb14949dc19",
    "status": "uploading",
    "parts_done": [
      1
    ],
    "bytes_done": 1920044,
    "declared_bytes": 1920044,
    "part_size_bytes": 16777216,
    "part_count": 1,
    "filename": "morning-session.wav",
    "expires_at": "2026-10-27T11:53:45.938Z"
  }
}
```

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 upload session with this id in your account. A session that belongs to another account is reported the same way.

```json
{
  "error": {
    "code": "upload_session_not_found",
    "message": "Upload session not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_session_not_found"
  }
}
```

429: Over the account request budget. 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: Storage or the database is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_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_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_session_not_found`](https://www.scarleta.ai/docs/errors#upload_session_not_found), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
SESSIONID="YOUR_SESSIONID"
curl "https://api.scarleta.ai/v1/audio/uploads/$SESSIONID" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## POST /v1/audio/uploads/{sessionId}/abort

Operation id: `abort_upload_session`

**Cancel an upload session**

Cancels an unfinished session: the parts are discarded and the storage reserved for the file is released. Cancelling a session that is already cancelled succeeds again; one that already completed returns 409 `session_not_uploading` (delete the file instead). Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sessionId` | path | string | yes | The `session_id` returned when the session was created. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `status` | "aborted" |   |

### Responses

200: The session is cancelled. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "status": "aborted"
  }
}
```

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 upload session with this id in your account. A session that belongs to another account is reported the same way.

```json
{
  "error": {
    "code": "upload_session_not_found",
    "message": "Upload session not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_session_not_found"
  }
}
```

409: The session already completed.

```json
{
  "error": {
    "code": "session_not_uploading",
    "message": "This upload has already finished",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#session_not_uploading"
  }
}
```

429: Over the account request budget. 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: Storage or the database is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_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_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`session_not_uploading`](https://www.scarleta.ai/docs/errors#session_not_uploading), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_session_not_found`](https://www.scarleta.ai/docs/errors#upload_session_not_found), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
SESSIONID="YOUR_SESSIONID"
curl -X POST "https://api.scarleta.ai/v1/audio/uploads/$SESSIONID/abort" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## POST /v1/audio/uploads/{sessionId}/complete

Operation id: `complete_upload_session`

**Complete an upload session**

Assembles the uploaded parts into the file. Every part must be uploaded with the right size and the total must match the size you declared. The bytes are then checked: a file that is not audio is removed and refused with 415 `not_audio`. On success the file is ready in your library. Completing a session that already completed returns the same result, so the call is safe to retry.

Optionally add `tags` (merged with the ones from the start), change `version_of`, or give `client_duration_seconds`. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sessionId` | path | string | yes | The `session_id` returned when the session was created. |

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `tags` | array<string> | Tags to put on the file, at most 50 of at most 64 characters each. Blank tags are dropped and duplicates merged. |
| `version_of` | string | `audio_id` of one of your ready files to attach this upload to. A value that is not one of yours is ignored. Allowed: at most 200 characters. |
| `client_duration_seconds` | number | Length of the audio in seconds. Display only. Allowed: at least 0. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `audio_id` | string | Library identifier of the file. |
| `file_id` | string | Identifier of the file. |
| `size_bytes` | integer | Final size in bytes. Allowed: -9007199254740991 to 9007199254740991. |
| `format` | string | Detected audio format. |
| `status` | "ready" |   |
| `version_link` | "linked" \| "failed" \| null | `linked` or `failed` when `version_of` was requested, otherwise `null`. |

### Responses

200: The file is ready. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "audio_id": "audio_bbb14949dc19",
    "file_id": "9e8d7c6b-5a49-4382-b170-6f5e4d3c2b1a",
    "size_bytes": 1920044,
    "format": "wav",
    "status": "ready",
    "version_link": null
  }
}
```

400: `tags` or `client_duration_seconds` is invalid.

```json
{
  "error": {
    "code": "invalid_tags",
    "message": "tags must be an array of strings",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_tags"
  }
}
```

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 upload session with this id in your account. A session that belongs to another account is reported the same way.

```json
{
  "error": {
    "code": "upload_session_not_found",
    "message": "Upload session not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_session_not_found"
  }
}
```

409: A part is missing or has the wrong size, the parts add up to more than the declared size, or the session was cancelled or already ended another way.

```json
{
  "error": {
    "code": "parts_missing",
    "message": "Some parts of this upload are missing",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#parts_missing"
  }
}
```

413: The uploaded bytes exceed the per-file limit; the upload is cancelled.

```json
{
  "error": {
    "code": "file_too_large",
    "message": "This file is larger than 10 GB, the most a single file can be.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#file_too_large"
  }
}
```

415: The assembled file is not a supported audio or video file; it is removed.

```json
{
  "error": {
    "code": "not_audio",
    "message": "This isn't an audio file we can store.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#not_audio"
  }
}
```

429: Over the account request budget. 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: Storage is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`file_too_large`](https://www.scarleta.ai/docs/errors#file_too_large), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_duration`](https://www.scarleta.ai/docs/errors#invalid_duration), [`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), [`invalid_tags`](https://www.scarleta.ai/docs/errors#invalid_tags), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`not_audio`](https://www.scarleta.ai/docs/errors#not_audio), [`part_size_mismatch`](https://www.scarleta.ai/docs/errors#part_size_mismatch), [`parts_missing`](https://www.scarleta.ai/docs/errors#parts_missing), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`session_not_uploading`](https://www.scarleta.ai/docs/errors#session_not_uploading), [`size_mismatch`](https://www.scarleta.ai/docs/errors#size_mismatch), [`tag_too_long`](https://www.scarleta.ai/docs/errors#tag_too_long), [`too_many_tags`](https://www.scarleta.ai/docs/errors#too_many_tags), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_reservation_missing`](https://www.scarleta.ai/docs/errors#upload_reservation_missing), [`upload_session_not_found`](https://www.scarleta.ai/docs/errors#upload_session_not_found), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
SESSIONID="YOUR_SESSIONID"
curl -X POST "https://api.scarleta.ai/v1/audio/uploads/$SESSIONID/complete" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": [
      "morning"
    ]
  }'
```

## POST /v1/audio/uploads/{sessionId}/part-urls

Operation id: `create_upload_part_urls`

**Get upload URLs for parts**

Returns one upload URL per requested part, at most 20 per call. Send each part's bytes with a `PUT` to its `url`, exactly as returned and with no API key (the URL is the credential), within 60 minutes. A part is exactly `part_size_bytes` long except the last one. A part can be sent again; the last copy wins. Ask again for fresh URLs if they expire. URLs are opaque: do not parse them. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; these routes do not use up the hourly account request budget. Available to free and paid accounts.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sessionId` | path | string | yes | The `session_id` returned when the session was created. |

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `part_numbers` | array<integer> | Parts to get URLs for, each between 1 and the session's `part_count`, at most 20 numbers. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `urls` | array<object> |   |
| `urls[].part_number` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `urls[].url` | string | Where to `PUT` this part. Opaque; use it as returned. |
| `urls[].expires_at` | string | ISO 8601 timestamp. |

### Responses

200: One URL per requested part. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "urls": [
      {
        "part_number": 1,
        "url": "https://storage.example.com/uploads/part-1?signature=…",
        "expires_at": "2026-10-21T04:40:00.000Z"
      }
    ]
  }
}
```

400: `part_numbers` is empty, too long or has a number outside the session.

```json
{
  "error": {
    "code": "invalid_part_numbers",
    "message": "part_numbers must be an array of 1–20 part numbers",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_part_numbers"
  }
}
```

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 upload session with this id in your account. A session that belongs to another account is reported the same way.

```json
{
  "error": {
    "code": "upload_session_not_found",
    "message": "Upload session not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_session_not_found"
  }
}
```

409: The session is already completed or cancelled.

```json
{
  "error": {
    "code": "session_not_uploading",
    "message": "This upload has already finished",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#session_not_uploading"
  }
}
```

429: Over the account request budget. 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: Part URLs could not be created. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_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_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), [`invalid_part_numbers`](https://www.scarleta.ai/docs/errors#invalid_part_numbers), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`session_not_uploading`](https://www.scarleta.ai/docs/errors#session_not_uploading), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`upload_session_not_found`](https://www.scarleta.ai/docs/errors#upload_session_not_found), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
SESSIONID="YOUR_SESSIONID"
curl -X POST "https://api.scarleta.ai/v1/audio/uploads/$SESSIONID/part-urls" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_numbers": [
      1
    ]
  }'
```

## PUT /v1/audio/uploads/{sessionId}/parts/{n}

Operation id: `upload_session_part`

**Upload a part through the API**

Fallback target of a part URL. When a URL from the part-urls route points at this API instead of at storage, send the part bytes to it exactly as returned: it already carries the one-time `t` token, which is the credential, so there is no API key. The request needs a `Content-Length` header (without one it is refused with HTTP 411 `missing_content_length`) and a body no larger than `part_size_bytes`. Most accounts receive storage URLs and never call this route.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sessionId` | path | string | yes | The `session_id` of the session. |
| `n` | path | string | yes | Part number, from 1 to the session's `part_count`. |
| `t` | query | string | no | One-time token. Already part of the URL that points here; leave it as returned. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `part_number` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `etag` | string | Opaque checksum of the stored part. |

### Responses

200: The part is stored. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "part_number": 1,
    "etag": "a54357aff0632cce46d942af68356b38"
  }
}
```

400: The part number is not within the session, or the body is missing.

```json
{
  "error": {
    "code": "invalid_part_number",
    "message": "part number must be between 1 and 3",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_part_number"
  }
}
```

403: The URL has expired, or the token is missing or wrong.

```json
{
  "error": {
    "code": "invalid_upload_token",
    "message": "Upload URL has expired or is invalid",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_upload_token"
  }
}
```

409: The session already completed or was cancelled.

```json
{
  "error": {
    "code": "session_not_uploading",
    "message": "This upload has already finished",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#session_not_uploading"
  }
}
```

413: The body is larger than `part_size_bytes`.

```json
{
  "error": {
    "code": "part_too_large",
    "message": "This part is larger than the upload part size",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#part_too_large"
  }
}
```

429: Over the account request budget. 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: Storage or the database is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "upload_unavailable",
    "message": "Upload couldn't start. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#upload_unavailable"
  }
}
```

### Error codes

[`invalid_part_number`](https://www.scarleta.ai/docs/errors#invalid_part_number), [`invalid_upload_token`](https://www.scarleta.ai/docs/errors#invalid_upload_token), [`missing_body`](https://www.scarleta.ai/docs/errors#missing_body), [`missing_content_length`](https://www.scarleta.ai/docs/errors#missing_content_length), [`part_too_large`](https://www.scarleta.ai/docs/errors#part_too_large), [`rate_limit_exceeded`](https://www.scarleta.ai/docs/errors#rate_limit_exceeded), [`rate_limit_unavailable`](https://www.scarleta.ai/docs/errors#rate_limit_unavailable), [`session_not_uploading`](https://www.scarleta.ai/docs/errors#session_not_uploading), [`upload_unavailable`](https://www.scarleta.ai/docs/errors#upload_unavailable)

### Example

```bash
SESSIONID="YOUR_SESSIONID"
N="YOUR_N"
curl -X PUT "https://api.scarleta.ai/v1/audio/uploads/$SESSIONID/parts/$N" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: audio/wav" \
  --data-binary @./recording.wav
```
