GUNZscope API v1

Build On Off The Grid Supply Data

GUNZscope independently indexes every mint and burn on GunzChain, aggregated per item. The free tier answers how many of an item exist right now and how scarce it is, for approved developers, with keyed access and no scraping required. The pro tier adds the history: lifetime mints and burns, first and last mint dates, time-based rankings and drop events, on the same calls with one path change.

Getting Started

  1. Sign in with your GUNZscope account.
  2. In the API Access panel just below, describe what you are building. Free access is granted as soon as you submit, with no wait and nobody to email. The same panel lives under Account, API Access if you would rather work from there.
  3. Create a key. The full key is shown once and stored hashed, so copy it then.
  4. Send it on every request as the X-API-Key header.

Everyone starts on free, and free is instant. We still ask what you are building because a key is issued to an account rather than to nobody in particular, but the answer does not gate anything: fill the form in, create a key, make your first call. Partner (a higher rate limit) and paid (the pro namespace) are the parts a person still reads, and you ask for them on the key you already hold rather than by starting again. Nothing stops working while we talk.

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/supply/item?name=Kestrel%20Adaptive%20Stock"

Authentication, Tiers And Limits

Send your key as X-API-Key

X-API-Key: gs_live_your_key_here

Do not use Authorization: Bearer. That header is reserved for signed-in user sessions and is rejected before the request reaches the API, so a key sent that way fails no matter how valid it is. This is the most common first-call mistake.

TierRate limitNamespacesData you getHow to get it
free60 requests per minute/api/v1/supply/Current active supply, scarcity bracket, rankings by current supply, indexer health.Sign in, fill in three fields at Account, API Access, and create your key. No wait.
partnerNegotiated per key, from the same 60 baseline./api/v1/supply/The same free data. Partner is a throughput arrangement, not a data entitlement, so it does not unlock pro.Start on free, then ask for a raise on the key you already hold. Reviewed by a person.
paidNegotiated per key, from the same 60 baseline./api/v1/supply/ and /api/v1/pro/supply/Everything free returns, plus lifetime mints and burns, first and last mint dates, every rankings ordering, drop events and the events cursor feed.Start on free, then ask from the API Access section of your account. Arranged case by case.

Rate limits are per key rather than per account, and apply to free and pro calls alike. Each key gets its own window, so a second key does not divide your first key’s allowance. Exceeding a limit returns 429 with a Retry-After header. Every key of every tier starts at 60 requests per minute; no tier raises that number on its own. A higher ceiling is set by hand on the key when it is granted, so tell us the volume you expect and we will set it.

What the key does
Your key carries a tier, and the tier decides which namespace serves you data: every key can call the free endpoints, and only a paid key can call the pro endpoints. It also governs your rate limit and usage accounting. Send it on every request. The free supply numbers are public and already visible on GUNZscope pages without an account, so free GET responses are edge cached and shared across callers; a cached free response can be served without the key being checked again. Pro responses are never shared or edge cached, so the key is checked on every pro call.
Base URLs
Free: https://gunzscope.xyz/api/v1/supply
Pro: https://gunzscope.xyz/api/v1/pro/supply
Same params, same header, same response wrappers. To upgrade a call, swap /supply/ for /pro/supply/ in the path.
Versioning
/api/v1/ is the stable contract, with two shapes: the free item shape served under /api/v1/supply/ and the full item shape served under /api/v1/pro/supply/. Each is now the baseline for its namespace, and changes within v1 are additive from here, so new fields may appear and existing ones will not change meaning or disappear. Parse defensively and ignore fields you do not know. A breaking change would ship as /api/v2/.
Keys
Up to 3 active keys per account. Revoke and replace them yourself at any time.
Usage and quota
Every call is counted against the key that made it, never against your account as a whole. Your trailing 30 days of per key usage, broken down by day, are shown at Account, API Access, alongside the keys themselves. A 429 means one key crossed its own per minute limit, so confirm which key your client is actually sending before asking for a higher ceiling. If usage is spread across two keys and the 429s land on only one of them, the fix is in your client rather than in your limit.
Cached free calls are not counted
Free GET responses are edge cached and shared across callers, so a cached hit is served without your key being read at all. Those hits reach neither the rate limiter nor the usage counters, which means free GET usage can read lower than the number of requests your client actually sent. Pro responses are never cached, so every pro call is counted exactly. Treat the free numbers as a floor rather than a total.
Paging
Rankings pages with limit and offset. limit caps at 500 (default 100); advance offset by your limit to walk the list, and stop when pagination.hasMore is false.
CORS
All endpoints allow any origin, so browser and mobile clients can call them directly.
Caching
Free GET responses are edge cached; the X-Vercel-Cache header reads HIT or MISS so you can tell a cached response from a fresh one. /batch is not cached. Pro responses are sent with Cache-Control: private, no-store and are never edge cached. Underlying data is refreshed continuously by the indexer, so poll on your own schedule rather than tuning to a cache window.

Errors

401 { "error": "API key required" } - no X-API-Key header was sent

401 { "error": "Invalid API key" } - unknown or revoked key. The two cases are deliberately indistinguishable

403 { "error": "This endpoint requires a paid tier" } - a valid free or partner key called a pro endpoint

429 { "error": "Rate limit exceeded" } - sent with Retry-After: 60

400 { "error": "..." } - invalid parameters, see below

500 { "error": "..." } - server side failure

On a 429, read the Retry-After header, wait that many seconds, then retry. It is currently always 60. Rate limit windows are per minute, so a fixed wait clears them and there is no need for exponential back-off. Do not retry immediately in a loop: a client that hammers through a 429 simply spends its next window on rejected requests. A 403 is not transient: the same key will get the same answer, so switch the call to the free path or upgrade the key rather than retrying.

Handling a 429

async function callWithRetry(url) {
  const headers = { 'X-API-Key': 'gs_live_your_key_here' };
  let res = await fetch(url, { headers });

  if (res.status === 429) {
    // Retry-After is in seconds. Wait it out, then retry once.
    const wait = Number(res.headers.get('Retry-After') ?? 60);
    await new Promise(resolve => setTimeout(resolve, wait * 1000));
    res = await fetch(url, { headers });
  }

  if (!res.ok) throw new Error('Request failed: ' + res.status);
  return res.json();
}

With curl, the same rule applies by hand: if the response status is 429, read the Retry-After header from curl -i, sleep that many seconds, and send the request again.

400 on rankings

{ "error": "Invalid bracket. Expected one of: abundant, circulating, limited, scarce" }

{ "error": "Invalid sort. Expected one of: activeMints, supplyBracket, itemName" } - free rankings only

Only those four bracket values are accepted on either tier. The internal database slugs are not, so passing something like ultra-rare is a 400 rather than an empty page. Sort differs by namespace: the free /rankings accepts activeMints, supplyBracket and itemName and answers any other value, including the pro orderings, with the 400 above; the pro /rankings accepts all six orderings and silently falls back to totalMints for a value it does not recognise.

400 on batch

{ "error": "Invalid JSON" }

{ "error": "items array is required" } - missing or empty items

{ "error": "Maximum 100 items per request" }

{ "error": "Each item must have a \"name\" string" }

Free Endpoints

Scope: the free namespace serves current on-chain supply, meaning how many of an item are active right now, its scarcity bracket, rankings by current supply, and indexer health. Every item object has exactly these keys: itemName, rarity, bracket, activeMints, imageUrl; rankings rows add rank and rarerThanPct (see /rankings). Lifetime mint and burn counts, mint dates, time-based rankings and drop events live in the pro namespace below. Pricing, valuation and profit-and-loss figures are not available through this API at any tier; those belong to the valuation product inside the GUNZscope app, which is a separate system rather than a higher tier of this feed.

GET/api/v1/supply/item

Per-rarity variants of one item by exact name, plus total active supply. An unknown name is a 200 with an empty items array, not a 404. Every row says how it was matched: matchedVia is "live" when the row carries the rarity you asked for (or you asked for none). With resolveRetired=1, a rarity that no live row carries any more (upstream relabels retire labels; the old label is kept in the item's history) resolves to the surviving item, returned under its CURRENT rarity with matchedVia "rarityHistory" and queriedRarity set to the label you sent; the counters are the survivor's own. A live match always wins and the fallback never runs beside it. If you query more than one rarity for the same name with resolveRetired on, two queries can return the same itemId. Dedupe on itemId before summing.

Params: name (required), rarity (optional), resolveRetired (optional; 1 or true turns retired-label resolution on, anything else leaves it off)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/supply/item?name=Kestrel%20Adaptive%20Stock"

JavaScript

const res = await fetch(
  'https://gunzscope.xyz/api/v1/supply/item?name=' + encodeURIComponent('Kestrel Adaptive Stock'),
  { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
);
const data = await res.json();

Response

{
  "items": [
    {
      "itemId": "cmmv8...",                        // stable row identifier; survives upstream rarity relabels and merges. Use this to dedupe
      "assetKey": "WeaponAttachment_DA_WA_SR01_STK_S04", // Gunzilla asset template key. Shared across items that use the same asset (sight families, renames); not unique on its own
      "itemName": "Kestrel Adaptive Stock",
      "rarity": "Epic",                            // nullable; always the row's current rarity
      "bracket": "scarce",                         // nullable
      "activeMints": 8,
      "imageUrl": "https://...",                   // nullable
      "matchedVia": "live"                         // "live" or "rarityHistory"; a rarityHistory row also carries queriedRarity
    }
  ],
  "totalSupply": 8,
  "updatedAt": "2026-08-21T00:00:00.000Z",
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
POST/api/v1/supply/batch

Up to 100 item lookups in one call. Results keyed by "name::rarity", or "name::" when no rarity was sent. Every requested key comes back, empty ones included. Not cached. With resolveRetired=1 on the URL, a retired rarity resolves the way /item resolves it and lands under the name::rarity key you sent, with matchedVia "rarityHistory" and queriedRarity on the row; every other row says matchedVia "live". Two entries with different retired labels for one name can resolve to the same itemId, so dedupe on itemId before summing.

Params: query: resolveRetired (optional; 1 or true, applies to every entry); body: { "items": [{ "name": string, "rarity"?: string | null }] }, 1 to 100 entries

curl

curl -X POST "https://gunzscope.xyz/api/v1/supply/batch" \
  -H "X-API-Key: gs_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"name":"Kestrel Adaptive Stock","rarity":"Epic"},{"name":"Pierser Holographic Sight"}]}'

JavaScript

const res = await fetch('https://gunzscope.xyz/api/v1/supply/batch', {
  method: 'POST',
  headers: {
    'X-API-Key': 'gs_live_your_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { name: 'Kestrel Adaptive Stock', rarity: 'Epic' },
      { name: 'Pierser Holographic Sight' },
    ],
  }),
});
const data = await res.json();

Response

{
  "results": {
    "Kestrel Adaptive Stock::Epic": {
      "items": [ /* free item objects, same keys as /item */ ],
      "totalSupply": 8
    }
  },
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
GET/api/v1/supply/rankings

Paginated scarcity rankings by current supply, with per-bracket summary counts. Every row also carries its scarcity position: rank (1 = fewest active copies) and rarerThanPct (share of the catalogue with more copies than this item, 0.0 to 100.0, one decimal), both computed over the default view (activeMints > 0, base limbs excluded) whose size the envelope reports as population. Ranks are against the full catalogue even when you filter by bracket, category or sort, so a bracket=scarce page starts at whatever rank its first row holds overall. Tied counts share a rarerThanPct and are ordered by id so paging is stable. Both are null for a row outside the default view (excludeZero=false or excludeBase=false).

Params: bracket (abundant | circulating | limited | scarce), category, sort (activeMints | supplyBracket | itemName; default activeMints; any other value is a 400), order (asc | desc), limit (1-500, default 100), offset, excludeZero (default true), excludeBase (default true)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/supply/rankings?bracket=scarce&limit=25&offset=0"

JavaScript

const res = await fetch(
  'https://gunzscope.xyz/api/v1/supply/rankings?bracket=scarce&limit=25&offset=0',
  { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
);
const data = await res.json();

Response

{
  "items": [
    {
      "itemId": "cmmv8...",                         // stable row identifier, same meaning as on /item
      "assetKey": "CustomizationItemTemplate_CIT_Pioneer_Hoodie", // asset template key, same meaning as on /item
      "itemName": "Pioneer Hoodie",
      "rarity": null,                               // nullable
      "bracket": "abundant",                        // nullable
      "activeMints": 367,
      "imageUrl": "https://cdn.example/pioneer.png", // nullable
      "rank": 196,           // 1 = fewest active copies, over the whole population
      "rarerThanPct": 94.6   // one decimal; render as "Rarer than 94.6%"
    }
    /* ...free item keys plus rank and rarerThanPct on every row */
  ],
  "total": 1284,
  "population": 3651,   // items in the default view; rank and rarerThanPct are computed against this
  "brackets": { "scarce": 41, "limited": 220, "circulating": 502, "abundant": 521 },   // key order not guaranteed
  "pagination": { "limit": 100, "offset": 0, "hasMore": true },
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
GET/api/v1/supply/health

Indexer liveness and data freshness. blocksBehind is the direct freshness signal: how many GunzChain blocks the indexer is behind the current chain tip, so 0 or a low number means fresh and a growing number means supply figures trail the chain, which makes them stale rather than wrong. chainTip is the height it was compared against and lastBlock is the most recent block the indexer has processed, so you can see both sides of that subtraction. Both chainTip and blocksBehind are null when the chain tip could not be read at that moment: the endpoint still returns 200 and the indexer state is still given by status and lastBlock. status is a live passthrough of indexer state ("live" while it is running), so treat it as an opaque string rather than a fixed set, and expect lastBlockTime to be null at times. This response is edge cached like the other free GET endpoints, so read blocksBehind as a lag indicator rather than a to-the-block measurement. Health is free for every key; there is no pro counterpart.

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/supply/health"

JavaScript

const res = await fetch('https://gunzscope.xyz/api/v1/supply/health', {
  headers: { 'X-API-Key': 'gs_live_your_key_here' },
});
const data = await res.json();

Response

{
  "status": "live",
  "lastBlock": 22926884,
  "lastBlockTime": null,   // nullable, ISO string when known
  "chainTip": 22926891,    // nullable, null if the tip read failed
  "blocksBehind": 7, // nullable, null if the tip read failed
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}

Pro Endpoints

Same calls, swap the path, full data. The pro namespace takes the same params and the same X-API-Key header, and returns the same wrappers, with every item object carrying the full key set: itemName, rarity, bracket, totalMints, activeMints, totalBurns, firstMintAt, lastMintAt, imageUrl, and rankings rows add rank and rarerThanPct as on the free route. It also serves the drops endpoint and every rankings ordering. Requires a paid key; any other key receives 403. Responses are private and never edge cached.

GET/api/v1/pro/supply/item

Same call as the free /item, full shape: adds lifetime mint and burn counts and the first and last mint dates to every item. resolveRetired, matchedVia and queriedRarity work exactly as on the free /item.

Params: name (required), rarity (optional), resolveRetired (optional; 1 or true turns retired-label resolution on, anything else leaves it off)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/pro/supply/item?name=Kestrel%20Adaptive%20Stock"

JavaScript

const res = await fetch(
  'https://gunzscope.xyz/api/v1/pro/supply/item?name=' + encodeURIComponent('Kestrel Adaptive Stock'),
  { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
);
const data = await res.json();

Response

{
  "items": [
    {
      "itemId": "cmmv8...",                        // stable row identifier; survives upstream rarity relabels and merges. Use this to dedupe
      "assetKey": "WeaponAttachment_DA_WA_SR01_STK_S04", // Gunzilla asset template key. Shared across items that use the same asset (sight families, renames); not unique on its own
      "itemName": "Kestrel Adaptive Stock",
      "rarity": "Epic",                            // nullable
      "bracket": "scarce",                         // nullable
      "totalMints": 8,
      "activeMints": 8,
      "totalBurns": 0,
      "firstMintAt": "2026-03-01T00:00:00.000Z",   // nullable
      "lastMintAt": "2026-08-16T00:00:00.000Z",    // nullable
      "imageUrl": "https://...",                   // nullable
      "matchedVia": "live"                         // "live" or "rarityHistory"; a rarityHistory row also carries queriedRarity
    }
  ],
  "totalSupply": 8,
  "updatedAt": "2026-08-21T00:00:00.000Z",
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
POST/api/v1/pro/supply/batch

Same body and rules as the free /batch, full item shape in every result. resolveRetired on the URL works exactly as on the free /batch.

Params: query: resolveRetired (optional; 1 or true, applies to every entry); body: { "items": [{ "name": string, "rarity"?: string | null }] }, 1 to 100 entries

curl

curl -X POST "https://gunzscope.xyz/api/v1/pro/supply/batch" \
  -H "X-API-Key: gs_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"name":"Kestrel Adaptive Stock","rarity":"Epic"},{"name":"Pierser Holographic Sight"}]}'

JavaScript

const res = await fetch('https://gunzscope.xyz/api/v1/pro/supply/batch', {
  method: 'POST',
  headers: {
    'X-API-Key': 'gs_live_your_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { name: 'Kestrel Adaptive Stock', rarity: 'Epic' },
      { name: 'Pierser Holographic Sight' },
    ],
  }),
});
const data = await res.json();

Response

{
  "results": {
    "Kestrel Adaptive Stock::Epic": {
      "items": [ /* full item objects, same keys as pro /item */ ],
      "totalSupply": 8
    }
  },
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
GET/api/v1/pro/supply/rankings

Same params as the free /rankings, full item shape, every ordering: rank by lifetime mints, burns or last mint date as well as current supply. Rows carry the same rank and rarerThanPct as the free route and the envelope the same population; the position is always by active copies over the default view, whatever sort you asked for.

Params: bracket (abundant | circulating | limited | scarce), category, sort (totalMints | activeMints | totalBurns | itemName | lastMintAt | supplyBracket; default totalMints; an unrecognised value falls back to totalMints), order (asc | desc), limit (1-500, default 100), offset, excludeZero (default true), excludeBase (default true)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/pro/supply/rankings?sort=lastMintAt&order=desc&limit=25"

JavaScript

const res = await fetch(
  'https://gunzscope.xyz/api/v1/pro/supply/rankings?sort=lastMintAt&order=desc&limit=25',
  { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
);
const data = await res.json();

Response

{
  "items": [ /* full item objects, same keys as pro /item, plus rank and rarerThanPct as on the free /rankings */ ],
  "total": 1284,
  "population": 3651,   // items in the default view; rank and rarerThanPct are computed against this
  "brackets": { "scarce": 41, "limited": 220, "circulating": 502, "abundant": 521 },   // key order not guaranteed
  "pagination": { "limit": 25, "offset": 0, "hasMore": true },
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
GET/api/v1/pro/supply/drops

Detected drop events, ongoing first, then most recently detected. Pro only; there is no free drops endpoint.

Params: limit (1-50, default 10), minItems (default 3), since (ISO timestamp), ongoing (true | false)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/pro/supply/drops?limit=5&ongoing=true"

JavaScript

const res = await fetch(
  'https://gunzscope.xyz/api/v1/pro/supply/drops?limit=5&ongoing=true',
  { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
);
const data = await res.json();

Response

{
  "drops": [
    {
      "detectedAt": "2026-08-16T21:00:00.000Z",
      "endedAt": null,
      "itemNames": ["Item A", "Item B", "Item C"],
      "newItemCount": 3,
      "isOngoing": true,
      "totalMinted": 120
    }
  ],
  "total": 1,
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}
GET/api/v1/pro/supply/events

Pull-only cursor feed of detected supply events: bracket transitions and drops, oldest first. Rows are immutable once written, so an event you have read never changes afterwards. Poll it with the cursor rather than re-reading a window.

Params: since (event id, exclusive, default 0), limit (1-200, default 50), type (bracket_transition | drop; omit for both)

curl

curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/pro/supply/events?since=0&limit=50"

# then keep the nextCursor from the response and pass it back:
curl -H "X-API-Key: gs_live_your_key_here" \
  "https://gunzscope.xyz/api/v1/pro/supply/events?since=1042&limit=50"

JavaScript

// Walk the feed forward, storing the cursor between runs.
let cursor = loadSavedCursor() ?? 0;
for (;;) {
  const res = await fetch(
    `https://gunzscope.xyz/api/v1/pro/supply/events?since=${cursor}&limit=50`,
    { headers: { 'X-API-Key': 'gs_live_your_key_here' } },
  );
  const { events, nextCursor, hasMore } = await res.json();
  for (const event of events) handle(event);
  if (nextCursor !== null) { cursor = nextCursor; saveCursor(cursor); }
  if (!hasMore) break;
}

Response

{
  "events": [
    {
      "id": 1042,
      "type": "bracket_transition",
      "itemName": "Boomslang Red Dot Sight",
      "payload": {
        "fromBracket": "uncommon",
        "toBracket": "rare",
        "activeMintsAtEmit": 47
      },
      "createdAt": "2026-08-23T12:04:11.000Z"
    },
    {
      "id": 1043,
      "type": "drop",
      "itemName": null,
      "payload": {
        "dropId": "cmr...",
        "detectedAt": "2026-08-23T12:30:00.000Z",
        "itemNames": ["Item A", "Item B", "Item C"],
        "itemCountAtEmit": 3
      },
      "createdAt": "2026-08-23T12:31:02.000Z"
    }
  ],
  "nextCursor": 1043,
  "hasMore": false,
  "attribution": { "text": "Data by GUNZscope", "url": "https://gunzscope.xyz", "logoUrl": "https://gunzscope.xyz/brand/gunzscope-mark-mono.svg" }
}

Scarcity Brackets

The bracket field describes how many copies of an item are still active on chain. It is a supply measure and is unrelated to the in-game quality tier (Common, Uncommon, Rare, Epic), which is reported separately as rarity.

scarce10 or fewer active
limited11 to 50 active
circulating51 to 200 active
abundantmore than 200 active

Terms Of Use

Attribution required

Any surface that displays this data must carry a visible "Data by GUNZscope" credit linking back to gunzscope.xyz.

No bulk redistribution

Use the data in your own product. Do not resell it, republish it as a dataset, or proxy the API to third parties.

Keys are revocable

Keys may be revoked or rate-limited at our discretion, including for abuse, scraping patterns, or attribution failures.

Supply data only

Valuation, profit and loss, cost basis and comparable sales are not part of this API, on either the free or the pro tier. They belong to the separate valuation product inside the GUNZscope app.

Attribution Kit

Everything you need to render a compliant "Data by GUNZscope" credit. Copy the assets, follow the placement rules, done.

The credit

Data by GUNZscope

The credit links to https://gunzscope.xyz.

Render as: mark icon (16-20px) + text in your own UI font. Do not recreate the wordmark; the text string in your font is the wordmark.

Every v1 response also carries this credit as a top-level attribution object (text, url, logoUrl), so you can render it from the payload instead of hardcoding it.

Assets

/brand/gunzscope-mark.svgColor mark (lime #90F700), for dark backgrounds.
/brand/gunzscope-mark-mono.svgMonochrome mark, inherits currentColor, for any background.

Placement rules

  1. One credit per screen where the data renders. List of 50 items = one credit anchored to the list, not 50 credits.
  2. On-screen means on-screen. If supply data is visible, the credit is visible. Settings pages, about sheets, and tap-to-reveal do not count.
  3. Item detail views place the credit directly beneath the supply block it attributes.
  4. Do not recolor the color mark, stretch, rotate, add effects, or place it on clashing backgrounds. Minimum clear space: half the mark height on all sides. Minimum size: 16px.

Example

Do[mark 16px] Data by GUNZscope - bottom of the inventory list, linked

Dontcredit buried in Settings > About, or repeated on every row

Ready to build? Sign in, fill in three fields, and your free key is waiting on the other side. Nothing to wait for.