NFT Trencher API

v1 · for Cassette holders · the board's slice, stages, changes

The same mints the board shows at neverfuckingtrade.com — Robinhood Chain, Ethereum, Ink and HyperEVM, a week ahead, selected by our rules — as JSON. Names, links, every mint stage with its time, price and per-wallet limit, the active and the next stage, the status, the supply, our tier and our flags.

Access

The API is for holders of the Cassette. Open your profile on the site, sign in with the wallet that holds it and press MAKE KEY in Alerts & links. The key is shown once — copy it. NEW KEY replaces it, REVOKE kills it. If the Cassette leaves your signed wallets, the key goes dark on its own within ten minutes.

Send the key in the X-API-Key header (or as Authorization: Bearer …).

curl -H "X-API-Key: nftt_…" https://neverfuckingtrade.com/api/v1/mints

Base URLs: https://neverfuckingtrade.com/api/v1 and https://cdn.neverfuckingtrade.com/api/v1 — the same server, the first goes through the site's edge.

Limits

WhatLimit
Per key120 requests a minute per key for a common Cassette, 180 for a rare, 240 for an epic, 360 for a 1/1 (bursts of up to 30 in a second are fine)
Whole API1 000 requests a minute across every key
Over the limit429 with Retry-After: 5
MethodsGET only (and OPTIONS for CORS) — anything else answers 405 with an Allow header
Headersevery answer carries X-RateLimit-Limit: 120 per minute per key and, machine-readable, RateLimit-Policy: "per-key";q=120;w=60. There is no remaining counter: the limit is enforced at the edge, not counted per answer — pace at two requests a second and you will never see a 429.
Freshnessthe board rebuilds every ~40 s; answers may be cached for 15 s — polling faster than once a minute buys nothing

The limit is per key, not per IP.

Endpoints

GET /api/v1/mints

The whole slice. Filters: ?chain=robinhood|ethereum|ink|hyperevm, ?status=upcoming|live|sold_out|ended, ?tier=hot|warm|cold|dust. Several values at once, comma-separated or repeated: ?chain=ethereum,ink, ?status=live&status=upcoming. An unknown value answers 400 with the list of known ones.

{ "v": 1, "built": "2026-09-07T10:31:12Z", "count": 147, "mints": [ … ] }

GET /api/v1/mints/{id}

One mint. id is chain:contract, a bare contract address, or the OpenSea slug (case does not matter). Only mints on the board are served — 404 otherwise. The mint object described under A mint comes wrapped:

{ "v": 1, "built": "2026-09-07T10:31:12Z", "mint": { "id": "robinhood:0x1f00…", … } }

GET /api/v1/changes?since=…

What changed since a moment: since as unix seconds, milliseconds or ISO 8601; an hour ago when omitted. changed holds the mints whose name, links, stages, status, supply or flags changed after since; removed the ids that left the board. A mint counts as changed when we first see it. After a restart of our service every mint counts as changed at that moment — take the whole list once and go on from its built. A since that cannot be read answers 400, never a silent default.

{ "v": 1, "built": "…", "since": "2026-09-07T09:31:12Z", "changed": [ … ], "removed": [ { "id": "ink:0x…", "at": "…" } ] }

GET /api/v1/me

Your key: profile, prefix, usage, limit. Handy to test the header.

GET /api/v1/raffles/won

The raffles of our Discord your profile won — through the Discord linked to the profile (PROFILE → LINK DISCORD). kind is gtd or fcfs, wallet is the address that went on the project's list, announcement opens the winners post. Without a linked Discord the list is empty and hint says so. Only raffles of the Never Fucking Trade server.

{ "v": 1, "discord": "8162…", "count": 2, "won": [ { "id": "7ba0db27", "title": "ROBINTARDIOS", "link": "https://x.com/…", "chain": "Robinhood", "kind": "gtd", "wallet": "0xfdb0…", "drawn_at": "2026-09-06T02:58:32Z", "ended_at": "…", "announcement": "https://discord.com/channels/…" } ], "hint": null }

GET /api/v1/raffles

The open raffles, soonest to end first: gtd / fcfs spots, entrants, ends_at, and for your profile entered, tickets, wallet; card opens the raffle in Discord.

A mint

{
  "id": "robinhood:0x1f00…", "chain": "robinhood", "contract": "0x1f00…",
  "name": "Fortune Foes", "slug": "fortune-foes",
  "links": { "x": "https://x.com/FortuneFoes", "site": "https://fortunefoes.com/",
             "opensea": "https://opensea.io/collection/fortune-foes", "mint": "https://fortunefoes.com/" },
  "stages": [
    { "kind": "gtd", "code": "GTD", "label": "GTD", "public": false, "open_to_visitors": true,
      "start": "2026-09-07T16:00:00Z", "end": "2026-09-07T17:00:00Z", "state": "upcoming",
      "price": 0.0, "currency": "ETH", "max_per_wallet": 2 },
    { "kind": "public", "code": "PUBLIC", "label": "Public stage", "public": true, "open_to_visitors": true,
      "start": "2026-09-07T18:00:00Z", "end": null, "state": "upcoming",
      "price": 0.002, "currency": "ETH", "max_per_wallet": 5 }
  ],
  "active_stage": null, "next_stage": { … the GTD stage … },
  "status": "upcoming", "minted": 0, "supply": 3333,
  "tier": "warm", "flags": [], "updated_at": "2026-09-07T10:12:40Z"
}
FieldMeaning
stages[].kindour reading of the stage: public · gtd · fcfs · wl · og · holder · raffle · team · other. OpenSea itself only tells public from gated; the names are the creator's free text, kept in label. Treat kind as a good guess.
stages[].open_to_visitorsfalse for team, dev and reserve stages nobody outside the project can enter.
stages[].pricein the chain's native coin (currency); 0 is a free mint; null unknown.
stages[].endnull when no end is announced.
active_stage · next_stagethe stage running now and the first one ahead, both taken from stages.
statusupcoming · live · sold_out · ended at build time; recompute from the stage times while you hold the answer.
tierour hype tier: hot · warm · cold · dust. The board folds dust by default.
flagsprice_switch — a free mint that turned paid while its window was open; locked — the contract refuses holders' transfers or marketplace approvals.
updated_atwhen the visible data last changed as we saw it (not the minted counter).

The public stage on Ink and HyperEVM is read from the chain every round; gated stages come from OpenSea by schedule and may lag by minutes. Solana is not served.

Errors

CodeWhy
400 bad filter · bad sincean unknown filter value or an unreadable since; the hint says what is accepted
401 unauthorizedno key, an unknown key, or a key that went dark with the Cassette
404 not on the boardthe id is not in the slice (or was never there)
405 method not allowedanything but GET on these paths — the Allow header lists what works
429 rate limitmore than the per-minute limit of this key (120 common, 180 rare, 240 epic, 360 for a 1/1) — wait for Retry-After

Every error is JSON with an error and, where it helps, a hint.

Whitelist eligibility

The same check the site does, on your own OpenSea session: your wallet signs OpenSea's sign-in message, OpenSea answers about that wallet, and its account limits are yours, not ours. Nothing of it is stored on our side — the pass you get lives with you for three days. One signature per wallet: OpenSea only answers about the wallet that signed. Server-side use only (no CORS).

StepCall
1 · messageGET /api/v1/wl/nonce?address=0x…message (OpenSea's SIWE text with a fresh nonce; nonces are single-use)
2 · signyour wallet signs the exact text with personal_sign (EIP-191); the private key never leaves you
3 · passPOST /api/v1/wl/pass {address, message, signature}pass (3 days). Optional: step 4 accepts the signature directly and returns the pass too.
4 · scanPOST /api/v1/wl/scan {address, slugs?} with X-OpenSea-Pass: … (or pass / message+signature in the body) → what the wallet is listed for
{
  "v": 1, "address": "0x…", "checked": 128, "seconds": 9.4,
  "listed": [
    { "slug": "fortune-foes", "contract": "0x…",
      "stages": [ { "label": "GTD", "kind": "gtd", "start": "2026-09-07T16:00:00Z", "end": "2026-09-07T17:00:00Z", "max_per_wallet": 2 } ] }
  ],
  "failed": [], "skipped": [],
  "pass": "…", "hours": 72,          ← only when the scan was signed, keep it
  "quota": { "op": "scan", "left": 19, "per_hour": 20 }
}
FieldMeaning
slugswhich collections to ask about (OpenSea slugs). Omitted — every collection on the board with a gated stage ahead, up to 130 per scan.
listedcollections where the wallet is on a gated list, with the stage it is listed for; the public stage is never reported — everyone is let in there.
failedslugs OpenSea did not answer for this time — ask again later, silence is not a refusal.
401 pass expiredthe pass is dead — take a new message and sign again.

Quotas

WhatLimit
Scans100 an hour per key, whatever the number of slugs
Sign-ins and nonces150 an hour per key
Over the quota429 with retry_in seconds

These endpoints live on https://neverfuckingtrade.com/api/v1/wl/… only (not on the cdn host). A scan of the whole board takes about ten seconds.

Changes

08.09.2026 — after a holder's test report: 405 with Allow for anything but GET (was a 403 or an HTML 501); 400 for an unreadable since and for unknown filter values (was a silent fallback); several values per filter; the RateLimit-Policy header; the answer of /mints/{id} documented as it is.

Questions · @Fr0oZi · @NevaFckngTrade