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.
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.
| What | Limit |
|---|---|
| Per key | 120 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 API | 1 000 requests a minute across every key |
| Over the limit | 429 with Retry-After: 5 |
| Methods | GET only (and OPTIONS for CORS) — anything else answers 405 with an Allow header |
| Headers | every 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. |
| Freshness | the 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.
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": [ … ] }
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…", … } }
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": "…" } ] }
Your key: profile, prefix, usage, limit. Handy to test the header.
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 }
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.
{
"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"
}
| Field | Meaning |
|---|---|
stages[].kind | our 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_visitors | false for team, dev and reserve stages nobody outside the project can enter. |
stages[].price | in the chain's native coin (currency); 0 is a free mint; null unknown. |
stages[].end | null when no end is announced. |
active_stage · next_stage | the stage running now and the first one ahead, both taken from stages. |
status | upcoming · live · sold_out · ended at build time; recompute from the stage times while you hold the answer. |
tier | our hype tier: hot · warm · cold · dust. The board folds dust by default. |
flags | price_switch — a free mint that turned paid while its window was open; locked — the contract refuses holders' transfers or marketplace approvals. |
updated_at | when 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.
| Code | Why |
|---|---|
400 bad filter · bad since | an unknown filter value or an unreadable since; the hint says what is accepted |
401 unauthorized | no key, an unknown key, or a key that went dark with the Cassette |
404 not on the board | the id is not in the slice (or was never there) |
405 method not allowed | anything but GET on these paths — the Allow header lists what works |
429 rate limit | more 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.
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).
| Step | Call |
|---|---|
| 1 · message | GET /api/v1/wl/nonce?address=0x… → message (OpenSea's SIWE text with a fresh nonce; nonces are single-use) |
| 2 · sign | your wallet signs the exact text with personal_sign (EIP-191); the private key never leaves you |
| 3 · pass | POST /api/v1/wl/pass {address, message, signature} → pass (3 days). Optional: step 4 accepts the signature directly and returns the pass too. |
| 4 · scan | POST /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 }
}
| Field | Meaning |
|---|---|
slugs | which collections to ask about (OpenSea slugs). Omitted — every collection on the board with a gated stage ahead, up to 130 per scan. |
listed | collections 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. |
failed | slugs OpenSea did not answer for this time — ask again later, silence is not a refusal. |
401 pass expired | the pass is dead — take a new message and sign again. |
| What | Limit |
|---|---|
| Scans | 100 an hour per key, whatever the number of slugs |
| Sign-ins and nonces | 150 an hour per key |
| Over the quota | 429 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.
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