# Caching, redirects and errors > Response headers, ETag revalidation, CORS limits, redirects for renames and trailing slashes, and what a 404 or 405 looks like. URL: https://gpuninja.com/docs/api/caching-and-errors Last updated: 2026-10-05 ## Response headers Every API response is a static file served by GPU Ninja's CDN. A successful response looks like this: ``` HTTP/2 200 content-type: application/json cache-control: public, max-age=300 access-control-allow-origin: * etag: "…" strict-transport-security: max-age=63072000; includeSubDomains x-content-type-options: nosniff referrer-policy: strict-origin-when-cross-origin content-security-policy: default-src 'self'; … x-frame-options: DENY ``` The `Content-Security-Policy` and `X-Frame-Options` headers are on every response, because one rule covers the whole site; they are meant for the web pages, and clients ignore them on JSON and text. | Files | `Cache-Control` | `Content-Type` | | ---------------------------------------- | ------------------------------------ | --------------------------- | | Everything under `/api/v1/` | `public, max-age=300` | `application/json` | | Every `.txt` file, including `/llms.txt` | `public, max-age=300` | `text/plain; charset=utf-8` | | Feeds and `/sitemap.xml` | `public, max-age=300` | the feed's own type | | Web pages | `public, max-age=0, must-revalidate` | `text/html` | The API never sets cookies, and GPU Ninja sets no `Vary` header. There is no `CDN-Cache-Control` header and no cache rule in front of the site, so a deploy is visible as soon as your copy's five minutes are up. ## Caching and freshness A response may be up to five minutes older than the latest deploy, because of `max-age=300`. Every payload carries absolute timestamps (`asOf`, `retrievedAt`, `lastVerifiedAt`, `mergedAt`), so a cached copy is never wrong about how old its data is, only about whether something newer has been published since. ### Revalidating with the ETag Each response has an `ETag`. Send it back in `If-None-Match`; if the file hasn't changed, you get `304 Not Modified` with no body: ```bash curl -sI https://gpuninja.com/api/v1/index curl -sI -H 'If-None-Match: ""' https://gpuninja.com/api/v1/index ``` ``` HTTP/2 304 cache-control: public, max-age=300 ``` - The `ETag` is derived from the file's content, so an unchanged file keeps its `ETag` across deploys and still revalidates with a `304` after one. - The extensionless URL and the `.json` URL share one `ETag`. - The `ETag` may arrive in weak form (`W/"…"`). Send it back as you received it; either form is accepted. - Send one `ETag` per request. Don't rely on a list of tags or on `If-None-Match: *`. - A `304` carries the same headers as the `200` would, including `Cache-Control`, so it renews your copy for another five minutes. - There is no `Last-Modified` header, so use `If-None-Match` rather than `If-Modified-Since`. ## CORS Every response, including redirects and `404`s, has `Access-Control-Allow-Origin: *`. Nothing on the site is authenticated and no credentials are involved, so any origin can read the API from the browser. > Important: > Only CORS simple requests work. The CDN answers `OPTIONS` with `405`, so any request that needs a preflight fails. From browser code, make a plain `GET` with no custom headers. Setting `If-None-Match` or any other non-simple header yourself triggers a preflight; the browser's own HTTP cache revalidates without one, so let it do that. Server-side clients, such as curl, Node or a Worker, aren't subject to CORS and can send any headers. ## Redirects | Request | Response | | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | An old slug after a rename, such as `/api/v1/boards/{old-slug}` | `301` to the new slug's URL. The `.json` form, the compatibility files, and the web page and its `.txt` redirect the same way. The query string is kept | | An API path with a trailing slash, such as `/api/v1/boards/{slug}/` | `308` to the same path without the slash | | A web page's `.html` file, such as `/docs.html` | `308` to the extensionless page URL | | Any path on `www.gpuninja.com` or `gpuninja.pages.dev` | `301` to the same path on `gpuninja.com`, query string kept | A rename redirect always points at the final URL, never at another redirect. A redirect is cached for the same time as its source path: five minutes under `/api/` and for `.txt` files, and revalidated on every request for web pages. Redirects are never cached indefinitely, so one can be withdrawn. Follow redirects in your client (`curl -L`). If you store slugs, update a stored slug when you see a `301` for it; the changes feed (https://gpuninja.com/docs/api/changes) also reports entities by their current slug. ## Errors ### 404 Not Found A path with no file returns `404` with `Cache-Control: no-store` and `Access-Control-Allow-Origin: *`. The body is the website's HTML "Page not found" page, even under `/api/`, so rely on the status code and don't parse the body. Under a `.txt` path the same HTML body is labelled `text/plain; charset=utf-8`. Common causes: - Wrong case. Paths are lowercase and case-sensitive. - A page past the end. Read `pageCount` from page 1; later pages don't exist. There is also no `/page/1`: page 1 is the collection URL. - A platform or benchmark page. Platforms and benchmarks are in the API but have no web page and no `.txt`; their `links.page` and `links.text` are `null`. - A removed entity. Discontinued hardware is never removed, but a record entered in error can be. Its last change appears in the changes feed (https://gpuninja.com/docs/api/changes) with `op: "removed"`. For up to about a week after a deploy, the hosting platform may still serve a removed file with a `200` from its own cache, so treat the changes feed as the record of what was removed. ### 405 Method Not Allowed Only `GET` and `HEAD` are supported. `POST`, `OPTIONS` and every other method return `405`, with `Access-Control-Allow-Origin: *` and the path's usual `Cache-Control`. Redirects are checked first, so a request of any method to a renamed slug or a path with a trailing slash gets its `301` or `308` instead. ### 5xx A server error comes from the hosting platform before any of GPU Ninja's headers are applied, so it carries no CORS header and no cache policy. Retry later with backoff. ## Query strings Query parameters are ignored everywhere on the API. `?page=2` returns page 1, and filter or sort parameters do nothing. Page through collections by path, and filter the search index (https://gpuninja.com/docs/api/search) on your side or ask through the MCP servers (https://gpuninja.com/docs/mcp). ## Being a good citizen There is no API key and no rate limit on `gpuninja.com`, and every response is cacheable. In return, please: - cache what you download and revalidate with `If-None-Match` rather than re-downloading; - poll no more often than every five minutes, since nothing changes faster than that; - read the search index and the changes feed instead of crawling every entity on a schedule.