Public API
Last updated
Overview#
GPU Ninja publishes its whole catalogue as a read-only JSON API. Every response is a static file, rebuilt and uploaded on each deploy, so there is no server to query and nothing to sign up for.
- Base URL:
https://gpuninja.com/api/v1/ - Format: JSON only, served as
application/json. - Authentication: none. There is no API key and no account.
- Rate limit: none. Please be reasonable: cache responses, revalidate with the
ETag, and download the compact search index rather than crawling every entity. - Methods:
GETandHEAD. Every other method, includingOPTIONS, returns405. - Licence: the data is CC BY 4.0. Use it for anything, including commercial apps, and credit GPU Ninja. The OpenAPI document is Apache 2.0.
Sample data for now. Until the research pipeline publishes the real catalogue, the API serves GPU Ninja's small development dataset: a few chips, graphics cards, cases, models, motherboards and risers, with sample offers. Its prices and stock are illustrative, not quotes. The URLs, shapes and rules on this page are the real contract and won't change when the real data arrives.
There is no server-side filtering. Filter on your side over the compact search index (see Search), or ask through the MCP servers, which do it for AI assistants. The buyer's guide does the same on the website. All of them use one open-source search library that you can run yourself.
Quick start#
Fetch the first page of graphics cards:
curl -s https://gpuninja.com/api/v1/boardsFetch one card, then the cases it fits and the AI models it can run:
curl -s https://gpuninja.com/api/v1/boards/asus-rog-strix-4090
curl -s https://gpuninja.com/api/v1/boards/asus-rog-strix-4090/fits
curl -s https://gpuninja.com/api/v1/boards/asus-rog-strix-4090/can-runDownload the compact search index and the last 30 days of changes:
curl -s https://gpuninja.com/api/v1/index
curl -s https://gpuninja.com/api/v1/changesAdd -L if you want curl to follow redirects, which matters when a slug has been renamed.
Endpoints#
{collection} is one of chips, boards, chassis, models, platforms, benchmarks, motherboards, risers or sockets. {slug} is a lowercase kebab-case identifier of at most 80 characters, such as asus-rog-strix-4090; page, index and 404 are never slugs. {n} is a page number of 2 or more, and {month} a calendar month in UTC, YYYY-MM.
| Path | Returns |
|---|---|
/api/v1/{collection} | Page 1 of a collection, 50 items per page |
/api/v1/{collection}/page/{n} | Page n of a collection |
/api/v1/{collection}/{slug} | One entity in full |
/api/v1/boards/{slug}/fits | Every case, with a fit verdict for this card |
/api/v1/chassis/{slug}/fits | Every graphics card, with a fit verdict for this case |
/api/v1/boards/{slug}/can-run | AI models that fit in this card's VRAM |
/api/v1/models/{slug}/runs-on | Graphics cards with enough VRAM for this model |
/api/v1/index | The compact search index of chips, boards, chassis, models, motherboards and risers |
/api/v1/changes | Every data change merged in the last 30 days, and a list of the monthly archives |
/api/v1/changes/{month} | Every data change merged in that month, for each month that has one |
/api/v1/openapi.json | The OpenAPI 3.1.1 description of every path and schema (see OpenAPI and generated clients) |
Field-by-field detail is in the pages of this section: Collections and entities, Compatibility, Search index, Changes feed and Caching, redirects and errors.
URL rules#
- Two forms, one resource. Every resource answers both without an extension and with
.json:/api/v1/boards/asus-rog-strix-4090and/api/v1/boards/asus-rog-strix-4090.jsonreturn the same bytes and the sameETag. The OpenAPI document is the exception: it is only at/api/v1/openapi.json. - Lowercase and case-sensitive. Paths are lowercase.
/API/V1/BOARDSis a404; nothing folds case for you. - No trailing slash. An API path with a trailing slash redirects with
308to the same path without it. - Query parameters are ignored.
/api/v1/boards?page=2and/api/v1/boards?vram=16return exactly what/api/v1/boardsreturns. There is no server-side filtering, sorting or field selection. - Pagination is by path. Page 1 is the collection URL itself, and later pages are
/page/2,/page/3and so on, 50 items per page. There is no/page/1, and pages beyondpageCountdon't exist. Each page links tofirst,last,prevandnext. - Renames are
301s. If an entity's slug changes, every old URL redirects permanently to the new one: the API document and its compatibility files (with or without.json), and the web page and its.txt. The query string is kept. Each redirect points straight at the final URL, so you never follow a chain. - 404s are HTML. A path that doesn't exist returns status
404with the site's HTML error page as the body, even under/api/. Check the status code; don't try to parse the body.
Response conventions#
Every JSON file except openapi.json carries the same envelope: apiVersion (always "v1"), kind (what the document is, such as "board", "collection" or "board-fits"), asOf, and links.
- Nothing is optional. Every property is always present. An unknown value is
null, never a missing key, an empty string or0; a list with nothing in it is[]. - Property names are camelCase, and enumerated values are kebab-case (
does-not-fit,board-can-run). - Units are in the field name.
lengthMmis millimetres,tdpWwatts,vramGbgigabytes,factoryBoostClockMhzmegahertz,memoryBusWidthBitsbits,parametersBbillions of parameters,contextLengthtokens,memoryBandwidthGbpsdecimal gigabytes per second,fp32Tflopstrillions of floating-point operations per second andint8Topstrillions of integer operations per second.slotWidthandmaxGpuSlotsare expansion slots and may be fractional, such as3.5. - Timestamps are absolute. Instants are UTC at whole-second precision with a
Zsuffix, such as2026-10-04T06:12:00Z. Calendar dates areYYYY-MM-DD. Nothing in the API is relative ("3 days ago"); work that out yourself at the moment you display it. - Money.
lowestPriceUsdandlaunchMsrpUsdare decimal US dollars. An offer'spriceis a decimal amount in that offer's owncurrency. Amounts have at most two decimal places. - Links are absolute.
links.selfis the extensionless API URL.links.pageandlinks.textare the matching web page and its plain-text companion, ornullfor platforms, benchmarks, motherboards, risers and sockets, which have no pages.
asOf and provenance#
asOf is the most recent time any value in the file was retrieved or verified. Each entity that appears in the file contributes the timestamps of its own values and offers, and nothing else. It is never the build time, so a cached copy is honest about how old its data is. It is null when nothing in the file has a timestamp. On /api/v1/changes it is the latest merge time in the window instead, and on a monthly archive the latest merge time in that month. Freshness for individual fields lives in each entity's provenance map, which names the source URL, when the value was retrieved, when a source last confirmed it, and its verification status. See Collections and entities and Data.
Discontinued and frozen records#
Chips, boards, chassis, motherboards and risers have a status of active or discontinued. A discontinued record is a frozen snapshot: its specs, last recorded offers and benchmark results are preserved exactly as they were and never updated again, except by an explicit correction. A discontinued motherboard is frozen with its PCIe slots. Discontinued records stay in every collection, keep their URLs, and still get fit and can-run answers against newly added hardware and models. The one exception is a discontinued board, which is API only: it keeps its document at /api/v1/boards/{slug}, its place in the complete list and the search index, and it still appears in a case's fits and a model's runs-on, but it has no site page, no .txt companion and no fits or can-run document of its own. Its links.page and links.text are null, and so are compatibility.fits and compatibility.canRun. A discontinued board's or case's inStock is false and its lowestPriceUsd is null, because a frozen price is history, not a current price. Models, platforms, benchmarks and sockets are never discontinued and never freeze.
Motherboards, risers and sockets#
Three collections exist for the Solution Builder and are API only: /api/v1/motherboards, /api/v1/risers and /api/v1/sockets, with one document per entity at /api/v1/motherboards/{slug}, /api/v1/risers/{slug} and /api/v1/sockets/{slug}. They are JSON only: they have no site page and no .txt companion, so their links.page and links.text are null.
- A motherboard names its
socket(a socket summary; its summary carriessocketSlug) and lists itspcieSlotsbypositionfrom the top, each with itsphysicalLanes, itselectricalLanesand itspcieGen. It has astatusandoffers, but nolowestPriceUsdorinStock. - A riser (a riser cable or vertical-mount kit) has its cable
lengthMm, itspcieGenand itslanes, astatusandoffers, and no derived price or stock. - A socket is a reference value, not a product: its
vendor, itssegmentand themotherboardsbuilt for it. It has no status, offers or provenance.
Three chassis fields point at them: expansionSlots (the case's expansion-slot positions), verticalMountSlots (the width in slots of its vertical GPU mount, or null when it has none) and, on the detail document only, bundledRiser (the riser that ships with the case, as a riser summary, or null). Details are on Collections and entities.
Fit and can-run in brief#
A fit verdict is one of three values: fits, check or does-not-fit. A card longer than the case's clearance, or wider in slots than the case allows, is does-not-fit. A card whose length leaves between 0 and 10 mm of clearance is check, as is a pair where a length or slot count isn't recorded. Each verdict carries its reasons (one for length, one for slots), and a maintainer override replaces the dimensional answer. A PSU advisory compares the case's included power supply with the card's recommended wattage; it never changes the verdict. Can-run compares the card's VRAM with the model's minimum VRAM and lists only models that fit, with the spare VRAM as headroomGb; when either value isn't recorded, the answer is determinable: false. The full rules are on Compatibility.
Changes#
/api/v1/changes lists every change record merged in the last 30 days, newest first, with each change's operation, entity, field and before and after values; an added or removed row also carries its whole contents as values. The window is chosen by merge time, so a record that merged late still appears; use the mergedAt timestamps as a polling cursor. A client that has been offline for longer than the 30-day window can't catch up from the feed: it must re-fetch every entity it watches. Older history is in the monthly archives at /api/v1/changes/{month}, which archive lists. See Changes feed.
Plain-text companions#
The API is JSON only. Plain text lives beside the web pages instead: every chip, board, chassis and model page, every catalog page and the home page has a .txt companion at the same path plus .txt, such as /boards/asus-rog-strix-4090.txt, /boards.txt or /boards/page/2.txt; the home page's is /index.txt. The blog, docs, legal and digest pages have them too. They are served as text/plain; charset=utf-8. Platforms, benchmarks, motherboards, risers and sockets have no web page, so they have no .txt either.
Two files help you find everything else:
/llms.txtindexes the.txtcompanions for AI agents./sitemap.xmllists every web page for crawlers, and/robots.txtpoints to it.
The .txt files are built from the same data and wording as the HTML, with these conventions:
- Absolute UTC times. Each file has a
Data as of:line with an ISO timestamp, such as2026-10-04T06:12:00Z. Times in the body are written as4 Oct 2026, 06:12 UTC, and dates as12 Oct 2022. Relative times never appear, because the files are written at build time and must not age. - "Not recorded". A value that is
nullin the API appears asNot recorded. It is never left out, shown as0, or written asN/A. A file whose data has no timestamps saysData as of: unknown. - Units are always written out:
24 GB,450 W,357.6 mm,2,640 MHz,384-bit,3.5-slot,128K tokens,8Bparameters. Numbers use comma thousands separators. Prices carry their currency symbol, such as$1,999.99or€1,799.
Caching#
Every API response and every .txt file is sent with Cache-Control: public, max-age=300, so data can be up to five minutes older than the latest deploy. Each response has an ETag; send it back in If-None-Match and an unchanged file returns 304 Not Modified. An unchanged file keeps its ETag across deploys. Details, including the headers on redirects and errors, are on Caching, redirects and errors.
CORS#
Every response carries Access-Control-Allow-Origin: *, so browser code on any origin can read the API. Only simple requests work: a plain GET with no custom headers. Anything that triggers a CORS preflight fails, because the OPTIONS request returns 405. That includes setting If-None-Match yourself from browser code; let the browser's own cache revalidate instead. No credentials or cookies are involved.
Stability and versioning#
The /api/v1/ contract is frozen against breaking changes.
- Additive changes can happen at any time: a new property on a response object, a new endpoint, or a new value in an open vocabulary such as
verificationStatus. Ignore properties you don't recognise, and acceptverificationStatusvalues you don't know. - Closed vocabularies won't grow in v1. Every enumerated value is fixed, including
kind,status,op,rowOp,aspect,verdict,basisand the fit reasoncode. So arepageSize, the 10 mm check margin, the ordering rules and the fit and can-run rules. - Nothing changes shape in v1. No property is removed, renamed or retyped, and no field that is never
nullbecomes nullable. - Breaking changes ship as
/api/v2/, beside v1, with their own search index and OpenAPI document. v1 keeps being served until its traffic, measured at the edge, stops. A new version will be announced on this page.
The contract has a semantic version, 1.0.0 at launch: the minor number rises with each additive change and the patch number with editorial ones. It is the info.version of the OpenAPI document. The search index carries its own formatVersion, described under Search.
Search#
The compact search index at /api/v1/index holds one flat row per chip, board, chassis, model, motherboard and riser, with the fields most searches need. Download it once, cache it, revalidate it with its ETag, and filter it on your side. Platforms and benchmarks aren't in it. Sockets aren't either: there are few of them, and /api/v1/sockets lists them all.
Every entry has the same properties; a property that doesn't apply to its kind is always null (or [] for pciDeviceIds):
| Field | chip | board | chassis | model | motherboard | riser |
|---|---|---|---|---|---|---|
slug, kind, name, status | Yes | Yes | Yes | Yes (status is always active) | Yes | Yes |
vendor | Designer | Board vendor | Vendor | Publisher | Vendor | Vendor |
chipSlug | null | The board's chip | null | null | null | null |
vramGb | Maximum VRAM | VRAM fitted | null | Minimum VRAM | null | null |
tdpW | Reference TDP | Board TDP | null | null | null | null |
lengthMm | null | The card's length | null | null | null | Cable length |
slotWidth | null | The card's slot width | null | null | null | null |
lowestPriceUsd, inStock | null | From offers | From offers | null | null | null |
pciVendorId, pciDeviceIds | PCI IDs | null, [] | null, [] | null, [] | null, [] | null, [] |
socket | null | null | null | null | Its socket's slug | null |
pcieGen | null | null | null | null | null | PCIe generation |
speedTokPerSec | null | Tokens per second on the speed reference | null | null | null | null |
vramType, recommendedPsuW, widthMm | null | Memory type, recommended PSU, height from the slot | null | null | null | null |
powerConnectors | null | [{ "type", "count" }], [] when slot-powered | null | null | null | null |
A case's clearances are deliberately left out of the index, so a "length at most" filter can never match a case; ask the compatibility endpoints instead. A board entry doesn't repeat its chip's vendor and name: read them from the chip entry whose slug is the board's chipSlug. Entries are ordered by kind (chip, board, chassis, model, motherboard, riser), then slug.
The envelope adds four fields to apiVersion, kind and asOf:
formatVersionis the index format asMAJOR.MINOR,1.0at launch. The major number equals the API version; the minor number rises when the index gains a property. Accept any1.x.datasetHashissha256:plus 64 hex digits. It changes exactly when some published value changes, so you can compare it with a copy you hold.speedReferencenames whatspeedTokPerSecmeasures: the benchmarkllama-cpp-tg128on the modelllama-3-1-8b-instruct-q4-k-m.countsgives the number of entries of each kind.
The same search library is used by the website, the MCP servers and the desktop app, and it is open source in the gpuninja-mcp repository. Its limits and defaults are:
| Search fact | Value | Meaning |
|---|---|---|
LIMITS.defaultLimit | 20 | Results per page when you don't set limit |
LIMITS.maxLimit | 1000 | The largest limit |
LIMITS.maxTextLength | 200 | Characters of search text |
LIMITS.maxTextTokens | 16 | Words of search text |
LIMITS.maxSortKeys | 3 | Sort keys per query |
LIMITS.maxFilterDepth | 8 | Nesting depth of a filter |
LIMITS.maxFilterNodes | 64 | Nodes in a filter |
LIMITS.maxInValues | 10000 | Values in one in filter |
LIMITS.maxIssues | 20 | Validation issues reported for one query |
DEFAULT_SORT.withText | [{"by":"relevance","dir":"desc"},{"by":"name","dir":"asc"}] | Default order when there is search text |
DEFAULT_SORT.withoutText | [{"by":"name","dir":"asc"}] | Default order without search text |
SORT_FIELDS.chip | ["relevance","name","vramGb","tdpW"] | Sortable fields for chips |
SORT_FIELDS.board | ["relevance","name","lowestPriceUsd","vramGb","tdpW","lengthMm","slotWidth","speedTokPerSec","pricePerVramGb","recommendedPsuW","widthMm","inStock"] | Sortable fields for boards |
SORT_FIELDS.chassis | ["relevance","name","lowestPriceUsd","inStock"] | Sortable fields for cases |
SORT_FIELDS.model | ["relevance","name","vramGb"] | Sortable fields for models |
SORT_FIELDS.motherboard | ["relevance","name"] | Sortable fields for motherboards |
SORT_FIELDS.riser | ["relevance","name","lengthMm","pcieGen"] | Sortable fields for risers |
includeDiscontinued | false | Discontinued entries are left out unless you ask for them |
pricePerVramGb is worked out by the library from each board's lowestPriceUsd and vramGb; it is not in the index. Every order ends with slug ascending as a final tie-break. See Search index for text matching, filters and worked examples.
OpenAPI and generated clients#
https://gpuninja.com/api/v1/openapi.json is an OpenAPI 3.1.1 document with every path above and every response schema, as JSON Schema 2020-12. It is licensed Apache 2.0, so you may generate, publish and sell clients made from it; the data they fetch stays CC BY 4.0.
What the document tells a generator:
- One
GETper path, each with a stableoperationIdsuch aslistBoards,listBoardsPage,getBoard,getBoardFits,getModelRunsOn,getSearchIndex,getChangesandgetChangesArchive. - One tag per collection, plus
searchandchanges, so generators that group operations by tag produce aBoardsApi, aChipsApiand so on. - No authentication.
securityis an empty list, so a generated client sends no credentials. - One server,
https://gpuninja.com. Point a client at another origin only for testing. 200has a JSON schema;404has none, because the body is the site's HTML error page. Treat a404as "no such resource" and don't parse it.pageis never a slug./api/v1/boards/page/{page}and/api/v1/boards/{slug}/fitshave the same shape, and some linters flag them as ambiguous. They aren't:page,indexand404are reserved and never used as slugs.
For example, TypeScript types with openapi-typescript:
npx openapi-typescript https://gpuninja.com/api/v1/openapi.json -o gpuninja.d.tsOr a client in another language with OpenAPI Generator:
npx @openapitools/openapi-generator-cli generate -g python \
-i https://gpuninja.com/api/v1/openapi.json -o gpuninja-clientRemember the open-schema rule under Stability and versioning: new properties can appear at any time, and no schema forbids unknown ones. Generate in a mode that keeps or ignores unknown properties rather than rejecting the response, and regenerate when info.version rises to pick up new fields.
A generated API reference at /docs/api/reference, listing each endpoint and schema in a readable form, is planned. Until it ships, Collections and entities and the other pages of this section are the field reference.
In this section
- Collections and entities
The nine collections, how paging works, and the fields of every entity summary and detail document, with provenance and frozen records.
- Compatibility
Which cases a card fits, which cards fit a case, and which AI models a card can run, with the exact rules behind each answer.
- Search index
The compact search index of chips, boards, cases, models, motherboards and risers, its fields per kind, and how to search it with the open-source library.
- Changes feed
The machine-readable record of every data change in the last 30 days, the monthly archives, and how to poll them to keep a local copy current.
- Caching, redirects and errors
Response headers, ETag revalidation, CORS limits, redirects for renames and trailing slashes, and what a 404 or 405 looks like.