API reference

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 #

CodeStatusMeaning and fix
token_missing401No token sent. Send Authorization: Bearer rsz_…
token_malformed401The header isn’t Bearer rsz_…
token_invalid401Unknown, revoked or expired token. Get a new one by device sign-in or at /settings/tokens
scope_missing403The token lacks submit or read; fix lists the scopes it has
device_token_cannot_delete_account403DELETE /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):

CodeStatusMeaning and fix
authorization_pending400The author hasn’t approved yet; keep showing the link and code and poll again after interval seconds (Retry-After)
slow_down400Polled sooner than interval; add 5 seconds to your interval
expired_token400The codes’ 10 minutes are over; start again with POST /auth/device
access_denied403The author denied it; don’t start another unless they ask
invalid_device_code400Unknown device_code, or its token was already handed out (once only)
token_limit_reached409The 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 #

CodeStatusMeaning and fix
json_required415Send Content-Type: application/json
invalid_json400The body isn’t valid JSON; detail gives the line and column
invalid_body400Unknown field (often a typo) or wrong type; detail names it
validation_failed422Fields are invalid; see errors below
invalid_idempotency_key400Use 1–255 visible ASCII characters, e.g. a UUID
idempotency_key_reused422The key was used for a different request; use a new key
request_in_progress409A request with this Idempotency-Key is still running; retry in a second for its answer
body_too_large413Bodies are small JSON; upload files with a presigned upload
not_found404No such endpoint
method_not_allowed405Wrong method for this path
invalid_kind400GET /tags?kind= takes ai, cat, platform, lang or license
invalid_status400?status= takes draft, published or history
mine_required400Only GET /projects?mine=true lists projects
invalid_query400Unknown query parameter, or a value of the wrong type; detail names it
gone404What the request was about (a project, media, your account) was deleted while it ran
invalid_period400GET /projects/{slug}/stats?period= takes 7d, 30d or all
internal_error500Our fault. Retry with the same Idempotency-Key
rate_limited42960 requests a minute per token, or per address without a valid token; wait Retry-After seconds
feedback_limited429POST /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 #

CodeStatusMeaning and fix
project_not_found404No project with that slug; add it with POST /projects
not_owner403Someone else owns the project, or it’s unclaimed and must be claimed first
repo_not_found404GitHub can’t find the repository, or it’s private
repo_private422Make the repository public first
repo_not_managed403You need admin or maintain permission on the repository
project_owned_by_other409Another account already listed the repository
github_reauth_required403Your GitHub sign-in on the website needs renewing (a new API token won’t help): open /auth/github, even when signed in
github_rate_limited503GitHub’s rate limit is used up; retry in a few minutes
github_unavailable502GitHub didn’t answer; retry
new_account_project_limit429GitHub accounts under 30 days old can add 3 projects a day
confirmation_required400Deleting is permanent: repeat the slug, DELETE /projects/{slug}?confirm={slug}, or your handle, DELETE /me?confirm={handle}

Releases #

CodeStatusMeaning and fix
release_not_found404No such tag; fix lists the known releases. New GitHub release? POST …/sync
release_is_history409Older releases can’t be edited or published, only the newest draft
edit_window_closed409Published releases and their media can be edited for 24 hours
already_published409Nothing to do
being_published409Another request is publishing it (a second or so); edits wait too. Retry soon
hero_required422Add a hero screenshot (also: a published release must keep its hero)
hero_processing422The hero is still being processed; poll GET /media/{id}
hero_failed422The hero file couldn’t be used; fix repeats its error
hero_quality422The 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.

CodeMeaning
changelog_missingWrite a real changelog (at least 40 characters of text)
patch_releasePatch releases reach followers but aren’t ranked
not_a_version_bumpNot a newer major or minor version, or a pre-release
window_takenAnother release of the project is already ranked today
account_too_newNo longer sent (the account-age gate, removed 2026-10-19)
account_unverifiedNo longer sent (the account-age gate, removed 2026-10-19)
release_too_oldReleased on GitHub more than 14 days ago
release_date_unknownGitHub has no publish date for the release
not_decidedA draft: decided when it’s published; check shows it

Media #

CodeStatusMeaning and fix
media_not_found404No such media; it may have been replaced by a newer hero
too_many_media422The carousel (9) or gallery (10) is full; delete one first
hero_locked409A published release’s hero is replaced via a passing carousel image + PATCH
hero_required409A published release keeps its hero: it can’t be deleted, demoted or moved away
hero_quality409Media made a hero by PATCH must pass every check (for a published release) and not repeat another release’s or project’s hero
edit_conflict409Another request moved the media meanwhile; GET /media/{id} and send the change again
upload_missing409PUT the file to the upload URL before …/complete
upload_spent409An 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_backlog42920 files of one account wait for processing at a time; poll until some are done
empty_file422The uploaded file is empty
file_too_large422Images can be at most 10 MB, GIFs 20 MB, terminal recordings 2 MB
not_an_upload409URL imports need no …/complete
storage_unavailable502Retry 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:

CodeMeaning
not_an_imageNot an image, often an HTML page: use the raw file URL
unsupported_formatVideo, HEIC, SVG, PDF, BMP or TIFF; use PNG, JPEG or WebP (or a GIF or terminal recording for an animation)
corrupt_imageThe file is damaged or truncated
image_too_largeMore than 16,384 px per side or 50 megapixels
empty_fileThe file is empty
file_too_largeMore than 10 MB (20 MB for a GIF, 2 MB for a terminal recording)
animated_heroAn imported hero turned out to be a GIF; a hero is a still image or a terminal recording, add the GIF as carousel
gif_unreadableThe GIF couldn’t be converted to video; export it again
gif_too_complexConverting the GIF took too much time or memory; shorten it or make it smaller
cast_invalidNot an asciicast v2 or v3 recording, or one of its lines is broken; record it again with asciinema rec
terminal_too_largeThe recorded terminal is over 240 columns × 80 rows; record in a smaller window
cast_unreadableThe recording couldn’t be rendered; record it again
cast_too_complexRendering the recording took too much time or memory; shorten it or use a smaller terminal
source_not_httpsThe URL (or a redirect) isn’t https
source_url_portThe URL (or a redirect) isn’t on the standard https port
source_private_addressThe URL points to a private or local network
source_unreachableThe download failed or timed out
source_http_errorThe URL answered an HTTP error, e.g. 404
source_too_many_redirectsMore than 3 redirects
processing_failedProcessing 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.

CheckFails when
still_imageThe image is animated
resolutionSmaller than 1200×630
aspect_ratioTaller than 4:5 or wider than 1.91:1; fix gives the crop size
sharpnessBlurry or heavily compressed
not_upscaledEnlarged from a smaller image
not_blankMostly one colour
bordersAn empty margin takes 15% or more of a side; fix gives pixels to crop
jpeg_qualityA JPEG saved below quality 75
fresh_heroLooks like the hero of one of your earlier releases
not_reusedAlready the hero of another project

Field errors #

Entries in a validation_failed problem’s errors:

FieldCodes
repo_urlinvalid_repo_url
summarysummary_length, summary_multiline, summary_emoji
descriptiondescription_too_long
homepage_urlinvalid_url
tagsunknown_tag (with a suggestion), tag_detected, ai_tag_required, ai_tag_conflict, too_many_categories, too_many_platforms
titletitle_invalid
changelogchangelog_too_long
modelstoo_many_models, unknown_provider, model_invalid, duplicate_model
source_urlmedia_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_typecontent_type_required, unsupported_media_type
filenamefilename_required, filename_mismatch
bytesbytes_required, bytes_invalid, file_too_large
rolerole_invalid, hero_must_be_still (a GIF can’t be the hero)
release_tagrelease_tag_required, gallery_has_release, release_not_found (with the draft tags)
alt_textalt_text_too_short, alt_text_too_long, alt_text_not_descriptive
positionposition_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
bodynothing_to_update