# Imports and duplicates: Scarleta API reference

Page: https://www.scarleta.ai/docs/reference/imports-and-duplicates
Base URL: https://api.scarleta.ai. Authentication: `Authorization: Bearer $SCARLETA_API_KEY`.
Operations: 4.

## POST /v1/audio/duplicates/check

Operation id: `check_duplicates`

**Check files for duplicates**

Tells you which of your files you already have, before you upload them. Send up to 1000 items, each with the SHA-256 of a file's bytes; the answer lists the items whose hash matches a file in your library (originals only, not renders). An item with no match is simply absent from `matches`. Hashes you sent at upload time are checked against the real bytes later, so a match is reliable but a brand-new upload may not match yet. Capped at 60 requests per minute per account across the upload session routes and the duplicate check; this route does 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 |
| --- | --- | --- |
| `items` | array<object> | Files to check, 1 to 1000 items. |
| `items[].ref` | string | Your own label for the file (a path or an id). Echoed back on a match. Allowed: at least 1 character. |
| `items[].sha256` | string | SHA-256 of the file bytes as 64 lowercase hexadecimal characters. |
| `items[].size_bytes` | number | Size of the file in bytes. Allowed: at least 0. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `matches` | array<object> |   |
| `matches[].ref` | string | The `ref` of the matching item you sent. |
| `matches[].audio_id` | string | Library identifier of the file you already have. |
| `matches[].file_id` | string \| null | Storage identifier of that file, when known. |
| `matches[].name` | string \| null | Display name of that file. |
| `matches[].uploaded_at` | string | When that file was uploaded, as an ISO 8601 timestamp. |
| `matches[].track_group_id` | string \| null | Identifier of the track the file is a version of, if any. |

### Responses

200: The matching items. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "matches": [
      {
        "ref": "morning-session.wav",
        "audio_id": "audio_bbb14949dc19",
        "file_id": "3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b",
        "name": "morning-session.wav",
        "uploaded_at": "2026-10-20T11:53:45.938Z",
        "track_group_id": null
      }
    ]
  }
}
```

400: `items` is empty or too long, or an item has no `ref`, a malformed `sha256` or a negative size.

```json
{
  "error": {
    "code": "invalid_items",
    "message": "items must be a non-empty array",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_items"
  }
}
```

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 check is temporarily unavailable. Retry shortly.

```json
{
  "error": {
    "code": "duplicates_unavailable",
    "message": "Couldn't check for duplicates. Try again.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#duplicates_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`duplicates_unavailable`](https://www.scarleta.ai/docs/errors#duplicates_unavailable), [`invalid_auth_format`](https://www.scarleta.ai/docs/errors#invalid_auth_format), [`invalid_items`](https://www.scarleta.ai/docs/errors#invalid_items), [`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), [`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/duplicates/check" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "ref": "morning-session.wav",
        "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "size_bytes": 1920044
      }
    ]
  }'
```

## GET /v1/audio/imports

Operation id: `list_imports`

**List imports**

Returns your imports, newest first, at most 100; there is no paging, so older imports are not returned. Add `active=1` to list only the ones still in progress (`queued` or `running`). Counts against 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 |
| --- | --- | --- | --- | --- |
| `active` | query | "1" | no | Set to `1` to list only imports that are still in progress. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `imports` | array<object> |   |
| `imports[].import_id` | string | Identifier of the import. |
| `imports[].provider` | "link" \| "google_drive" \| "dropbox" | Where the file comes from. Only `link` can be requested today. |
| `imports[].source_label` | string | The host and path of the link, without any query string. |
| `imports[].status` | "queued" \| "running" \| "completed" \| "failed" \| "rejected" | `queued` and `running` are in progress; the other three are final. |
| `imports[].audio_id` | string \| null | Library identifier of the imported file once `completed`. |
| `imports[].bytes` | number \| null | Size of the imported file in bytes once `completed`. |
| `imports[].tags` | array<string> |   |
| `imports[].error_code` | string \| null | Why the import failed or was rejected, when it did. |
| `imports[].error_message` | string \| null |   |
| `imports[].created_at` | string | ISO 8601 timestamp. |
| `imports[].updated_at` | string | ISO 8601 timestamp. |

### Responses

200: Your imports. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "imports": [
      {
        "import_id": "2b4d6f80-1a3c-4e5f-8071-92a3b4c5d6e7",
        "provider": "link",
        "source_label": "example.com/audio/morning-session.wav",
        "status": "completed",
        "audio_id": "audio_bbb14949dc19",
        "bytes": 1920044,
        "tags": [
          "morning"
        ],
        "error_code": null,
        "error_message": null,
        "created_at": "2026-10-20T11:53:45.938Z",
        "updated_at": "2026-10-20T11: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"
  }
}
```

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 list could not be read. Retry shortly.

```json
{
  "error": {
    "code": "db_unavailable",
    "message": "Database not available; try again",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#db_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/imports" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```

## POST /v1/audio/imports

Operation id: `create_import`

**Import a file from a link**

Asks Scarleta to fetch an audio file from a public link and add it to your library, so you do not have to download and re-upload it. The call returns `202` at once with the import in state `queued`; read the import (or list the active ones) until its `status` is final. A completed import carries the new file's `audio_id`. The link must be a public `http` or `https` address that serves audio; links to private or internal addresses are refused with 400 `invalid_url`, and a rejected request is still recorded in your import list with status `rejected`. Only `provider: "link"` is available today; any other provider returns 400 `unsupported_provider`. If the import service cannot start the job, the call still returns `202` with the import already `failed` (`error_code: "import_unavailable"`). Counts against 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 |
| --- | --- | --- |
| `provider` | "link" | The source type. Only `link` is available. |
| `url` | string | Public address of the audio file. |
| `tags` | array<string> | Tags to put on the imported file, at most 50 of at most 64 characters each. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `imports` | array<object> | One entry for the import just created. |
| `imports[].import_id` | string | Identifier of the import. |
| `imports[].provider` | "link" \| "google_drive" \| "dropbox" | Where the file comes from. Only `link` can be requested today. |
| `imports[].source_label` | string | The host and path of the link, without any query string. |
| `imports[].status` | "queued" \| "running" \| "completed" \| "failed" \| "rejected" | `queued` and `running` are in progress; the other three are final. |
| `imports[].audio_id` | string \| null | Library identifier of the imported file once `completed`. |
| `imports[].bytes` | number \| null | Size of the imported file in bytes once `completed`. |
| `imports[].tags` | array<string> |   |
| `imports[].error_code` | string \| null | Why the import failed or was rejected, when it did. |
| `imports[].error_message` | string \| null |   |
| `imports[].created_at` | string | ISO 8601 timestamp. |
| `imports[].updated_at` | string | ISO 8601 timestamp. |

### Responses

202: The import is queued. The body is `{ data }` without `meta`.

```json
{
  "data": {
    "imports": [
      {
        "import_id": "2b4d6f80-1a3c-4e5f-8071-92a3b4c5d6e7",
        "provider": "link",
        "source_label": "example.com/audio/morning-session.wav",
        "status": "queued",
        "audio_id": null,
        "bytes": null,
        "tags": [
          "morning"
        ],
        "error_code": null,
        "error_message": null,
        "created_at": "2026-10-20T11:53:45.938Z",
        "updated_at": "2026-10-20T11:53:45.938Z"
      }
    ]
  }
}
```

400: The provider is not supported, the link is empty, malformed, or points to a private or unsafe address, or `tags` is invalid.

```json
{
  "error": {
    "code": "invalid_url",
    "message": "That link can't be imported.",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#invalid_url"
  }
}
```

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 import could not be recorded. Retry shortly.

```json
{
  "error": {
    "code": "db_unavailable",
    "message": "Database not available; try again",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#db_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_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), [`invalid_url`](https://www.scarleta.ai/docs/errors#invalid_url), [`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), [`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), [`unsupported_provider`](https://www.scarleta.ai/docs/errors#unsupported_provider)

### Example

```bash
curl -X POST "https://api.scarleta.ai/v1/audio/imports" \
  -H "Authorization: Bearer $SCARLETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "link",
    "url": "https://example.com/audio/morning-session.wav",
    "tags": [
      "morning"
    ]
  }'
```

## GET /v1/audio/imports/{importId}

Operation id: `get_import`

**Get an import**

Returns one import. Poll it after creating the import until `status` is `completed`, `failed` or `rejected`. Only imports created by your own account can be read; any other id returns 404. Counts against 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 |
| --- | --- | --- | --- | --- |
| `importId` | path | string | yes | The `import_id` returned when the import was created. |

### Response fields (inside `data`)

| Field | Type | Description |
| --- | --- | --- |
| `import_id` | string | Identifier of the import. |
| `provider` | "link" \| "google_drive" \| "dropbox" | Where the file comes from. Only `link` can be requested today. |
| `source_label` | string | The host and path of the link, without any query string. |
| `status` | "queued" \| "running" \| "completed" \| "failed" \| "rejected" | `queued` and `running` are in progress; the other three are final. |
| `audio_id` | string \| null | Library identifier of the imported file once `completed`. |
| `bytes` | number \| null | Size of the imported file in bytes once `completed`. |
| `tags` | array<string> |   |
| `error_code` | string \| null | Why the import failed or was rejected, when it did. |
| `error_message` | string \| null |   |
| `created_at` | string | ISO 8601 timestamp. |
| `updated_at` | string | ISO 8601 timestamp. |

### Responses

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

```json
{
  "data": {
    "import_id": "2b4d6f80-1a3c-4e5f-8071-92a3b4c5d6e7",
    "provider": "link",
    "source_label": "example.com/audio/morning-session.wav",
    "status": "completed",
    "audio_id": "audio_bbb14949dc19",
    "bytes": 1920044,
    "tags": [
      "morning"
    ],
    "error_code": null,
    "error_message": null,
    "created_at": "2026-10-20T11:53:45.938Z",
    "updated_at": "2026-10-20T11: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 import with this id in your account.

```json
{
  "error": {
    "code": "import_not_found",
    "message": "Import not found",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#import_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: The import could not be read. Retry shortly.

```json
{
  "error": {
    "code": "db_unavailable",
    "message": "Database not available; try again",
    "request_id": "req_5b1c0f3e-2d41-4a8e-9a57-0c1b2d3e4f50",
    "docs_url": "https://www.scarleta.ai/docs/errors#db_unavailable"
  }
}
```

### Error codes

[`auth_unavailable`](https://www.scarleta.ai/docs/errors#auth_unavailable), [`db_unavailable`](https://www.scarleta.ai/docs/errors#db_unavailable), [`import_not_found`](https://www.scarleta.ai/docs/errors#import_not_found), [`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
IMPORTID="YOUR_IMPORTID"
curl "https://api.scarleta.ai/v1/audio/imports/$IMPORTID" \
  -H "Authorization: Bearer $SCARLETA_API_KEY"
```
