Skip to content
x.md
Esc
navigateopen⌘Jpreview
On this page

Errors, limits, and caching

What a failure looks like, how much room a caller has left, and when a response came from cache.

Errors

Every failure answers with an RFC 9457 problem document, served as application/problem+json. It carries a stable machine code, a human detail, and a one-sentence resolution.

{
  "type": "https://x.pcstyle.dev/docs/reliability#invalid-handle",
  "title": "Invalid X handle",
  "status": 400,
  "detail": "Invalid X handle",
  "instance": "https://x.pcstyle.dev/api/v1/profiles/not%20a%20handle",
  "code": "invalid_handle",
  "resolution": "Handles are 1-15 characters of letters, digits, or underscores, with no leading @.",
  "documentation_url": "https://x.pcstyle.dev/docs/reliability#errors",
  "error": "Invalid X handle"
}

Branch on status for the class of failure and on code for the specific one. error is an alias of detail, kept for clients written against the older { error, code } shape. Each problem’s type is a stable URI that points back into this documentation.

Status Typical cause What to do
400 Missing query, invalid handle, unsupported parameter value Fix the request. Retrying it unchanged fails identically.
401 An API key was offered and rejected Drop the header to call anonymously. The public API needs no credentials.
404 Post or profile missing, deleted, protected, or an unknown path Follow the links in the body. Check the public source URL on x.com.
405 A write method on a read-only route Use GET, HEAD, or OPTIONS; read Allow.
406 An Accept header that excludes every representation Ask for text/markdown, application/json, or text/html.
429 A quota is exhausted Sleep for Retry-After seconds, then retry once.
502, 503 Provider failure, or live search has no healthy session Retry with backoff and jitter, not in a loop.
500 A bug in x.md Retry with backoff; if it persists, open an issue.

The full error catalogue lists every machine code with its meaning and its recovery step.

Rate limits

Limits exist so the service stays responsive, not to meter usage: it is free and there is nothing to buy. Every response says how much room is left, so a client can pace itself instead of discovering the limit by hitting it.

Policy Quota Window Keyed by Applies to
api-ip 600 60s client IP Every public API route.
search-ip 5 60s client IP Live search lookups by anonymous callers. Charged only when a search misses the cache and reaches a provider.
search-key 30 60s API key The same burst gate for a key holder.
account-ip 10 900s client IP Account-backed feeds (Photos, Videos, Users, and the Latest/Top live fallback), drawn from a shared public pool.
account-key the key’s own 900s API key A key holder’s account-backed allowance, instead of the public one.

Cache hits are free: a response served from the application cache never reaches a counter. Only the policies that apply to the route you called are advertised, so a profile read reports api-ip alone and a search reports three.

Response headers

Two forms go out together on every measured response — success, error, and cache miss alike.

RateLimit-Policy and RateLimit are the IETF structured fields: a comma-separated list of named policies, q = quota, w = window seconds, r = units remaining, t = seconds until that policy’s window resets.

RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset are the older single-value form that most clients already read. They describe the tightest policy — the one closest to exhaustion — because they can only describe one.

A search from an anonymous caller with a full allowance:

RateLimit-Policy: "api-ip";q=600;w=60, "search-ip";q=5;w=60, "account-ip";q=10;w=900
RateLimit: "api-ip";r=599;t=60, "search-ip";r=5;t=60, "account-ip";r=10;t=900
RateLimit-Limit: 5
RateLimit-Remaining: 5
RateLimit-Reset: 60

Read that as: 599 requests left this minute overall, but only 5 uncached searches, and 10 account-backed lookups left in the current 15-minute window. Pace against search-ip, the one the compatibility triple picked out.

The same caller after spending the search burst:

RateLimit: "api-ip";r=594;t=43, "search-ip";r=0;t=17, "account-ip";r=10;t=900
RateLimit-Limit: 5
RateLimit-Remaining: 0
RateLimit-Reset: 17
Retry-After: 17
Cache-Control: no-store

The response is 429 with a rate_limited problem document. Retry-After and the exhausted policy’s t are the same number of seconds; if they ever disagree, Retry-After wins. Sleep for it — do not poll, and do not retry on a shorter interval hoping for a different answer. A 503 from live search carries Retry-After the same way.

The /api/* routes list all five, plus Retry-After, in Access-Control-Expose-Headers, so a browser client can read them cross-origin.

Cached responses lie about quota

x.md’s successful responses are cacheable, and a CDN replays the origin’s headers verbatim. On a cache hit those RateLimit values were computed for whoever caused the cache to fill, minutes ago, possibly from another IP.

Ignore RateLimit on any response with a positive Age header, which is what the specification tells clients to do. X-Cache and x-vercel-cache say the same thing in x.md’s own terms. Rejections are never cached — a 429 carries Cache-Control: no-store — so a 429 you receive is always about you.

If the shared counter store is unreachable, the front door fails open and reports a full quota — honest about the throttling actually in effect, which in that window is none. Account-backed search is the exception: it fails closed and answers 429 rather than risk overspending the shared pool.

Caching

Successful responses cache for about one hour by default. Degraded search results are rechecked after about one minute.

nocache=true skips x.md’s application cache. It cannot bypass a provider’s own cache. Use it when freshness matters, rather than on every request — a cached read is faster and costs you no search allowance.

X-Cache reports the application cache status and X-Source the provider that served the result.

Incomplete results

Latest and Top search can fall back to web-indexed snippets. Check degraded or X-Search-Degraded; snippets may be truncated, contain no metrics, and have different ordering. Photos, Videos, and Users require live search.

Public data and privacy

x.md is read-only. It does not post, follow, or like on your behalf. Public X Lists and private posts are not supported. Successful responses may be cached and served to other callers requesting the same resource.

Related: error catalogue · versioning and deprecation · response formats.

Last updated on September 8, 2026

Was this page helpful?