Error catalogue
Every machine-readable error code x.md can return, what it means, and how to recover from it.
Every failure answers with a structured document, never a stack trace or a generic HTML error page. It carries a stable machine code, a human detail, and a resolution telling the caller what to do next.
The shape
Errors follow RFC 9457 problem details, served as application/problem+json:
{
"type": "https://x.pcstyle.dev/docs/reliability#rate-limited",
"title": "Rate limit exceeded",
"status": 429,
"detail": "Too many live search lookups from this IP. Slow down and retry shortly.",
"instance": "https://x.pcstyle.dev/api/v1/search?q=vercel",
"code": "rate_limited",
"resolution": "Wait the number of seconds in the `Retry-After` header, then retry. Cached responses do not count against the allowance.",
"documentation_url": "https://x.pcstyle.dev/docs/reliability#errors",
"error": "Too many live search lookups from this IP. Slow down and retry shortly.",
"retry_after": 43
}
| Field | Always present | Meaning |
|---|---|---|
type |
yes | Stable URI identifying the problem type. It dereferences to documentation; it is not a lookup key. |
title |
yes | Short, stable summary of the problem type. Does not change between occurrences. |
status |
yes | The HTTP status code, repeated in the body so a logged payload is self-contained. |
detail |
yes | What went wrong with this request. Written for a human; can change between occurrences. |
instance |
yes | Absolute URL of the request that failed. |
code |
yes | The machine identifier. Switch on this. |
resolution |
yes | What the caller should do next, in one sentence. |
documentation_url |
yes | Where the codes are documented. |
error |
yes | Alias of detail, kept for clients written against the older { error, code } shape. |
retry_after |
on 429 and 503 |
Seconds to wait, mirroring the Retry-After header. |
links |
sometimes | Related routes, each { label, href }. A 404 on an unknown path uses this to suggest where to go instead. |
Branch on status for the class of failure and on code for the specific one. Do not parse detail, and do not compare title strings.
Media type
The body is application/problem+json; charset=utf-8. A caller whose Accept header names application/json — and not application/problem+json — gets the same bytes labelled application/json; charset=utf-8 instead, so a strict client-side content-type check still passes.
Headers on an error
| Header | On | Meaning |
|---|---|---|
Cache-Control: no-store |
every problem response | Errors are never stored, by us or by a CDN. The recoverable 404 document sends public, max-age=0, must-revalidate instead, so a cache must revalidate it before reusing it. |
Link: …; rel="help" |
every problem response | This documentation. |
Link: …; rel="service-desc" |
every error | /openapi.json. |
Retry-After |
429, 503 |
Seconds to wait. Takes precedence over the RateLimit reset if the two disagree. |
Allow |
405 |
The methods this route accepts. |
Deprecation, Sunset |
errors from a deprecated route | See versioning. |
The negotiated 404 is the exception: instead of rel="help" it carries the site discovery links — alternate, sitemap, describedby, service-desc, and api-catalog — and puts the recovery routes in the body’s links array.
Request errors — 400
The request never reached a provider. Fix the request; retrying it unchanged will fail identically.
| Code | Title | Meaning | What to do |
|---|---|---|---|
missing_url |
Missing url parameter | A post read arrived with no post to read. | Add ?url=<public X status URL>, or call /{handle}/status/{id} directly. |
invalid_url |
Invalid X status URL | url was not parseable as a URL. |
Pass a public x.com or twitter.com status URL in url, for example ?url=https://x.com/jack/status/20. |
unsupported_host |
Unsupported host | The URL parsed, but points somewhere other than X. | Only x.com and twitter.com status URLs are supported. Rewrite the host and retry. |
invalid_path |
Not a status permalink | An X URL that is not a status permalink, such as a profile or a search page. | Use a permalink shaped like https://x.com/{handle}/status/{id}. |
invalid_params |
Invalid handle or status id | The handle/id pair was missing or malformed. |
Provide handle (1-15 word characters) and a numeric id. |
invalid_handle |
Invalid X handle | The handle is not a possible X handle. | Handles are 1-15 characters of letters, digits, or underscores, with no leading @. |
invalid_resource |
Unsupported browse resource | resource on the legacy /api/browse alias was not one of the four. |
Use resource=profile, search, followers, or following. |
invalid_format |
Unsupported format | format was not a format this route can render. |
Use format=markdown, format=obsidian, or format=json. Browse accepts markdown or json. |
invalid_thread |
Invalid thread parameter | thread was neither a keyword nor a count in range. |
Use thread=off, full, conversation, or a number from 2 to 100. |
invalid_userinfo |
Invalid userinfo parameter | userinfo was not a known level. |
Use userinfo=off, author, or all. |
invalid_context |
Invalid context parameter | context was not a known mode. |
Use context=full or context=thread. |
invalid_replies |
Invalid replies parameter | replies was not a known ordering. |
Use replies=top, recent, or off. |
invalid_mode |
Invalid mode parameter | A view mode this route does not offer. | See the parameter tables at /docs/responses. |
missing_query |
Missing search query | A search arrived with no query, or an empty one. | Add ?q=<search terms>, for example /search?q=vercel. |
invalid_body |
Invalid JSON body | A request body was sent that is not well-formed JSON. The public read routes take no body. | Send a well-formed JSON object, or omit the body entirely. |
Authentication — 401
The public read API needs no credentials. These appear only when a credential was offered and rejected, or a private route was reached.
| Code | Title | Meaning | What to do |
|---|---|---|---|
invalid_key |
Invalid or disabled API key | An Authorization: Bearer key was present but unknown or revoked. |
Remove the Authorization header to call anonymously, or present a valid Authorization: Bearer <key>. |
unauthorized |
Unauthorized | A non-public route, such as the admin API. | This route is private. The public read-only API needs no credentials; see /openapi.json. |
Not found — 404
| Code | Title | Meaning | What to do |
|---|---|---|---|
not_found |
Not found | The post or profile does not exist, is deleted, protected, or suspended. | Confirm the post or profile is public and still exists on x.com, then retry. Deleted and protected content is never available. |
route_not_found |
API route not found | The path is not a route on this service. | Discover the public API through /api, /openapi.json, or /.well-known/api-catalog. |
A 404 is a routing dead end for an agent, so both forms carry links pointing at the routes that do exist. Follow them rather than guessing at another path.
The 404 document is the one error with more than one representation: it is negotiated from Accept, so a browser gets HTML, an agent asking for text/markdown gets the same recovery links as prose, and application/json (or ?format=json) gets the problem document above.
Method and negotiation — 405, 406
| Code | Title | Meaning | What to do |
|---|---|---|---|
method_not_allowed |
Method not allowed | Anything other than GET, HEAD, or OPTIONS on a read route. x.md never writes to X. |
Use GET, HEAD, or OPTIONS; the Allow response header lists what this route accepts. |
not_acceptable |
No acceptable representation | The Accept header excluded every representation this route can produce. On the site pages a 406 answers as text/plain listing what is on offer, since there is by definition no acceptable JSON to send. |
Request text/markdown, application/json, or text/html, or omit the Accept header. |
Limits — 429
| Code | Title | Meaning | What to do |
|---|---|---|---|
rate_limited |
Rate limit exceeded | A quota is exhausted. The response says which one in its RateLimit header, at r=0. |
Wait the number of seconds in the Retry-After header, then retry. Cached responses do not count against the allowance. |
Retry-After and retry_after are the same number of seconds. Sleep for it; do not poll. The quotas themselves are in rate limits.
Service and upstream — 5xx
x.md reads third-party providers that fail independently of it, so these are usually transient. Retry with exponential backoff and jitter; do not retry in a tight loop.
| Code | Title | Meaning | What to do |
|---|---|---|---|
internal_error |
Unexpected error | A bug or an unhandled condition in x.md itself. | Retry with exponential backoff. If it persists, open an issue at github.com/pc-style/x-md/issues. |
upstream_error |
Upstream provider error | A data provider failed or answered with something unusable. | Retry with backoff. x.md reads a third-party provider that can fail independently. |
search_unavailable |
Search temporarily unavailable | Live search has no healthy session. Photos, Videos, and Users have no fallback. | Wait the number of seconds in Retry-After, then retry. Photos, Videos, and Users need configured sessions. |
admin_unconfigured |
Admin is not configured | The deployment’s private admin route has no token set. Not part of the public API. | Set X_MD_ADMIN_TOKEN on the deployment. This route is not part of the public API. |
Retry policy in one table
| Status | Retry? | How |
|---|---|---|
400, 405, 406 |
No | Fix the request first. The same request always fails the same way. |
401 |
No | Fix or drop the credential. |
404 |
No | Follow links, or confirm the content is public. A retry does not undelete a post. |
429 |
Yes | After Retry-After seconds, exactly once per window. |
500, 502 |
Yes | Exponential backoff with jitter, a few attempts, then give up and report. |
503 |
Yes | After Retry-After seconds. search_unavailable on Photos, Videos, or Users may stay down for a while. |
The same catalogue, machine-readable
Anything on this page can be read by a program instead:
/openapi.json—x-error-cataloglists every code with its status, title, and resolution, and each operation lists the codes it can actually emit, with example bodies./api— theerrors.codesarray, in a much smaller document.
Related: errors, limits, and caching · versioning and deprecation.