---
title: "Quick start"
description: "Make your first Partner API call, then add NimbusMint hits to a Discord /gas command."
canonical: "https://docs.nimbusmint.bot/quick-start/"
---
> Index: https://docs.nimbusmint.bot/llms.txt

# 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

Request access with the form or a ticket in the NimbusMint Discord; we issue a private key for your server. The
[Get API keys](https://docs.nimbusmint.bot/get-keys.md) page lists what to include. Then:

1. **Check your allowlist.** The private key only works from the server IPs you sent us. See
   [IP allowlist](https://docs.nimbusmint.bot/authentication.md#ip-allowlist).
2. **Store the key as a secret** on that server, for example in an environment variable called
   `NIMBUS_PRIVATE_KEY`. It looks like `nmk_live_<id>_<secret>`. Never put it in a browser, a mobile app or a
   repository.

> **Keep the private key on your server:** Anyone holding the private key can read your data until it is revoked. If it leaks, ask us to revoke it;
> revocation takes effect within 10 seconds.

## 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

```sh
curl https://api.nimbusmint.bot/partner/v1/health \
  -H "Authorization: Bearer $NIMBUS_PRIVATE_KEY"
```

### Node

```js
// 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());
```

### Python

```python
import os
import 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())
```

```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](https://docs.nimbusmint.bot/errors.md).

## 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

```sh
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"
```

### Node

```js
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();
```

### Python

```python
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):

```json
{
  "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
}
```

- `users` are listed by hits, most first. Each row identifies the user: `user_id`, `discord_id`,
  `discord_username` and the `wallets` that minted in the window. `handle` is a ready-made masked name; mask
  anything you show publicly (see the [Terms](https://nimbusmint.bot/terms/)).
- For one row per mint (wallet, transaction hash, quantity, time), page through
  [`GET /partner/v1/mints`](https://docs.nimbusmint.bot/api/operations/getmints.md) with its `cursor`.
- `others` sums everyone beyond the `top` rows.
- Cache the response yourself for `cache_ttl_s` seconds instead of calling again (responses carry
  `Cache-Control: no-store`, so no HTTP cache will do it for you). `since` and `until` are floored to the minute.

Every field is described in the [API reference](https://docs.nimbusmint.bot/api/operations/gethits.md).

## 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:

```text
🖥️: 71 hits / ☁️: 36 hits
☁️ s******* — 9 hits — ⛽ 3/0.5
☁️ k******* — 4 hits — ⛽ auto
…and 9 more cloud users — 23 hits
```

Inside 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.

### curl

```sh
# What the bot calls for each /gas, with the collection's contract and your tracking start
curl -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"
```

### Node

```js
// discord.js v14: helpers for your /gas command
const 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 }] });
```

### Python

```python
# discord.py: helpers for your /gas command
import os
import time
import 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
```

> **Tip:** `requests` blocks; in a discord.py bot, run `cloud_hits` with `await asyncio.to_thread(cloud_hits, ...)` or
> use an async HTTP client so the bot stays responsive.

> **Note:** If the API is unavailable or rate-limited, show your own numbers and skip the cloud rows rather than failing
> the command. The cache above keeps repeated `/gas` calls inside the [rate limit](https://docs.nimbusmint.bot/rate-limits.md).
