Quick start
The Partner API is read-only and server-to-server. You call it from your own backend with a private key, from an IP address you have allowlisted with us. Never call it from a browser or a mobile app.
Base URL: https://api.nimbusmint.bot/partner/v1
Step 1: get your keys
Section titled “Step 1: get your keys”Request access with the form or a ticket in the NimbusMint Discord; we issue a private key for your server. The Get API keys page lists what to include. Then:
- Check your allowlist. The private key only works from the server IPs you sent us. See IP allowlist.
- Store the key as a secret on that server, for example in an environment variable called
NIMBUS_PRIVATE_KEY. It looks likenmk_live_<id>_<secret>. Never put it in a browser, a mobile app or a repository.
Step 2: check your setup
Section titled “Step 2: check your setup”GET /health needs the same key and allowlisted IP as every other route, so a 200 proves both are right. It
returns the key’s own details, including when it expires:
curl https://api.nimbusmint.bot/partner/v1/health \ -H "Authorization: Bearer $NIMBUS_PRIVATE_KEY"// Node 18+ (built-in fetch)const res = await fetch('https://api.nimbusmint.bot/partner/v1/health', { headers: { Authorization: `Bearer ${process.env.NIMBUS_PRIVATE_KEY}` },});console.log(res.status, await res.json());import osimport requests
res = requests.get( "https://api.nimbusmint.bot/partner/v1/health", headers={"Authorization": f"Bearer {os.environ['NIMBUS_PRIVATE_KEY']}"}, timeout=10,)print(res.status_code, res.json()){ "tenant": "example-partner", "key_id": "nmk_exampleid000", "scopes": ["read:hits"], "expires_at": "2027-10-01T00:00:00Z"}A 401 invalid_key means the key is wrong, revoked or expired. A 403 ip_not_allowed means the request did
not come from an allowlisted address. See Errors.
Step 3: read hits for a collection
Section titled “Step 3: read hits for a collection”A hit is one successful mint NimbusMint completed for a user of your app (booked through your app, receipt confirmed, not reverted). Ask for one contract and a start time; the window can span up to 31 days (currently):
curl -G https://api.nimbusmint.bot/partner/v1/hits \ -H "Authorization: Bearer $NIMBUS_PRIVATE_KEY" \ --data-urlencode "contract=0x000000000000000000000000000000000000dEaD" \ --data-urlencode "since=2026-10-01T00:00:00Z" \ --data-urlencode "top=25"const url = new URL('https://api.nimbusmint.bot/partner/v1/hits');url.search = new URLSearchParams({ contract: '0x000000000000000000000000000000000000dEaD', since: '2026-10-01T00:00:00Z', top: '25',});const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.NIMBUS_PRIVATE_KEY}` },});const hits = await res.json();res = requests.get( "https://api.nimbusmint.bot/partner/v1/hits", headers={"Authorization": f"Bearer {os.environ['NIMBUS_PRIVATE_KEY']}"}, params={ "contract": "0x000000000000000000000000000000000000dEaD", "since": "2026-10-01T00:00:00Z", "top": 25, }, timeout=10,)hits = res.json()The response (shortened):
{ "contract": "0x000000000000000000000000000000000000dead", "chain_ids": [33139], "since": "2026-10-01T00:00:00Z", "until": "2026-10-02T00:00:00Z", "total": { "hits": 36, "users": 11 }, "users": [ { "handle": "s*******", "user_id": "100000000000000001", "discord_id": "100000000000000001", "discord_username": "sam", "wallets": ["0x1111111111111111111111111111111111111111"], "hits": 9, "gas": { "mode": "manual", "max_fee_gwei": "3", "priority_gwei": "0.5" } }, { "handle": "k*******", "user_id": "100000000000000002", "discord_id": "100000000000000002", "discord_username": "kai", "wallets": ["0x2222222222222222222222222222222222222222"], "hits": 4, "gas": { "mode": "auto", "max_fee_gwei": null, "priority_gwei": null } } ], "others": { "users": 9, "hits": 23 }, "generated_at": "2026-10-02T00:00:03Z", "cache_ttl_s": 30}usersare listed by hits, most first. Each row identifies the user:user_id,discord_id,discord_usernameand thewalletsthat minted in the window.handleis a ready-made masked name; mask anything you show publicly (see the Terms).- For one row per mint (wallet, transaction hash, quantity, time), page through
GET /partner/v1/mintswith itscursor. otherssums everyone beyond thetoprows.- Cache the response yourself for
cache_ttl_sseconds instead of calling again (responses carryCache-Control: no-store, so no HTTP cache will do it for you).sinceanduntilare floored to the minute.
Every field is described in the API reference.
Example: a Discord /gas command
Section titled “Example: a Discord /gas command”A typical integration is a Discord bot whose /gas command shows mint activity for a collection. The bot
already counts hits from its own users and adds the NimbusMint cloud users underneath:
🖥️: 71 hits / ☁️: 36 hits☁️ s******* — 9 hits — ⛽ 3/0.5☁️ k******* — 4 hits — ⛽ auto…and 9 more cloud users — 23 hitsInside the /gas handler, call /hits with the collection’s contract and your own tracking start as
since, then render the rows. The bot runs on your server, so the private key never reaches Discord users.
# What the bot calls for each /gas, with the collection's contract and your tracking startcurl -G https://api.nimbusmint.bot/partner/v1/hits \ -H "Authorization: Bearer $NIMBUS_PRIVATE_KEY" \ --data-urlencode "contract=$CONTRACT" \ --data-urlencode "since=$TRACKING_SINCE" \ --data-urlencode "top=25"// discord.js v14: helpers for your /gas commandconst API = 'https://api.nimbusmint.bot/partner/v1';const cache = new Map(); // `${contract}|${since}` -> { data, expires }
export async function cloudHits(contract, since) { const key = `${contract.toLowerCase()}|${since}`; const cached = cache.get(key); if (cached && cached.expires > Date.now()) return cached.data;
const url = new URL(`${API}/hits`); url.search = new URLSearchParams({ contract, since, top: '25' }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.NIMBUS_PRIVATE_KEY}` }, }); if (!res.ok) return null; // show your own numbers only; see Errors
const data = await res.json(); cache.set(key, { data, expires: Date.now() + data.cache_ttl_s * 1000 }); return data;}
export function cloudLines(data, ownHits) { const gas = (g) => (g.mode === 'auto' ? '⛽ auto' : `⛽ ${g.max_fee_gwei}/${g.priority_gwei}`); const lines = [`🖥️: ${ownHits} hits / ☁️: ${data.total.hits} hits`]; for (const u of data.users) lines.push(`☁️ ${u.handle} — ${u.hits} hits — ${gas(u.gas)}`); if (data.others.users > 0) { lines.push(`…and ${data.others.users} more cloud users — ${data.others.hits} hits`); } return lines;}
// In the handler:// const data = await cloudHits(contract, trackingSince.toISOString());// const body = data ? cloudLines(data, ownHits).join('\n') : `🖥️: ${ownHits} hits`;// await interaction.reply({ embeds: [{ title: 'Gas', description: body }] });# discord.py: helpers for your /gas commandimport osimport timeimport requests
API = "https://api.nimbusmint.bot/partner/v1"_cache = {} # (contract, since) -> (expires, data)
def cloud_hits(contract: str, since: str): key = (contract.lower(), since) cached = _cache.get(key) if cached and cached[0] > time.time(): return cached[1]
res = requests.get( f"{API}/hits", headers={"Authorization": f"Bearer {os.environ['NIMBUS_PRIVATE_KEY']}"}, params={"contract": contract, "since": since, "top": 25}, timeout=5, ) if not res.ok: return None # show your own numbers only; see Errors
data = res.json() _cache[key] = (time.time() + data["cache_ttl_s"], data) return data
def cloud_lines(data, own_hits: int): def gas(g): return "⛽ auto" if g["mode"] == "auto" else f"⛽ {g['max_fee_gwei']}/{g['priority_gwei']}"
lines = [f"🖥️: {own_hits} hits / ☁️: {data['total']['hits']} hits"] lines += [f"☁️ {u['handle']} — {u['hits']} hits — {gas(u['gas'])}" for u in data["users"]] if data["others"]["users"] > 0: lines.append(f"…and {data['others']['users']} more cloud users — {data['others']['hits']} hits") return lines