# GunSpec.io API Docs > Developer documentation for the GunSpec.io REST API: a firearms specification > database covering 9,000+ firearms (dimensions, ballistics, materials, game-balance > stats) plus ammunition, calibers, manufacturers, and more. This file is a > machine-readable index for AI agents and crawlers. ## Quick facts - API base URL: https://api.gunspec.io/v1 - Authentication: API key in the `X-API-Key` header, or `Authorization: Bearer ` - both accepted everywhere, neither is being retired. Some reads (blog, changelog, shared collections) need no key. - Rate limits (per minute): Explorer 10, Builder 60, Studio 120, Enterprise 300. A 429 returns `Retry-After` (seconds) - the only rate-limit header sent. There is no `X-RateLimit-Remaining`: the edge limiter reports whether a request was allowed, not how much budget is left. Poll `GET /v1/me/usage` for consumption. - Caching: encouraged. Read `Cache-Control` off each response; every catalog record carries `updatedAt` (when it last changed) and `version` (a hash of its own fields), and every catalog response carries an `ETag`. Send it back as `If-None-Match` to get `304 Not Modified` with no body. A 304 counts toward the per-minute rate limit but not the daily allowance. Table: https://docs.gunspec.io/en/caching - Webhooks: on Studio and Enterprise, `POST /v1/me/webhooks` registers an endpoint and catalog changes are pushed to it, so a mirror needs no polling loop. Events: `firearm.created` / `.updated` / `.deleted`, the same three for `manufacturer.*` and `caliber.*`, plus `firearm.variant.updated`, `firearm.source.changed`, `firearm.confidence.changed`, and `catalog.resynced` (too many records changed at once - re-sync rather than applying it). The delivery carries the record in the same shape the detail endpoint returns it, `version` included; a deletion carries the id alone. Dedupe on `X-Webhook-Id` (unchanged across retries) and verify `X-Webhook-Signature` before trusting the body. - Tiers gate which endpoints a key may call (Explorer < Builder < Studio < Enterprise). - OpenAPI 3 spec: https://api.gunspec.io/openapi.json - Interactive explorer (Swagger UI): https://api.gunspec.io/docs - Human docs: https://docs.gunspec.io ## Response shape Success: `{ "success": true, "data": ... }`. List endpoints add `"pagination": { page, limit, total, totalPages }`. Error: `{ "success": false, "error": { "code", "message", "request_id" } }`. Not every success is JSON: `/v1/ammunition/{id}/bullet.svg`, `/v1/firearms/{id}/media/{selector}` and `/v1/firearms/{id}/images/{imageId}` with `format=raw` (the default) and `/v1/firearms/{id}/model` return the file's bytes, and `/v1/out/{clickId}` answers a 302 redirect. Check `Content-Type` before parsing; see https://docs.gunspec.io/en/media Error codes: VALIDATION_ERROR (400), UNAUTHORIZED (401), FORBIDDEN (403), SUBSCRIPTION_REQUIRED (403), NOT_FOUND (404), RATE_LIMITED (429), INTERNAL_ERROR (500), SERVICE_UNAVAILABLE (503). SUBSCRIPTION_REQUIRED is a 403 carrying `details.requiredTier` - the caller is authenticated but their plan does not include the endpoint. Treat it as "upgrade", not "retry". Every response carries an `X-Request-Id` header. ## Pagination `page` (1-based, max 10,000) and `per_page` (max 100; a few ranking endpoints take a single `limit` instead). Stop when a page comes back with fewer than `per_page` rows - that works on every plan. `pagination.total` and `totalPages` are absent on Explorer and anonymous calls, so never drive the loop from them. Page depth is capped per plan (403 `PAGINATION_DEPTH_EXCEEDED`) and more than 10 sequential pages in 90 seconds is a 429 `PAGINATION_BURST`. The official SDKs expose `listAutoPaging` / `list_auto_paging`. Full rules and every paged endpoint: https://docs.gunspec.io/en/pagination ## Versioning Every endpoint is under `/v1`; `info.version` in the OpenAPI document says which release of that contract is live. Additive changes (new endpoints, fields, parameters, enum values, error reasons) land under `/v1` without a new prefix - ignore what you do not recognise. A breaking change ships under a new prefix and `/v1` keeps answering for at least 12 months; the one exception is a defect corrected in place on a surface almost nobody calls, announced as a `breaking` changelog entry at least 30 days ahead. Deprecated operations carry `deprecated: true` in the spec. Policy and history: https://docs.gunspec.io/en/versioning ## Agent workflow (read this before calling anything) 1. **Resolve the name to an id first.** `GET /v1/firearms/resolve?q={what the user said}`. Pass their words through unchanged - it is built for "G19 gen 5 MOS", "AK-47", "H&K MP5". Never guess a slug: `glock-19-gen5`, `glock-g19-gen5` and `glock-19` are not interchangeable, and a guess returns 404 or a record for a different variant. This is not search - `/v1/firearms/search` ranks a page of records *about* a query and has no notion of certainty; resolve answers which record a query *is*. 2. **`status: "ambiguous"` means ask the user, not pick the first.** `firearmId` is null and `alternatives` holds the tied candidates with their names. "Glock 19" does not name a generation, and the generations differ in exactly the weights and dimensions people ask about. `status: "not_found"` is a 200, not a 404 - say we hold no such record. Any `match: "fuzzy"` candidate is a suggestion, always scores 0, and is never an answer. Read `unresolvedTokens` before replying: those are words the resolver could not place, usually a variant the catalog does not hold. `POST /v1/firearms/resolve` resolves up to 50 names in one request (Studio). 3. `GET /v1/firearms/{id}` for the full record. `GET /v1/firearms/{id}/variants` for the rest of the family. 4. **Never infer a missing specification.** `null` means GunSpec does not hold the value. Do not fill it from model knowledge, from a sibling variant, or by computing it. Say it is not in the database. 5. **Units are in the field names**: `weightEmptyG` grams, `barrelLengthMm` and `overallLengthMm` millimetres, `muzzleVelocityMps` m/s. Read the suffix and state the unit. `/v1/firearms/{id}/dimensions` gives metric and imperial. 6. **`dataConfidence` is not accuracy.** It is a 0-1 record-level score set from what was actually sourced and never raised by hand, not a per-field probability that a number is correct: 0.95 does not mean the barrel length is 95% likely to be right. 0.5 with `verifiedAt` null is seed model knowledge; treat anything below 0.7 as unverified. Use it to rank and triage; follow `sources` (or the `provenance` object on a detail record) to verify a specific figure. Bands and source order: https://docs.gunspec.io/en/field-reference#data-confidence 7. Specifications legitimately differ by production year, factory, batch and regional variant. Where a record notes disagreement between sources, surface the disagreement rather than presenting one number as settled. 8. **Cache what you fetch, and refresh conditionally.** Store the `ETag` you were given and send it back as `If-None-Match`; a record that has not changed answers 304 with no body and no charge against your daily allowance. Compare `version` (stable across plans) rather than `ETag` when reconciling mirrors held under different keys. Full agent brief (workflow, tool-layer shape, pagination and error handling): https://docs.gunspec.io/llms-full.txt ## Endpoints ### Firearms - GET /v1/firearms - list. Filters: manufacturer, caliber, category, action_type, country_of_origin (NOT "country"), status, features, has_image, has_3d_model, year_introduced_min/_max, weight_min/_max, barrel_length_min, created_after/_before; sort + order; fields. An unknown parameter is ignored, not rejected. - GET /v1/firearms/resolve?q={name} - a name as a person writes it to one id, with an honest `ambiguous` when it is several. Call this first when you start from text - POST /v1/firearms/resolve - the same for up to 50 names in one request (Studio) - GET /v1/firearms/search?q={query} - full-text search - GET /v1/firearms/compare?ids={a},{b} - side-by-side comparison - GET /v1/firearms/filter-options - all filter dropdown values in one call - GET /v1/firearms/action-types - distinct action types - GET /v1/firearms/random - a random firearm - GET /v1/firearms/{id} - full specifications - GET /v1/firearms/{id}/variants - variant models - GET /v1/firearms/{id}/images - images - GET /v1/firearms/{id}/silhouette - SVG silhouette (raw / datauri / json) - GET /v1/firearms/{id}/game-stats - 0-100 game-balance stats - GET /v1/firearms/{id}/dimensions - metric + imperial dimensions - GET /v1/firearms/{id}/similar - similar firearms - GET /v1/popular/firearms - most-viewed firearms ### Ammunition & calibers - GET /v1/ammunition - list ammunition - GET /v1/ammunition/{id} - ammunition details - GET /v1/ammunition/{id}/ballistics - ballistics curve - GET /v1/ammunition/{id}/bullet.svg - bullet illustration - GET /v1/calibers - list calibers - GET /v1/calibers/{id} - caliber details + ballistics - GET /v1/calibers/ballistics - ballistics calculator - GET /v1/calibers/{id}/ammunition - ammunition for a caliber ### Manufacturers, categories, countries, conflicts - GET /v1/manufacturers, /v1/manufacturers/{id}, /v1/manufacturers/{id}/firearms - GET /v1/categories, /v1/categories/{slug}/firearms - GET /v1/countries, /v1/countries/{code}/arsenal - GET /v1/conflicts ### Statistics & game - GET /v1/stats/summary, /v1/stats/production-status, /v1/stats/field-coverage - GET /v1/stats/calibers/popular, /v1/stats/manufacturers/prolific, /v1/stats/by-category - GET /v1/game/tier-list, /v1/game/matchups, /v1/game/role-roster, /v1/game/balance-report - GET /v1/game-stats/versions, /v1/game-stats/versions/{v}/firearms ### Attachment compatibility (what fits what) Computed from mount interfaces, never from names. Browsing the catalog is open; anything that answers "does this go on that" is Studio. - GET /v1/attachments - attachment catalog (open). `requires=` narrows by interface. `fits=` runs the compatibility engine and therefore needs Studio. - GET /v1/attachments/{id} - one attachment (open) - GET /v1/interfaces - interface standards vocabulary, e.g. `thread:1/2x28`, `mag:stanag` (open) - GET /v1/attachments/{id}/firearms - firearms an attachment fits (Studio) - GET /v1/interfaces/{id}/firearms - firearms exposing a standard, id URL-encoded (Studio) - GET /v1/firearms/{id}/attachments - attachments that fit a firearm (Studio) - GET /v1/firearms/{id}/interfaces - a firearm's mount interfaces (Studio) - GET /v1/platforms, /v1/platforms/{id} - platform families (Studio) Every fit carries `source` (the weakest evidence behind it: `curated`, `universal`, `inherited:parent`, `inherited:platform` or `inferred`) and a `confidence` capped by the weakest interface it went through. Treat `inferred` as unverified; `min_confidence` hides fits below a figure you choose, and convention rows (a thread guessed from cartridge and country) are the weakest evidence and are labelled. A caliber, bore or minimum-barrel mismatch is never bridged by an adapter. A lower tier gets 403 SUBSCRIPTION_REQUIRED, not an empty list. ### Where to buy (open) - GET /v1/attachments/{id}/offers, GET /v1/firearms/{id}/offers - sellers stocking a record. Prices are integer minor units plus an ISO 4217 code; format at render, never divide first. ### Discovery and analysis - GET /v1/firearms/by-action (open) - GET /v1/firearms/by-designer, /v1/firearms/by-feature, /v1/firearms/by-material (Builder) - GET /v1/firearms/by-conflict (Studio) - GET /v1/firearms/timeline, /v1/firearms/top, /v1/firearms/head-to-head, /v1/firearms/power-rating (Builder) - GET /v1/firearms/media (media index), /v1/firearms/{id}/media/{slot}, /v1/firearms/{id}/images/{n} - GET /v1/firearms/{id}/calculate (derived ballistics), /v1/firearms/{id}/game-profile (Builder) - GET /v1/firearms/game-meta (Builder), /v1/firearms/{id}/family-tree (Builder) - GET /v1/firearms/{id}/schematics, /v1/firearms/{id}/adoption-map (Studio) - GET /v1/firearms/{id}/media, /v1/firearms/{id}/model, /v1/firearms/{id}/users, /v1/firearms/{id}/load - GET /v1/calibers/compare, /v1/calibers/{id}/family, /v1/calibers/{id}/parent-chain (Builder) - GET /v1/manufacturers/{id}/stats, /v1/manufacturers/{id}/timeline (Builder) ### Data quality (Enterprise) - GET /v1/data/confidence - per-record confidence scores, lowest first - GET /v1/data/coverage - field coverage across the dataset ### Site - GET /v1/notices - active site notices - GET /v1/out/{clickId} - seller click tracker; 302s to the shop. Not for clients to call directly. ### Statistics (full list) - GET /v1/stats/summary - live catalog totals; use this rather than hardcoding counts - GET /v1/stats/by-category, /v1/stats/by-era, /v1/stats/action-types, /v1/stats/materials - GET /v1/stats/feature-frequency, /v1/stats/caliber-popularity-by-era, /v1/stats/catalog-coverage - GET /v1/stats/production-status, /v1/stats/field-coverage - GET /v1/stats/adoption/by-country, /v1/stats/adoption/by-type - GET /v1/stats/calibers/popular, /v1/stats/manufacturers/prolific - GET /v1/game/stat-distribution, /v1/game-stats/versions/{version}/firearms/{id} ### Seller (Enterprise, for shops listing stock) There is no such thing as a vendor key: a shop names an ordinary Enterprise key in Profile > Seller, and that mapping is the entire vendor scope. - GET /v1/vendor/shops - the shop ids this key may act for. An integration is handed a key, never a shop id. - GET /v1/vendor/offers - what the shop holds, private stock counts and click counts included - PUT /v1/vendor/offers - upsert up to 500 rows keyed by the shop's own SKU; unknown ids come back as `unmatched` - PATCH /v1/vendor/offers/{sku} - change one listing; every field optional, absent means unchanged ### Content (public, no key) - GET /v1/blog, /v1/blog/{slug} - GET /v1/changelog, /v1/changelog/{id} - GET /v1/collections/{shareId} ### Account (API key) - GET/POST/DELETE /v1/me/favorites, GET /v1/me/favorites/ids - GET/POST /v1/me/reports, /v1/me/support - GET/POST/PUT/DELETE /v1/me/webhooks, /v1/me/webhooks/{id}, POST /v1/me/webhooks/{id}/test (HMAC-signed delivery, X-Webhook-Signature) - DELETE /v1/me/favorites/{id}; GET /v1/me/support/{id}, /v1/me/support/{id}/replies - GET /v1/me/usage ## SDKs - TypeScript / Node: `npm install @buun_group/gunspec-sdk` - https://www.npmjs.com/package/@buun_group/gunspec-sdk - Python: `pip install gunspec` - https://pypi.org/project/gunspec/ ## MCP server - Hosted, read-only MCP server: https://mcp.gunspec.io (Streamable HTTP). Send the API key in `X-API-Key`. - Install guide an assistant can follow: https://docs.gunspec.io/mcp/install.md - Agent Skill for using the tools: https://docs.gunspec.io/mcp/SKILL.md - Every tool and its arguments: https://docs.gunspec.io/mcp/tools.md - The same as JSON, with each tool's example arguments: https://docs.gunspec.io/mcp/tools.json - Tool calls count against the plan's daily requests and a lower daily MCP allowance: https://docs.gunspec.io/en/mcp/limits ## For AI agents - Generate tools from the OpenAPI spec (https://api.gunspec.io/openapi.json) for function/tool calling. - The uniform envelope and stable error codes make responses easy to parse. - Ready-made coding-agent packs (Claude Code, GitHub Copilot, Codex, Cursor): https://assets.gunspec.io/ai/ - More: https://docs.gunspec.io (see the AI & LLMs page).