Errors
Every error code the API returns, what it means and what to do about it.
Coding agents: read this page as markdown.
Every error is a JSON problem 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.
{
"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):
| 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 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 |