# Errors

> Error format and common error codes

Source: https://platform.snapany.com/docs/errors

All errors share one JSON shape:

```json
{ "message": "human readable message", "code": "machine_code", "detail": "optional detail" }
```

## Common codes

| HTTP | Code                   | Meaning                                     | Fix                                                                                                               |
| ---- | ---------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| 400  | _(varies)_             | Business failure                            | Handle by the `code` field (extraction codes below) — these calls are **not charged**                             |
| 401  | `invalid_api_key`      | Missing, malformed or deleted key           | Check the `Authorization: Bearer sk_snapany_xxx` header and the key status in [Console → API Keys](https://platform.snapany.com/console/keys) |
| 402  | `insufficient_credits` | Balance too low                             | Top up in the [Console](https://platform.snapany.com/console), or enable auto top-up                                                          |
| 404  | —                      | Resource not found (e.g. an invalid job id) | Check the resource `id` returned by the API                                                                       |
| 422  | —                      | Invalid request parameters                  | The `detail` field says which parameter failed and why                                                            |
| 429  | `rate_limited`         | Over 1200 requests/min                      | Back off and retry after a delay                                                                                  |
| 500  | —                      | Server error                                | Retry later; contact us if it persists                                                                            |

## Extraction error codes

Extraction failures return HTTP `400` with a code from the extraction engine. These are business failures — **no credits are charged**.

| Code                        | Common cause                                                 | What to do                                                                |
| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
| `invalid_url`               | Malformed link                                               | Fix the URL format                                                        |
| `unsupported_url`           | A valid URL, but not a post/video link                       | Use the share link of a single post or video                              |
| `unsupported_site`          | The site is not supported, or not supported by this endpoint | Check which sites the endpoint accepts, or contact us to request the site |
| `no_subtitles`              | The video has no subtitle tracks (HTTP 404)                  | Fall back to [Transcription](https://platform.snapany.com/docs/transcription)                         |
| `invalid_playlist_url`      | Not a channel/profile/playlist link                          | Use a public channel, profile or playlist URL                             |
| `playlist_not_supported`    | A playlist URL was sent to the single-post endpoint          | Use `/extract/playlist`, or pass one post URL                             |
| `content_deleted`           | The post was deleted or never existed                        | Nothing to extract                                                        |
| `user_not_found`            | Account deleted, renamed or restricted                       | Verify the handle                                                         |
| `no_story`                  | The account has no active stories                            | Stories expire after 24h                                                  |
| `private_content`           | Private or follower-only content                             | Not extractable                                                           |
| `members_only_content`      | Paid or members-only content                                 | Not extractable                                                           |
| `age_restricted`            | The media is age-restricted                                  | Not extractable (see note below)                                          |
| `region_restricted`         | The media is region-restricted                               | Not extractable (see note below)                                          |
| `not_premiered`             | Scheduled content that has not gone live                     | Retry after the publish time                                              |
| `live_stream_not_supported` | Live streams on this site aren't supported yet               | Retry once archived as a normal video (see note below)                    |
| `extract_failed`            | Site changes, or the content is currently unreachable        | Retry later (see note below)                                              |
| `retryable`                 | Transient failure during extraction                          | Retry after a short delay (see note below)                                |
| `timeout`                   | The extraction took too long                                 | Retry after a short delay (see note below)                                |
| `unknown`                   | Unclassified failure                                         | Retry once (see note below)                                               |

> `extract_failed`, `retryable`, `timeout` and `unknown` are **transient** — retrying the same URL later can succeed. Every other code is a settled conclusion about the content itself, so retrying will not change the outcome. Codes get added over time, so treat any code you don't recognise as permanent and log it rather than retrying in a loop. If one site keeps returning `extract_failed`, or `unknown` shows up repeatedly, contact us with the request time.

> `age_restricted`, `region_restricted` and `live_stream_not_supported` only come up on **a small number of sites**. Age- and region-restricted media usually extracts fine, and live streams are supported on many sites — Twitch and TikTok among them. Getting one of these three means we can't reach that kind of media on **that particular site**; it does not mean we don't support age-restricted media, region-restricted media or live streams.

## 403 Forbidden when downloading YouTube media

This `403` does not come from the SnapAny API: extraction succeeded, but downloading the returned YouTube `video_url` / `audio_url` / `resource_url` from your own server fails. YouTube often refuses downloads from data-center IPs (cloud servers, VPS), and some links only download from US IPs — sending the media `headers` doesn't change that.

Use [YouTube Proxy Download](https://platform.snapany.com/docs/youtube-proxy-download) instead: it returns the same response plus proxy URLs for variants up to 1080p, which download through our network rather than from YouTube directly. If a proxy URL returns `404` (`payload_expired`), call the endpoint again; a `403` (`upstream_error`) is rare and a single retry usually succeeds, but call the endpoint again if it persists.

## Transcription request errors

These come back from the submit call (HTTP `400`), before a job is created — nothing is charged.

| Code                       | Common cause                                                                                                                                          |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file_too_large`           | Upload over 512 MB, or the file at `file_url` exceeds the limit                                                                                       |
| `audio_too_long`           | Audio longer than 6 hours                                                                                                                             |
| `unsupported_audio_format` | Audio format not in the supported list, or the duration can't be read                                                                                 |
| `download_failed`          | We could not fetch `file_url`                                                                                                                         |
| `unsupported_language`     | `language` is not a value our provider accepts — omit the field to auto-detect, or pass a language name / ISO code (`english`, `en`, `chinese`, `zh`) |

## Transcription failures

A failed job shows `"status": "failed"` with an `error` field when polling, and the reserved credits are refunded automatically.
