# Errors

Every error code the API returns, what it means and what to do about it.

Every error is a JSON [problem](/docs/api#errors) with a stable `code` and a
`fix` sentence that says what to do next. Read `fix` first: it's written for
this exact request, often with numbers (how many pixels to crop, which tags
exist). This page lists every code.

```json
{
  "type": "/docs/errors",
  "title": "The request has invalid fields",
  "status": 422,
  "code": "validation_failed",
  "detail": "2 problems found.",
  "fix": "Fix each entry in `errors` (its `message` says how) and send the request again.",
  "errors": [
    { "field": "summary", "code": "summary_length",
      "message": "The summary is 4 characters; write one line of 10–100 characters …" },
    { "field": "tags[0]", "code": "unknown_tag",
      "message": "\"cat/clii\" isn't a known tag. Did you mean \"cat/cli\"?" }
  ]
}
```

Invalid input is reported **all at once**: one entry in `errors` per
problem, so you can fix everything in one go.

## Authentication

| Code                                 | Status | Meaning and fix                                                                                                    |
| ------------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------ |
| `token_missing`                      | 401    | No token sent. Send `Authorization: Bearer rsz_…`                                                                  |
| `token_malformed`                    | 401    | The header isn't `Bearer rsz_…`                                                                                    |
| `token_invalid`                      | 401    | Unknown, revoked or expired token. Get a new one by device sign-in or at `/settings/tokens`                        |
| `scope_missing`                      | 403    | The token lacks `submit` or `read`; `fix` lists the scopes it has                                                  |
| `device_token_cannot_delete_account` | 403    | `DELETE /me` with a token from a device sign-in, which can't delete the account; the author does it on `/settings` |

### Device sign-in

Answers of `POST /auth/device/token` until it hands out the token
([API overview](/docs/api#device-sign-in)):

| Code                    | Status | Meaning and fix                                                                                                        |
| ----------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| `authorization_pending` | 400    | The author hasn't approved yet; keep showing the link and code and poll again after `interval` seconds (`Retry-After`) |
| `slow_down`             | 400    | Polled sooner than `interval`; add 5 seconds to your interval                                                          |
| `expired_token`         | 400    | The codes' 10 minutes are over; start again with `POST /auth/device`                                                   |
| `access_denied`         | 403    | The author denied it; don't start another unless they ask                                                              |
| `invalid_device_code`   | 400    | Unknown `device_code`, or its token was already handed out (once only)                                                 |
| `token_limit_reached`   | 409    | The approving account has 20 tokens; the author revokes one on `/settings/tokens`, then poll again                     |

Starting more than 10 sign-ins an hour from one address is `429 rate_limited`.

## Requests

| Code                      | Status | Meaning and fix                                                                                                   |
| ------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------- |
| `json_required`           | 415    | Send `Content-Type: application/json`                                                                             |
| `invalid_json`            | 400    | The body isn't valid JSON; `detail` gives the line and column                                                     |
| `invalid_body`            | 400    | Unknown field (often a typo) or wrong type; `detail` names it                                                     |
| `validation_failed`       | 422    | Fields are invalid; see `errors` below                                                                            |
| `invalid_idempotency_key` | 400    | Use 1–255 visible ASCII characters, e.g. a UUID                                                                   |
| `idempotency_key_reused`  | 422    | The key was used for a different request; use a new key                                                           |
| `request_in_progress`     | 409    | A request with this `Idempotency-Key` is still running; retry in a second for its answer                          |
| `body_too_large`          | 413    | Bodies are small JSON; upload files with a presigned upload                                                       |
| `not_found`               | 404    | No such endpoint                                                                                                  |
| `method_not_allowed`      | 405    | Wrong method for this path                                                                                        |
| `invalid_kind`            | 400    | `GET /tags?kind=` takes `ai`, `cat`, `platform`, `lang` or `license`                                              |
| `invalid_status`          | 400    | `?status=` takes `draft`, `published` or `history`                                                                |
| `mine_required`           | 400    | Only `GET /projects?mine=true` lists projects                                                                     |
| `invalid_query`           | 400    | Unknown query parameter, or a value of the wrong type; `detail` names it                                          |
| `gone`                    | 404    | What the request was about (a project, media, your account) was deleted while it ran                              |
| `invalid_period`          | 400    | `GET /projects/{slug}/stats?period=` takes `7d`, `30d` or `all`                                                   |
| `internal_error`          | 500    | Our fault. Retry with the same `Idempotency-Key`                                                                  |
| `rate_limited`            | 429    | 60 requests a minute per token, or per address without a valid token; wait `Retry-After` seconds                  |
| `feedback_limited`        | 429    | `POST /feedback` takes 5 messages an hour per account, or per visitor without a token; wait `Retry-After` seconds |

A retry with the same `Idempotency-Key` and body returns the stored answer
with the header `Idempotent-Replayed: true`. Only successes are stored: a
refusal (`4xx`) changed nothing, so the retry runs again and gets the answer
that holds now.

## Projects

| Code                        | Status | Meaning and fix                                                                                                                 |
| --------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `project_not_found`         | 404    | No project with that slug; add it with `POST /projects`                                                                         |
| `not_owner`                 | 403    | Someone else owns the project, or it's unclaimed and must be claimed first                                                      |
| `repo_not_found`            | 404    | GitHub can't find the repository, or it's private                                                                               |
| `repo_private`              | 422    | Make the repository public first                                                                                                |
| `repo_not_managed`          | 403    | You need **admin** or **maintain** permission on the repository                                                                 |
| `project_owned_by_other`    | 409    | Another account already listed the repository                                                                                   |
| `github_reauth_required`    | 403    | Your GitHub sign-in on the website needs renewing (a new API token won't help): open `/auth/github`, even when signed in        |
| `github_rate_limited`       | 503    | GitHub's rate limit is used up; retry in a few minutes                                                                          |
| `github_unavailable`        | 502    | GitHub didn't answer; retry                                                                                                     |
| `new_account_project_limit` | 429    | GitHub accounts under 30 days old can add 3 projects a day                                                                      |
| `confirmation_required`     | 400    | Deleting is permanent: repeat the slug, `DELETE /projects/{slug}?confirm={slug}`, or your handle, `DELETE /me?confirm={handle}` |

## Releases

| Code                 | Status | Meaning and fix                                                                |
| -------------------- | ------ | ------------------------------------------------------------------------------ |
| `release_not_found`  | 404    | No such tag; `fix` lists the known releases. New GitHub release? `POST …/sync` |
| `release_is_history` | 409    | Older releases can't be edited or published, only the newest draft             |
| `edit_window_closed` | 409    | Published releases and their media can be edited for 24 hours                  |
| `already_published`  | 409    | Nothing to do                                                                  |
| `being_published`    | 409    | Another request is publishing it (a second or so); edits wait too. Retry soon  |
| `hero_required`      | 422    | Add a hero screenshot (also: a published release must keep its hero)           |
| `hero_processing`    | 422    | The hero is still being processed; poll `GET /media/{id}`                      |
| `hero_failed`        | 422    | The hero file couldn't be used; `fix` repeats its error                        |
| `hero_quality`       | 422    | The hero fails quality checks; `fix` lists each check's fix                    |

`publish` refuses with these codes and includes the full `report`.

### Ranking gates

These appear in the `check` and `publish` report. They don't stop
publishing; they decide whether the release is ranked.

| Code                   | Meaning                                                   |
| ---------------------- | --------------------------------------------------------- |
| `changelog_missing`    | Write a real changelog (at least 40 characters of text)   |
| `patch_release`        | Patch releases reach followers but aren't ranked          |
| `not_a_version_bump`   | Not a newer major or minor version, or a pre-release      |
| `window_taken`         | Another release of the project is already ranked today    |
| `account_too_new`      | No longer sent (the account-age gate, removed 2026-10-19) |
| `account_unverified`   | No longer sent (the account-age gate, removed 2026-10-19) |
| `release_too_old`      | Released on GitHub more than 14 days ago                  |
| `release_date_unknown` | GitHub has no publish date for the release                |
| `not_decided`          | A draft: decided when it's published; `check` shows it    |

## Media

| Code                  | Status | Meaning and fix                                                                                                                    |
| --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `media_not_found`     | 404    | No such media; it may have been replaced by a newer hero                                                                           |
| `too_many_media`      | 422    | The carousel (9) or gallery (10) is full; delete one first                                                                         |
| `hero_locked`         | 409    | A published release's hero is replaced via a passing carousel image + PATCH                                                        |
| `hero_required`       | 409    | A published release keeps its hero: it can't be deleted, demoted or moved away                                                     |
| `hero_quality`        | 409    | Media made a hero by `PATCH` must pass every check (for a published release) and not repeat another release's or project's hero    |
| `edit_conflict`       | 409    | Another request moved the media meanwhile; `GET /media/{id}` and send the change again                                             |
| `upload_missing`      | 409    | `PUT` the file to the upload URL before `…/complete`                                                                               |
| `upload_spent`        | 409    | An upload is completed twice at most, and not again after `gif_too_complex` or `cast_too_complex`; add the fixed file as new media |
| `media_backlog`       | 429    | 20 files of one account wait for processing at a time; poll until some are done                                                    |
| `empty_file`          | 422    | The uploaded file is empty                                                                                                         |
| `file_too_large`      | 422    | Images can be at most 10 MB, GIFs 20 MB, terminal recordings 2 MB                                                                  |
| `not_an_upload`       | 409    | URL imports need no `…/complete`                                                                                                   |
| `storage_unavailable` | 502    | Retry in a moment                                                                                                                  |

### Processing failures

When a file can't be used, the media's `status` is `failed` and its `error`
has a `code`, a `message` and a `fix`:

| Code                        | Meaning                                                                                                           |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `not_an_image`              | Not an image, often an HTML page: use the raw file URL                                                            |
| `unsupported_format`        | Video, HEIC, SVG, PDF, BMP or TIFF; use PNG, JPEG or WebP (or a GIF or terminal recording for an animation)       |
| `corrupt_image`             | The file is damaged or truncated                                                                                  |
| `image_too_large`           | More than 16,384 px per side or 50 megapixels                                                                     |
| `empty_file`                | The file is empty                                                                                                 |
| `file_too_large`            | More than 10 MB (20 MB for a GIF, 2 MB for a terminal recording)                                                  |
| `animated_hero`             | An imported hero turned out to be a GIF; a hero is a still image or a terminal recording, add the GIF as carousel |
| `gif_unreadable`            | The GIF couldn't be converted to video; export it again                                                           |
| `gif_too_complex`           | Converting the GIF took too much time or memory; shorten it or make it smaller                                    |
| `cast_invalid`              | Not an asciicast v2 or v3 recording, or one of its lines is broken; record it again with `asciinema rec`          |
| `terminal_too_large`        | The recorded terminal is over 240 columns × 80 rows; record in a smaller window                                   |
| `cast_unreadable`           | The recording couldn't be rendered; record it again                                                               |
| `cast_too_complex`          | Rendering the recording took too much time or memory; shorten it or use a smaller terminal                        |
| `source_not_https`          | The URL (or a redirect) isn't https                                                                               |
| `source_url_port`           | The URL (or a redirect) isn't on the standard https port                                                          |
| `source_private_address`    | The URL points to a private or local network                                                                      |
| `source_unreachable`        | The download failed or timed out                                                                                  |
| `source_http_error`         | The URL answered an HTTP error, e.g. 404                                                                          |
| `source_too_many_redirects` | More than 3 redirects                                                                                             |
| `processing_failed`         | Processing failed repeatedly; add the media again                                                                 |

### Quality checks

A processed image (`status: "ready"`) has a `quality` report. Each check
that fails has a `fix`; `hero_ok` is true only when all pass. See
[Release requirements](/docs/release-requirements) for the limits.

| Check          | Fails when                                                              |
| -------------- | ----------------------------------------------------------------------- |
| `still_image`  | The image is animated                                                   |
| `resolution`   | Smaller than 1200×630                                                   |
| `aspect_ratio` | Taller than 4:5 or wider than 1.91:1; `fix` gives the crop size         |
| `sharpness`    | Blurry or heavily compressed                                            |
| `not_upscaled` | Enlarged from a smaller image                                           |
| `not_blank`    | Mostly one colour                                                       |
| `borders`      | An empty margin takes 15% or more of a side; `fix` gives pixels to crop |
| `jpeg_quality` | A JPEG saved below quality 75                                           |
| `fresh_hero`   | Looks like the hero of one of your earlier releases                     |
| `not_reused`   | Already the hero of another project                                     |

### Field errors

Entries in a `validation_failed` problem's `errors`:

| Field                                                    | Codes                                                                                                                                                                             |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `repo_url`                                               | `invalid_repo_url`                                                                                                                                                                |
| `summary`                                                | `summary_length`, `summary_multiline`, `summary_emoji`                                                                                                                            |
| `description`                                            | `description_too_long`                                                                                                                                                            |
| `homepage_url`                                           | `invalid_url`                                                                                                                                                                     |
| `tags`                                                   | `unknown_tag` (with a suggestion), `tag_detected`, `ai_tag_required`, `ai_tag_conflict`, `too_many_categories`, `too_many_platforms`                                              |
| `title`                                                  | `title_invalid`                                                                                                                                                                   |
| `changelog`                                              | `changelog_too_long`                                                                                                                                                              |
| `models`                                                 | `too_many_models`, `unknown_provider`, `model_invalid`, `duplicate_model`                                                                                                         |
| `source_url`                                             | `media_source_required`, `media_source_conflict`, `source_url_invalid`, `source_url_insecure`, `source_url_private`, `source_url_port`, `source_url_html_page` (with the raw URL) |
| `content_type`                                           | `content_type_required`, `unsupported_media_type`                                                                                                                                 |
| `filename`                                               | `filename_required`, `filename_mismatch`                                                                                                                                          |
| `bytes`                                                  | `bytes_required`, `bytes_invalid`, `file_too_large`                                                                                                                               |
| `role`                                                   | `role_invalid`, `hero_must_be_still` (a GIF can't be the hero)                                                                                                                    |
| `release_tag`                                            | `release_tag_required`, `gallery_has_release`, `release_not_found` (with the draft tags)                                                                                          |
| `alt_text`                                               | `alt_text_too_short`, `alt_text_too_long`, `alt_text_not_descriptive`                                                                                                             |
| `position`                                               | `position_invalid`                                                                                                                                                                |
| `kind`, `message`, `name`, `email` (of `POST /feedback`) | `kind_invalid`, `message_length`, `name_too_long`, `email_invalid`                                                                                                                |
| `client_name` (of `POST /auth/device`)                   | `client_name_invalid`                                                                                                                                                             |
| `body`                                                   | `nothing_to_update`                                                                                                                                                               |
