# Library: Scarleta API reference

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

## GET /v1/audio/library

Operation id: `list_library`

**List the library**

Lists your files grouped into tracks, newest first by default. A track holds one file, or several versions of the same song that you grouped. Each version includes the analyses it already has, plus its tags and archive state. Only your own files are ever returned.

Page with `limit` and `cursor`: pass `pagination.cursor` from one response as `cursor` in the next while `pagination.has_more` is true. `limit` is 1 to 200. The analysis results make responses large; send `include_summaries=false` to receive only the category names. With summaries, an array inside a summary object that holds more than 2048 values in total (counting every number or string, also inside nested arrays, such as an embedding or a spectrogram; each record of a list of records counts as one, plus any array inside it) is left out of the list, and its path is named in the summary's `omitted_large_arrays`. A summary that is itself an array is returned whole. `GET /v1/audio/:audioId/analyses` returns the complete summary.

**View mode.** Send `view` (a saved view id) and/or `q` (a view definition as base64url JSON, `{"version":2,"filters":{...}}`) and the route reads the library through that view instead. The response then carries the standard `{ data, meta }` envelope with an `X-Request-ID` header, `data` also has `total_tracks`, `coverage` and `view`, tracks have `hidden_versions` and `group_keys`, versions have `values`, `details`, `starred` and `duration_seconds`, and `limit` is at most 100. An unknown `view` is 404 `view_not_found`; an invalid `q` is 400 `invalid_config`; grouping by sound before building sound groups is 400 `invalid_group`.

Without `view` and `q` the route answers with the older body shape: success is `{ data }` with no `meta`, errors for bad query values are `{ error, message }`, and the older bodies have no `request_id` of their own.

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

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Tracks per page, 1 to 200 (at most 100 in view mode). |
| `cursor` | query | string | no | The `pagination.cursor` of the previous page. |
| `sort_key` | query | "uploaded" \| "name" \| "size" \| "last_activity" \| "tags" | no | What to sort by. Defaults to `uploaded`. |
| `sort_dir` | query | "asc" \| "desc" | no | Sort direction. Defaults to `desc`. |
| `filter_tags` | query | string | no | Comma-separated tags to keep, at most 25. |
| `filter_analysis_type` | query | string | no | Keep files that have this analysis category. |
| `filter_date_from` | query | number | no | Keep files uploaded at or after this time, in milliseconds since the Unix epoch. |
| `filter_date_to` | query | number | no | Keep files uploaded at or before this time, in milliseconds since the Unix epoch. |
| `filter_archived` | query | "true" | no | `true` returns only archived files. Archived files are hidden otherwise. |
| `view` | query | string | no | Id of one of your saved views. Switches the route to view mode; see the description. |
| `q` | query | string | no | A view definition, JSON encoded as base64url. Switches the route to view mode; see the description. |
| `include_summaries` | query | "true" \| "false" | no | `false` leaves out the stored analysis results and keeps only the category names. Defaults to `true`. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `storage` | object |   |
| `storage.used_bytes` | number | Bytes your files currently use. |
| `storage.cap_bytes` | number | The storage allowance shown for the account, in bytes. |
| `storage.tier` | "free" \| "paid" | Whether the account is on the free or the paid tier. |
| `storage.past_due` | boolean | True when the account has an unpaid storage overage. |
| `storage.enforced` | boolean | True when storage locks are actually applied. |
| `tracks` | array<object> |   |
| `tracks[].id` | string | Track identifier. |
| `tracks[].name` | string | Name of the newest version. |
| `tracks[].versions` | array<object> | Versions of the track, newest first. |
| `tracks[].versions[].id` | string | Identifier of this version of the track. Use it wherever a route asks for a file id. |
| `tracks[].versions[].audio_id` | string | Registry identifier of the file; it starts with `audio_`. |
| `tracks[].versions[].name` | string | Display name of the file. |
| `tracks[].versions[].label` | string | Short label shown for this version. |
| `tracks[].versions[].uploaded` | number \| null | When the file was uploaded, in milliseconds since the Unix epoch. |
| `tracks[].versions[].file_size_bytes` | number \| null | Size of the stored file in bytes, when known. |
| `tracks[].versions[].format` | string \| null | File format, for example `wav`, when known. |
| `tracks[].versions[].download_available` | boolean | True when the original file is stored and can be downloaded. |
| `tracks[].versions[].audio_url` | string \| null | URL to pass as `audio_url` when running an analysis on this file. Null when the original is not stored. |
| `tracks[].versions[].playback_url` | string \| null | Short-lived URL for playing the file in a browser. Null when `audio_url` is null. |
| `tracks[].versions[].analyses` | array<object> | The analyses this file already has. |
| `tracks[].versions[].locked` | boolean | True when the file is locked because of an unpaid storage overage. Locked files stay listed. |
| `tracks[].versions[].pending_categories` | array<string> | Analysis categories still running on this file. |
| `tracks[].versions[].failed_categories` | array<string> | Analysis categories that failed and have no completed result. |
| `tracks[].versions[].tags` | array<string> | Tags you added to this file. |
| `tracks[].versions[].is_best` | boolean | True when this version is marked as the best take of its track. |
| `tracks[].versions[].archived_at` | string \| number \| null | When the file was archived, or null. |
| `tracks[].versions[].last_activity` | number \| null | The latest of upload time, newest analysis result and newest note, in milliseconds since the Unix epoch. |
| `tracks[].versions[].duration_seconds` | number \| null | Length of the audio. Present only when you send `view` or `q`. |
| `tracks[].versions[].values` | object | The searchable values of this file (`bpm`, `key_label`, ...) with `bpm_source` and `key_source`. Present only when you send `view` or `q`. |
| `tracks[].versions[].details` | object | File details such as title, artist and trash times. Present only when you send `view` or `q`. |
| `tracks[].versions[].starred` | boolean | True when the file is starred. Present only when you send `view` or `q`. |
| `tracks[].versions[].similarity` | number \| null | Similarity to a `sounds_like` reference. Present only with that filter. |
| `tracks[].hidden_versions` | integer | Versions the view hides. View mode only. Allowed: -9007199254740991 to 9007199254740991. |
| `tracks[].group_keys` | array<string> | The group buckets of the track. View mode only. |
| `pagination` | object |   |
| `pagination.limit` | integer \| null | The page size used, or null when you sent no `limit`. Allowed: -9007199254740991 to 9007199254740991. |
| `pagination.cursor` | string \| null | Pass this as `cursor` to get the next page. |
| `pagination.has_more` | boolean |   |
| `total_tracks` | integer | Tracks the view matches. View mode only. Allowed: -9007199254740991 to 9007199254740991. |
| `coverage` | object | How many files have each searchable value. View mode only. |
| `coverage.total_library_size` | integer | Files in scope before any value filter. Allowed: -9007199254740991 to 9007199254740991. |
| `coverage.indexed` | object | For each searchable value (`bpm`, `key_label`, ...), how many files in scope have it. |
| `view` | object | The view that was applied. View mode only. |
| `view.id` | string \| null |   |
| `view.name` | string \| null |   |
| `view.definition` | object |   |
| `view.dropped` | array<string> |   |

### Responses

200: A page of tracks. Without `view` and `q` this response has no `meta`.

```json
{
  "data": {
    "storage": {
      "used_bytes": 184220160,
      "cap_bytes": 1099511627776,
      "tier": "paid",
      "past_due": false,
      "enforced": true
    },
    "tracks": [
      {
        "id": "audio_3f9c1a7d2b60",
        "name": "demo-mix.wav",
        "versions": [
          {
            "id": "audio_3f9c1a7d2b60",
            "audio_id": "audio_3f9c1a7d2b60",
            "name": "demo-mix.wav",
            "label": "demo-mix.wav",
            "uploaded": 1791333870854,
            "file_size_bytes": 5290044,
            "format": "wav",
            "download_available": true,
            "audio_url": "https://api.scarleta.ai/v1/audio/media/9b2c7d1e-5a40-4f83-b6c9-0d1e2f3a4b5c.wav",
            "playback_url": null,
            "analyses": [
              {
                "category": "rhythm"
              }
            ],
            "locked": false,
            "pending_categories": [],
            "failed_categories": [],
            "tags": [
              "demo"
            ],
            "is_best": false,
            "archived_at": null,
            "last_activity": 1791333870854
          }
        ]
      }
    ],
    "pagination": {
      "limit": 25,
      "cursor": "1791333870854_audio_3f9c1a7d2b60",
      "has_more": false
    }
  }
}
```

400: A query value is not allowed: `limit` out of range, an unsupported `sort_key` or `sort_dir`, or too many `filter_tags` (body `{ error, message }`); in view mode, an invalid `q`, `view` or `group`, or a `limit` above the view cap (standard error body).

```json
{
  "error": "invalid_limit",
  "message": "limit must be an integer between 1 and 200"
}
```

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: View mode: the saved view does not exist or is not yours.

```json
{
  "error": {
    "code": "view_not_found",
    "message": "View not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#view_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_config`](https://www.scarleta.ai/docs/errors#invalid_config), [`invalid_group`](https://www.scarleta.ai/docs/errors#invalid_group), [`invalid_key`](https://www.scarleta.ai/docs/errors#invalid_key), [`invalid_key_format`](https://www.scarleta.ai/docs/errors#invalid_key_format), [`invalid_limit`](https://www.scarleta.ai/docs/errors#invalid_limit), [`invalid_sort_dir`](https://www.scarleta.ai/docs/errors#invalid_sort_dir), [`invalid_sort_key`](https://www.scarleta.ai/docs/errors#invalid_sort_key), [`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), [`too_many_filter_tags`](https://www.scarleta.ai/docs/errors#too_many_filter_tags), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`view_not_found`](https://www.scarleta.ai/docs/errors#view_not_found)

### Example

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

## POST /v1/audio/library/batch

Operation id: `run_recipe_on_library_view`

**Run a recipe on a set of library files**

Runs one recipe on every library file that matches a saved view (`view_id`) or a set of `filters`, as a batch. Send exactly one of the two. Batches are available on paid accounts only; a free-account key receives 403 `batch_paid_only`.

Send `dry_run: true` first: it changes nothing, charges nothing, and returns how many files matched and what the run would cost. Without `dry_run` the run starts and the call returns `202` with a `batch_id`; follow it with the batch routes. At most 500 files may match (`max_items` can lower that); a bigger match is refused with 422 `query_result_too_large` and an empty match with 422 `query_result_empty`. A real run is charged and rate limited exactly like a batch submit; see the Batch reference for the cost rule.

Access: API key required; paid accounts only. Cost: uses tokens. A dry run is free. A real run is charged per file when its run finishes, like a batch submit. Use `dry_run` to see the estimate first..

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `recipe_id` | string | The recipe to run on every matched file. Allowed: at least 1 character. |
| `view_id` | string | Id of one of your saved views. Send this or `filters`, not both. |
| `filters` | object | Which files to keep. Every field you send narrows the result; an empty object keeps every file. An unknown field is refused with 400 `invalid_filter`. |
| `filters.bpm` | object \| object | Tempo range `{min, max}`, or `{near, tolerance}` (tolerance defaults to 3). |
| `filters.bpm.min` | number |   |
| `filters.bpm.max` | number |   |
| `filters.bpm.near` | number |   |
| `filters.bpm.tolerance` | number |   |
| `filters.tempo_stability` | object |   |
| `filters.tempo_stability.min` | number |   |
| `filters.tempo_stability.max` | number |   |
| `filters.loudness_integrated` | object |   |
| `filters.loudness_integrated.min` | number |   |
| `filters.loudness_integrated.max` | number |   |
| `filters.loudness_range` | object |   |
| `filters.loudness_range.min` | number |   |
| `filters.loudness_range.max` | number |   |
| `filters.true_peak_dbfs` | object |   |
| `filters.true_peak_dbfs.min` | number |   |
| `filters.true_peak_dbfs.max` | number |   |
| `filters.pitch_lowest_midi` | object |   |
| `filters.pitch_lowest_midi.min` | number |   |
| `filters.pitch_lowest_midi.max` | number |   |
| `filters.pitch_highest_midi` | object |   |
| `filters.pitch_highest_midi.min` | number |   |
| `filters.pitch_highest_midi.max` | number |   |
| `filters.notes_per_second` | object |   |
| `filters.notes_per_second.min` | number |   |
| `filters.notes_per_second.max` | number |   |
| `filters.duration_seconds` | object |   |
| `filters.duration_seconds.min` | number |   |
| `filters.duration_seconds.max` | number |   |
| `filters.key_label` | string \| array<string> | One key name or a list, for example `A minor`. |
| `filters.key_root` | string \| array<string> |   |
| `filters.key_mode` | "major" \| "minor" |   |
| `filters.meter` | string \| array<string> | One time signature or a list, for example `4/4`. |
| `filters.speech_language` | string \| array<string> |   |
| `filters.format` | array<string> | File formats to keep, for example `["wav", "mp3"]`. |
| `filters.transcript_contains` | string | Keep files whose transcript contains this text. |
| `filters.sound_description` | string |   |
| `filters.name_contains` | string | Keep files whose name contains this text. |
| `filters.user_tags` | object | Tag rules: keep files with any, with all, or with none of the listed tags. |
| `filters.user_tags.any` | array<string> |   |
| `filters.user_tags.all` | array<string> |   |
| `filters.user_tags.none` | array<string> |   |
| `filters.auto_tags` | array<string> |   |
| `filters.analyzed` | array<string> | Keep files that have results for every listed analysis category. |
| `filters.analyzed_any` | array<string> | Keep files that have results for at least one listed category. |
| `filters.not_analyzed` | boolean | True keeps only files with no analysis results. |
| `filters.missing` | array<string> | Keep files that lack a value, for example `["bpm"]`. |
| `filters.uploaded_after` | number | Milliseconds since the Unix epoch. |
| `filters.uploaded_before` | number | Milliseconds since the Unix epoch. |
| `filters.starred` | boolean |   |
| `filters.archived` | "hide" \| "include" \| "only" | Archived files: hidden (default), included, or the only ones shown. |
| `filters.include_derived` | boolean | True also returns files made from other files, such as stems. |
| `filters.sounds_like` | object | Keep files that sound like the given file. |
| `filters.sounds_like.audio_id` | string |   |
| `filters.sounds_like.min_score` | number |   |
| `filters.sound_group` | object | Keep the files of one sound group from a grouping job. |
| `filters.sound_group.job_id` | string |   |
| `filters.sound_group.group_id` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `filters.trash` | "hide" \| "include" \| "only" | Trashed files: hidden (default), included, or the only ones shown. |
| `filters.duplicates` | boolean | True keeps only files that have an identical copy. |
| `filters.artist` | array<string> |   |
| `filters.project` | array<string> |   |
| `filters.album` | array<string> |   |
| `filters.title_contains` | string |   |
| `filters.credits_contains` | string |   |
| `max_items` | integer | Refuse the run when more files than this match. 1 to 500; defaults to 500. |
| `dry_run` | boolean | True returns the estimate and starts nothing. Defaults to false. |
| `name` | string | A label for the batch (real runs only). |
| `webhook_url` | string | Where to POST the `batch_completed` event (real runs only). At most 4096 bytes. |
| `metadata` | object | Free-form JSON kept with the batch (real runs only), at most 8192 bytes when serialized. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `matched_count` | integer | Files the view or filters matched. Allowed: -9007199254740991 to 9007199254740991. |
| `recipe_id` | string |   |
| `total_cost` | number | Tokens the run would cost. |
| `total_duration_seconds` | number | Audio length the estimate covers, in seconds. |
| `unknown_duration_count` | integer | Files whose length could not be read; they add nothing to `total_cost`. Allowed: -9007199254740991 to 9007199254740991. |
| `approximate` | boolean | True when some lengths were reported by the uploader or unknown, so the real charge can differ. |
| `per_file` | array<object> |   |
| `per_file[].audio_id` | string |   |
| `per_file[].audio_url` | string |   |
| `per_file[].duration_seconds` | number \| null | Length of the file, or null when it could not be read. |
| `per_file[].cost` | number | Tokens this file would cost. |

### Responses

200: Dry run: the estimate. Nothing was started.

```json
{
  "data": {
    "matched_count": 2,
    "recipe_id": "rec_1005_cat_spectral",
    "total_cost": 40,
    "total_duration_seconds": 7.2,
    "unknown_duration_count": 0,
    "approximate": false,
    "per_file": [
      {
        "audio_id": "audio_3f9c1a7d2b60",
        "audio_url": "https://api.scarleta.ai/v1/audio/media/9b2c7d1e-5a40-4f83-b6c9-0d1e2f3a4b5c.wav",
        "duration_seconds": 4,
        "cost": 20
      },
      {
        "audio_id": "audio_a81e5c40d7f2",
        "audio_url": "https://api.scarleta.ai/v1/audio/media/2c4e6a80-1b3d-4f57-8a9c-0e1d2c3b4a59.wav",
        "duration_seconds": 3.2,
        "cost": 20
      }
    ]
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

202: Accepted. The batch is queued.

```json
{
  "data": {
    "batch_id": "b1a2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
    "matched_count": 2,
    "status": "IN_QUEUE"
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

400: The body is not a JSON object, has an unknown field, lacks `recipe_id`, names both or neither of `view_id` and `filters`, or a value such as `max_items` or `dry_run` is not valid.

```json
{
  "error": {
    "code": "invalid_config",
    "message": "Provide exactly one of view_id or filters",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_config"
  }
}
```

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

402: A real run needs a token balance or auto-recharge.

```json
{
  "error": {
    "code": "insufficient_balance",
    "message": "Your team has no token balance and auto-recharge is not active. Top up to submit batches.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#insufficient_balance"
  }
}
```

403: The key belongs to a free account.

```json
{
  "error": {
    "code": "batch_paid_only",
    "message": "Batch processing is available on paid plans only.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#batch_paid_only"
  }
}
```

404: The saved view or the recipe does not exist or is not visible to your account.

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

422: No file matches, or more than `max_items` files match. `error.details.matched_count` says how many matched.

```json
{
  "error": {
    "code": "query_result_empty",
    "message": "No files in this view.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#query_result_empty"
  }
}
```

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), [`batch_paid_only`](https://www.scarleta.ai/docs/errors#batch_paid_only), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_unavailable), [`insufficient_balance`](https://www.scarleta.ai/docs/errors#insufficient_balance), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_config`](https://www.scarleta.ai/docs/errors#invalid_config), [`invalid_dry_run`](https://www.scarleta.ai/docs/errors#invalid_dry_run), [`invalid_filter`](https://www.scarleta.ai/docs/errors#invalid_filter), [`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_max_items`](https://www.scarleta.ai/docs/errors#invalid_max_items), [`missing_api_key`](https://www.scarleta.ai/docs/errors#missing_api_key), [`missing_auth`](https://www.scarleta.ai/docs/errors#missing_auth), [`missing_recipe_id`](https://www.scarleta.ai/docs/errors#missing_recipe_id), [`query_result_empty`](https://www.scarleta.ai/docs/errors#query_result_empty), [`query_result_too_large`](https://www.scarleta.ai/docs/errors#query_result_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), [`recipe_not_found`](https://www.scarleta.ai/docs/errors#recipe_not_found), [`unauthorized`](https://www.scarleta.ai/docs/errors#unauthorized), [`view_not_found`](https://www.scarleta.ai/docs/errors#view_not_found)

### Example

```bash
curl -X POST "https://api.scarleta.ai/v1/audio/library/batch" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipe_id": "rec_1005_cat_spectral",
    "filters": {
      "analyzed": [
        "rhythm"
      ],
      "name_contains": "demo"
    },
    "max_items": 20,
    "dry_run": true
  }'
```

## GET /v1/audio/library/builtin-counts

Operation id: `get_library_counts`

**Count files in the standard views**

Returns one count per standard library view: everything, recently added, starred, not yet analyzed, duplicates and trash. Use it for a quick header without paging through the library. Only your own files are ever returned.

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

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `all` | integer | Files in the library, not counting trash and archive. Allowed: -9007199254740991 to 9007199254740991. |
| `recent_30d` | integer | Files added recently; the key name states the window. Allowed: -9007199254740991 to 9007199254740991. |
| `starred` | integer | Starred files. Allowed: -9007199254740991 to 9007199254740991. |
| `not_analyzed` | integer | Files with no analysis results yet. Allowed: -9007199254740991 to 9007199254740991. |
| `duplicates` | integer | Files that have an identical copy. Allowed: -9007199254740991 to 9007199254740991. |
| `trash` | integer | Files in the trash. Allowed: -9007199254740991 to 9007199254740991. |
| `library_files_total` | integer | Every file in your library, including archived and derived ones, but not files in the trash. Allowed: -9007199254740991 to 9007199254740991. |
| `unverified_hash_count` | integer | Files whose content fingerprint has not been checked yet. Allowed: -9007199254740991 to 9007199254740991. |

### Responses

200: Success

```json
{
  "data": {
    "all": 42,
    "recent_30d": 7,
    "starred": 3,
    "not_analyzed": 11,
    "duplicates": 2,
    "trash": 1,
    "library_files_total": 50,
    "unverified_hash_count": 0
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

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 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), [`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
curl "https://api.scarleta.ai/v1/audio/library/builtin-counts" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## GET /v1/audio/library/overview

Operation id: `get_library_overview`

**Get a library overview**

Summarises the whole library in one call: file and track counts, how many files each analysis category covers, your most used tags, your saved views, and when sound groups were last built. Only your own files are ever returned.

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

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `file_count` | integer | Files in the library. Allowed: -9007199254740991 to 9007199254740991. |
| `track_count` | integer | Tracks in the library; versions of one song count once. Allowed: -9007199254740991 to 9007199254740991. |
| `analyzed_count` | integer | Files with at least one analysis result. Allowed: -9007199254740991 to 9007199254740991. |
| `analyzed_by_category` | object | Number of files with a result in each analysis category. |
| `tags` | array<object> | Your most used tags. |
| `tags[].tag` | string |   |
| `tags[].count` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `saved_views` | array<object> |   |
| `saved_views[].id` | string | Saved view id. |
| `saved_views[].name` | string |   |
| `sound_groups` | object \| null | The latest sound grouping, or null when you never built one. |
| `sound_groups.count` | integer | Groups found. Allowed: -9007199254740991 to 9007199254740991. |
| `sound_groups.built_at` | number | When the grouping finished, in milliseconds since the Unix epoch. |

### Responses

200: Success

```json
{
  "data": {
    "file_count": 42,
    "track_count": 40,
    "analyzed_count": 31,
    "analyzed_by_category": {
      "rhythm": 22,
      "spectral": 18
    },
    "tags": [
      {
        "tag": "demo",
        "count": 5
      }
    ],
    "saved_views": [
      {
        "id": "7d2c9e1a-4b3f-4c68-9a5d-1e0f8b7c6a52",
        "name": "Slow tracks"
      }
    ],
    "sound_groups": null
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

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 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), [`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
curl "https://api.scarleta.ai/v1/audio/library/overview" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## POST /v1/audio/library/query

Operation id: `query_library`

**Query the library**

Finds files by what the analyses found in them: tempo, key, loudness, tags, names, upload time and more. Send `filters`, optionally `sort` and `limit`, and read the matches from `items`. Only your own files are ever returned.

`limit` is 50 unless you send it, and at most 100. While `has_more` is true, send `next_cursor` back as `cursor` with the same filters and sort to get the next page. `total_matches` counts every match; `coverage` shows how many files have each searchable value, so you can tell "no match" from "not analyzed yet". Reading does not spend tokens.

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

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `filters` | object | Which files to keep. Every field you send narrows the result; an empty object keeps every file. An unknown field is refused with 400 `invalid_filter`. |
| `filters.bpm` | object \| object | Tempo range `{min, max}`, or `{near, tolerance}` (tolerance defaults to 3). |
| `filters.bpm.min` | number |   |
| `filters.bpm.max` | number |   |
| `filters.bpm.near` | number |   |
| `filters.bpm.tolerance` | number |   |
| `filters.tempo_stability` | object |   |
| `filters.tempo_stability.min` | number |   |
| `filters.tempo_stability.max` | number |   |
| `filters.loudness_integrated` | object |   |
| `filters.loudness_integrated.min` | number |   |
| `filters.loudness_integrated.max` | number |   |
| `filters.loudness_range` | object |   |
| `filters.loudness_range.min` | number |   |
| `filters.loudness_range.max` | number |   |
| `filters.true_peak_dbfs` | object |   |
| `filters.true_peak_dbfs.min` | number |   |
| `filters.true_peak_dbfs.max` | number |   |
| `filters.pitch_lowest_midi` | object |   |
| `filters.pitch_lowest_midi.min` | number |   |
| `filters.pitch_lowest_midi.max` | number |   |
| `filters.pitch_highest_midi` | object |   |
| `filters.pitch_highest_midi.min` | number |   |
| `filters.pitch_highest_midi.max` | number |   |
| `filters.notes_per_second` | object |   |
| `filters.notes_per_second.min` | number |   |
| `filters.notes_per_second.max` | number |   |
| `filters.duration_seconds` | object |   |
| `filters.duration_seconds.min` | number |   |
| `filters.duration_seconds.max` | number |   |
| `filters.key_label` | string \| array<string> | One key name or a list, for example `A minor`. |
| `filters.key_root` | string \| array<string> |   |
| `filters.key_mode` | "major" \| "minor" |   |
| `filters.meter` | string \| array<string> | One time signature or a list, for example `4/4`. |
| `filters.speech_language` | string \| array<string> |   |
| `filters.format` | array<string> | File formats to keep, for example `["wav", "mp3"]`. |
| `filters.transcript_contains` | string | Keep files whose transcript contains this text. |
| `filters.sound_description` | string |   |
| `filters.name_contains` | string | Keep files whose name contains this text. |
| `filters.user_tags` | object | Tag rules: keep files with any, with all, or with none of the listed tags. |
| `filters.user_tags.any` | array<string> |   |
| `filters.user_tags.all` | array<string> |   |
| `filters.user_tags.none` | array<string> |   |
| `filters.auto_tags` | array<string> |   |
| `filters.analyzed` | array<string> | Keep files that have results for every listed analysis category. |
| `filters.analyzed_any` | array<string> | Keep files that have results for at least one listed category. |
| `filters.not_analyzed` | boolean | True keeps only files with no analysis results. |
| `filters.missing` | array<string> | Keep files that lack a value, for example `["bpm"]`. |
| `filters.uploaded_after` | number | Milliseconds since the Unix epoch. |
| `filters.uploaded_before` | number | Milliseconds since the Unix epoch. |
| `filters.starred` | boolean |   |
| `filters.archived` | "hide" \| "include" \| "only" | Archived files: hidden (default), included, or the only ones shown. |
| `filters.include_derived` | boolean | True also returns files made from other files, such as stems. |
| `filters.sounds_like` | object | Keep files that sound like the given file. |
| `filters.sounds_like.audio_id` | string |   |
| `filters.sounds_like.min_score` | number |   |
| `filters.sound_group` | object | Keep the files of one sound group from a grouping job. |
| `filters.sound_group.job_id` | string |   |
| `filters.sound_group.group_id` | integer | Allowed: -9007199254740991 to 9007199254740991. |
| `filters.trash` | "hide" \| "include" \| "only" | Trashed files: hidden (default), included, or the only ones shown. |
| `filters.duplicates` | boolean | True keeps only files that have an identical copy. |
| `filters.artist` | array<string> |   |
| `filters.project` | array<string> |   |
| `filters.album` | array<string> |   |
| `filters.title_contains` | string |   |
| `filters.credits_contains` | string |   |
| `sort` | "uploaded_at_desc" \| "uploaded_at_asc" \| "bpm_asc" \| "bpm_desc" \| "loudness_asc" \| "loudness_desc" \| "name_asc" \| array<object> | Either one of the short names (`uploaded_at_desc`, `bpm_asc`, ...) or a list of `{ key, dir }`. Defaults to newest first. |
| `limit` | integer | Matches per page, 1 to 100. Defaults to 50. |
| `cursor` | string | The `next_cursor` of the previous page. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `items` | array<object> | The matching files. |
| `items[].audio_id` | string |   |
| `items[].original_filename` | string |   |
| `items[].display_name` | string \| null |   |
| `items[].audio_url` | string | URL to pass as `audio_url` when running an analysis on this file. |
| `items[].playback_url` | string \| null |   |
| `items[].duration_seconds` | number \| null |   |
| `items[].format` | string \| null |   |
| `items[].uploaded_at` | number | Milliseconds since the Unix epoch. |
| `items[].starred` | boolean |   |
| `items[].user_tags` | array<string> |   |
| `items[].analyzed_categories` | array<string> | Analysis categories this file has results for. |
| `items[].bpm` | number \| null |   |
| `items[].tempo_stability` | number \| null |   |
| `items[].key_label` | string \| null |   |
| `items[].key_root` | string \| null |   |
| `items[].key_mode` | string \| null |   |
| `items[].meter` | string \| null |   |
| `items[].loudness_integrated` | number \| null |   |
| `items[].loudness_range` | number \| null |   |
| `items[].true_peak_dbfs` | number \| null |   |
| `items[].pitch_lowest_midi` | number \| null |   |
| `items[].pitch_highest_midi` | number \| null |   |
| `items[].notes_per_second` | number \| null |   |
| `items[].speech_language` | string \| null |   |
| `items[].transcript_text` | string \| null |   |
| `items[].sound_description` | string \| null |   |
| `items[].auto_tags` | array<string> \| null |   |
| `items[].bpm_source` | string \| null | Where the tempo came from: your own value or the analysis. |
| `items[].key_source` | string \| null | Where the key came from: your own value or the analysis. |
| `items[].details` | object |   |
| `items[].details.title` | string \| null |   |
| `items[].details.artist` | string \| null |   |
| `items[].details.album` | string \| null |   |
| `items[].details.project` | string \| null |   |
| `items[].details.credits` | string \| null |   |
| `items[].details.has_cover` | boolean | True when the file has cover art. |
| `items[].details.content_sha256` | string \| null | Fingerprint of the file content; equal values mean identical files. |
| `items[].details.trashed_at` | number \| null | When the file was moved to the trash, in milliseconds since the Unix epoch, or null. |
| `items[].details.purge_at` | number \| null | When a trashed file is removed for good, or null. |
| `items[].similarity` | number \| null | Similarity to the `sounds_like` reference. Present only when the query uses `sounds_like`. |
| `count` | integer | Number of items in this page. Allowed: -9007199254740991 to 9007199254740991. |
| `total_matches` | integer | Number of files that match the filters. Allowed: -9007199254740991 to 9007199254740991. |
| `has_more` | boolean |   |
| `next_cursor` | string \| null | Pass this as `cursor` to get the next page. |
| `coverage` | object |   |
| `coverage.total_library_size` | integer | Files in scope before any value filter. Allowed: -9007199254740991 to 9007199254740991. |
| `coverage.indexed` | object | For each searchable value (`bpm`, `key_label`, ...), how many files in scope have it. |

### Responses

200: Success

```json
{
  "data": {
    "items": [
      {
        "audio_id": "audio_3f9c1a7d2b60",
        "original_filename": "demo-mix.wav",
        "display_name": "Demo mix",
        "audio_url": "https://api.scarleta.ai/v1/audio/media/9b2c7d1e-5a40-4f83-b6c9-0d1e2f3a4b5c.wav",
        "playback_url": null,
        "duration_seconds": 184.2,
        "format": "wav",
        "uploaded_at": 1791333870854,
        "starred": false,
        "user_tags": [
          "demo"
        ],
        "analyzed_categories": [
          "rhythm"
        ],
        "bpm": 118.5,
        "tempo_stability": null,
        "key_label": null,
        "key_root": null,
        "key_mode": null,
        "meter": null,
        "loudness_integrated": null,
        "loudness_range": null,
        "true_peak_dbfs": null,
        "pitch_lowest_midi": null,
        "pitch_highest_midi": null,
        "notes_per_second": null,
        "speech_language": null,
        "transcript_text": null,
        "sound_description": null,
        "auto_tags": null,
        "bpm_source": "analysis",
        "key_source": null,
        "details": {
          "title": null,
          "artist": null,
          "album": null,
          "project": null,
          "credits": null,
          "has_cover": false,
          "content_sha256": null,
          "trashed_at": null,
          "purge_at": null
        }
      }
    ],
    "count": 1,
    "total_matches": 1,
    "has_more": false,
    "next_cursor": null,
    "coverage": {
      "total_library_size": 42,
      "indexed": {
        "bpm": 17,
        "key_label": 9,
        "meter": 12
      }
    }
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

400: The body is not a JSON object, has an unknown field, or a filter, sort, `limit` or `cursor` is not valid. `error.details.field` names the field.

```json
{
  "error": {
    "code": "invalid_filter",
    "message": "Unknown filter \"colour\"",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_filter"
  }
}
```

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: A `sounds_like` reference file does not exist in your library.

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

409: The `sounds_like` reference file has no sound fingerprint yet.

```json
{
  "error": {
    "code": "embedding_missing",
    "message": "Run embedding analysis on this file first.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#embedding_missing"
  }
}
```

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

[`audio_not_found`](https://www.scarleta.ai/docs/errors#audio_not_found), [`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`cluster_job_not_found`](https://www.scarleta.ai/docs/errors#cluster_job_not_found), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_unavailable), [`embedding_missing`](https://www.scarleta.ai/docs/errors#embedding_missing), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_config`](https://www.scarleta.ai/docs/errors#invalid_config), [`invalid_cursor`](https://www.scarleta.ai/docs/errors#invalid_cursor), [`invalid_filter`](https://www.scarleta.ai/docs/errors#invalid_filter), [`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_limit`](https://www.scarleta.ai/docs/errors#invalid_limit), [`invalid_sort`](https://www.scarleta.ai/docs/errors#invalid_sort), [`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)

### Example

```bash
curl -X POST "https://api.scarleta.ai/v1/audio/library/query" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "bpm": {
        "min": 90,
        "max": 130
      },
      "analyzed": [
        "rhythm"
      ]
    },
    "sort": [
      {
        "key": "uploaded",
        "dir": "desc"
      }
    ],
    "limit": 25
  }'
```

## GET /v1/audio/library/view-counts

Operation id: `get_saved_view_counts`

**Count tracks in each saved view**

Returns an object that maps the id of each of your saved views to the number of tracks it currently matches. The object is empty when you have no saved views. Only your own files are ever returned.

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

### Responses

200: Success

```json
{
  "data": {
    "7d2c9e1a-4b3f-4c68-9a5d-1e0f8b7c6a52": 12
  },
  "meta": {
    "request_id": "req_0b7a3c2e-6f1d-4d9a-8e55-3a4b5c6d7e8f"
  }
}
```

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 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), [`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
curl "https://api.scarleta.ai/v1/audio/library/view-counts" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```
