# File details: Scarleta API reference

Page: https://www.scarleta.ai/docs/reference/file-details
Base URL: https://api.scarleta.ai. Authentication: `Authorization: Bearer $SCARLETA_API_KEY`.
Operations: 5.

## GET /v1/audio/{audioId}/cover

Operation id: `get_file_cover`

**Download the cover image of a file**

Returns the cover image of one of your files. The response body is the image itself (`image/jpeg`, `image/png` or `image/webp`), not JSON, and may be cached privately for five minutes. Works for files in the trash too. A file with no cover is 404 `cover_not_found`; a file that is not yours or does not exist is 404 `not_found`. If the database is briefly unavailable the route answers 503 `db_unavailable`; retry after a short wait.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `audioId` | path | string | yes | The `audio_id` or the file id of one of your files. |

### Responses

200: The cover image. The body is binary, not JSON.

```text
Binary file
Content-Type: image/jpeg | image/png | image/webp
```

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: The file has no cover (`cover_not_found`), or it does not exist or is not yours (`not_found`).

```json
{
  "error": {
    "code": "cover_not_found",
    "message": "This file has no cover image",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#cover_not_found"
  }
}
```

429: Over the account request budget or the per-minute cap of this route. 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), [`cover_not_found`](https://www.scarleta.ai/docs/errors#cover_not_found), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_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), [`not_found`](https://www.scarleta.ai/docs/errors#not_found), [`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)

### Example

```bash
AUDIOID="YOUR_AUDIOID"
curl "https://api.scarleta.ai/v1/audio/$AUDIOID/cover" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  --fail \
  -o output.jpg
```

## PUT /v1/audio/{audioId}/cover

Operation id: `set_file_cover`

**Set the cover image of a file**

Stores a cover image for one of your files, replacing any earlier one. The request body is the image itself, not JSON: a JPEG, PNG or WebP of at most 4 MiB (4194304 bytes). Send the matching `Content-Type`; the format is recognised from the bytes, so a file in another format, or an empty body, is 400 `invalid_image`, and a larger one is 413 `image_too_large`. A file that is not yours, does not exist or is in the trash is 404 `not_found`. The answer is the address to fetch the cover from. If storage or the database is briefly unavailable the route answers 503 `upload_unavailable`; retry after a short wait. Success is `{ data }` without `meta`.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `audioId` | path | string | yes | The `audio_id` or the file id of one of your files. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `cover_url` | string | Address to fetch the cover from. Send your API key with the request. |

### Responses

200: The cover is stored.

```json
{
  "data": {
    "cover_url": "https://api.scarleta.ai/v1/audio/audio_3f9c1a7d2b60/cover"
  }
}
```

400: The body is empty or is not a JPEG, PNG or WebP image.

```json
{
  "error": {
    "code": "invalid_image",
    "message": "That doesn't look like a JPEG, PNG or WebP image",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_image"
  }
}
```

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: The file does not exist, is in the trash, or is not yours.

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

413: The image is larger than 4 MiB.

```json
{
  "error": {
    "code": "image_too_large",
    "message": "Cover images must be 4 MB or smaller",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#image_too_large"
  }
}
```

429: Over the account request budget or the per-minute cap of this route. 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), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_unavailable), [`image_too_large`](https://www.scarleta.ai/docs/errors#image_too_large), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_image`](https://www.scarleta.ai/docs/errors#invalid_image), [`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), [`not_found`](https://www.scarleta.ai/docs/errors#not_found), [`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
AUDIOID="YOUR_AUDIOID"
curl -X PUT "https://api.scarleta.ai/v1/audio/$AUDIOID/cover" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @./upload.jpg
```

## DELETE /v1/audio/{audioId}/cover

Operation id: `delete_file_cover`

**Remove the cover image of a file**

Removes the cover image of one of your files. The answer is 204 with no body. Removing a cover that is not there also answers 204. A file that is not yours, does not exist or is in the trash is 404 `not_found`. If the database is briefly unavailable the route answers 503 `db_unavailable`; retry after a short wait.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `audioId` | path | string | yes | The `audio_id` or the file id of one of your files. |

### Responses

204: The cover is removed. There is no body.

```text
(empty body)
```

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: The file does not exist, is in the trash, or is not yours.

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

429: Over the account request budget or the per-minute cap of this route. 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), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_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), [`not_found`](https://www.scarleta.ai/docs/errors#not_found), [`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)

### Example

```bash
AUDIOID="YOUR_AUDIOID"
curl -X DELETE "https://api.scarleta.ai/v1/audio/$AUDIOID/cover" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -i
```

## GET /v1/audio/{audioId}/details

Operation id: `get_file_details`

**Get the details of a file**

Returns the editable details of one of your files (title, artist, album, project, credits, tempo and key you entered) next to the tempo and key the analyses found, so you can show either. Works for files in the trash too, so the file page of a trashed file still loads. A file that is not yours or does not exist is 404 `not_found`. `cover_url` is where to fetch the cover image; it is null when the file has none. If the database is briefly unavailable the route answers 503 `db_unavailable`; retry after a short wait. Success is `{ data }` without `meta`.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `audioId` | path | string | yes | The `audio_id` or the file id of one of your files. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `audio_id` | string | Registry identifier of the file; it starts with `audio_`. |
| `title` | string \| null | Title you set, or null. |
| `artist` | string \| null | Artist you set, or null. |
| `album` | string \| null | Album you set, or null. |
| `project` | string \| null | Project you set, or null. |
| `credits` | string \| null | Free-text credits you set, or null. |
| `user_bpm` | number \| null | Tempo you entered by hand, or null. |
| `user_key_label` | string \| null | Musical key you entered by hand, for example `A minor`, or null. |
| `analysis_bpm` | number \| null | Tempo found by the rhythm analysis, or null when the file has none. |
| `analysis_key_label` | string \| null | Key found by the harmony analysis, or null when the file has none. |
| `embedded` | object \| null | Tags read from inside the uploaded file (title, artist and so on), or null. |
| `archived_at` | string \| number \| null | When the file was archived, or null. |
| `trashed_at` | string \| number \| null | When the file was moved to the trash, or null. |
| `cover_url` | string \| null | Address of the cover image, or null when the file has no cover. Fetch it with your API key. |

### Responses

200: The details of the file.

```json
{
  "data": {
    "audio_id": "audio_3f9c1a7d2b60",
    "title": "Morning Session",
    "artist": "Example Artist",
    "album": null,
    "project": "Demo EP",
    "credits": "Recorded at home",
    "user_bpm": 120,
    "user_key_label": "A minor",
    "analysis_bpm": 119.8,
    "analysis_key_label": "A minor",
    "embedded": null,
    "archived_at": null,
    "trashed_at": null,
    "cover_url": null
  }
}
```

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: The file does not exist or is not yours.

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

429: Over the account request budget or the per-minute cap of this route. 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), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_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), [`not_found`](https://www.scarleta.ai/docs/errors#not_found), [`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)

### Example

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

## PATCH /v1/audio/{audioId}/details

Operation id: `update_file_details`

**Edit the details of a file**

Changes the details of one of your files. Send only the fields you want to change; a field you leave out stays as it is, and `null` clears it. A blank text becomes null. `title`, `artist`, `album` and `project` take at most 200 characters, `credits` at most 2000. `user_bpm` is a number from 20 to 300. `user_key_label` is a key such as `A minor` or `C# major`; it is stored in that normal form. Fields the route does not know are ignored. The answer is the updated details, as `GET` returns them. A file that is not yours, does not exist or is in the trash is 404 `not_found`; the file is checked before the fields.

A text field that is not a string is `invalid_field`; a text field longer than its limit is `field_too_long`; a `user_key_label` that is not a valid key is `invalid_key_label`. If the database is briefly unavailable the route answers 503 `db_unavailable`; retry after a short wait. Success is `{ data }` without `meta`.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `audioId` | path | string | yes | The `audio_id` or the file id of one of your files. |

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `title` | string \| null | Title, at most 200 characters, or null to clear. |
| `artist` | string \| null | Artist, at most 200 characters, or null to clear. |
| `album` | string \| null | Album, at most 200 characters, or null to clear. |
| `project` | string \| null | Project, at most 200 characters, or null to clear. |
| `credits` | string \| null | Credits, at most 2000 characters, or null to clear. |
| `user_bpm` | number \| null | Tempo in beats per minute, from 20 to 300, or null to clear. |
| `user_key_label` | string \| null | Musical key such as `A minor` or `C# major`, or null to clear. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `audio_id` | string | Registry identifier of the file; it starts with `audio_`. |
| `title` | string \| null | Title you set, or null. |
| `artist` | string \| null | Artist you set, or null. |
| `album` | string \| null | Album you set, or null. |
| `project` | string \| null | Project you set, or null. |
| `credits` | string \| null | Free-text credits you set, or null. |
| `user_bpm` | number \| null | Tempo you entered by hand, or null. |
| `user_key_label` | string \| null | Musical key you entered by hand, for example `A minor`, or null. |
| `analysis_bpm` | number \| null | Tempo found by the rhythm analysis, or null when the file has none. |
| `analysis_key_label` | string \| null | Key found by the harmony analysis, or null when the file has none. |
| `embedded` | object \| null | Tags read from inside the uploaded file (title, artist and so on), or null. |
| `archived_at` | string \| number \| null | When the file was archived, or null. |
| `trashed_at` | string \| number \| null | When the file was moved to the trash, or null. |
| `cover_url` | string \| null | Address of the cover image, or null when the file has no cover. Fetch it with your API key. |

### Responses

200: The updated details.

```json
{
  "data": {
    "audio_id": "audio_3f9c1a7d2b60",
    "title": "Morning Session (final)",
    "artist": "Example Artist",
    "album": null,
    "project": "Demo EP",
    "credits": "Recorded at home",
    "user_bpm": 120,
    "user_key_label": "A minor",
    "analysis_bpm": 119.8,
    "analysis_key_label": "A minor",
    "embedded": null,
    "archived_at": null,
    "trashed_at": null,
    "cover_url": null
  }
}
```

400: The body is not valid JSON (`invalid_json`); a text field is not a string (`invalid_field`) or is too long (`field_too_long`); `user_bpm` is out of range (`invalid_bpm`); or `user_key_label` is not a valid key (`invalid_key_label`).

```json
{
  "error": {
    "code": "field_too_long",
    "message": "title must be 200 characters or fewer",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#field_too_long"
  }
}
```

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: The file does not exist, is in the trash, or is not yours.

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

413: The request body is larger than 64 KB.

```json
{
  "error": {
    "code": "payload_too_large",
    "message": "Request body must be 64 KB or smaller",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#payload_too_large"
  }
}
```

429: Over the account request budget or the per-minute cap of this route. 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), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_unavailable), [`field_too_long`](https://www.scarleta.ai/docs/errors#field_too_long), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_bpm`](https://www.scarleta.ai/docs/errors#invalid_bpm), [`invalid_field`](https://www.scarleta.ai/docs/errors#invalid_field), [`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_key_label`](https://www.scarleta.ai/docs/errors#invalid_key_label), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`not_found`](https://www.scarleta.ai/docs/errors#not_found), [`payload_too_large`](https://www.scarleta.ai/docs/errors#payload_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), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized)

### Example

```bash
AUDIOID="YOUR_AUDIOID"
curl -X PATCH "https://api.scarleta.ai/v1/audio/$AUDIOID/details" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Morning Session (final)",
    "user_bpm": 120,
    "user_key_label": "A minor"
  }'
```
