Skip to content

GraveMint v1 API

Base URL — https://api.solanadeads.com/gravemint/v1

Every example on this page is real

These are actual responses recorded from production, not illustrations. If a field appears here it exists; if a status code appears here it is one the API really returns. Regenerate with npm run sync.

Authentication

Two ways in, and exactly one of them is right for your situation.

headerbounded byuse it
PublishableX-API-Key: gm_pub_…the origin allow-list on the keyin your web page
SecretX-API-Key: gm_live_…secrecyfrom your server

A publishable key is public by construction — it ships in your bundle and anyone can read it. What protects it is that it only works from origins registered on the key. So the job is not hiding it, it is telling us every origin you serve from, staging and preview domains included.

Never put a gm_live_ key in browser code

It has no origin binding, so a leak is unbounded until we revoke it.

The converse also bites: a gm_pub_ key does not work server-side. It is admitted by matching the request's Origin against the key's list, and a Node process sends no Origin — so it is refused every time, with no way to satisfy it.

Try it live

Every endpoint below is a working request builder. Fill in the parameters, press Send, and you get both halves of the exchange side by side — the exact request that left your browser, and exactly what came back, with status and timing.

Why every console here is a DEVNET drop

Each console runs against 7zn5qa (DEAD DAWGS — ONCHAIN TEST), our own mpl-core drop on solana-devnet, using a gm_test_ sandbox key. Three reasons, all measured rather than cautious:

  • Nothing here can touch a real mint. A gm_test_ key is refused on any production chain (403 SANDBOX_KEY_ON_MAINNET), server-side. That is what makes a live prepare-mint console publishable at all.
  • The examples stay working. A production drop decays under the documentation. These examples were previously captured from a mainnet drop, and by the time anyone looked it read percentMinted: 100 — the reference example was a sold-out drop.
  • The data is better. That mainnet drop's gallery returned total: 0, so the gallery console demonstrated nothing. This one has 867 items.

Publishable keys are public by construction — they ship in a partner's bundle and anyone can read them. What bounds this one is server-side: an origin allow-list (only docs.solanadeads.com), a collection scope (this one drop), and a chain restriction (devnet only). Secrecy is not what protects it, so printing it costs nothing.

Your own integration would use a gm_pub_ key — same origin-binding and scoping, issued for your drop. See Keys and origins.

execute-mint is shown, not sent

prepare-mint below does send — safely, because it targets the devnet drop with the sandbox key, and because the balance pre-flight runs before the NFT lock, so the default request reserves nothing.

execute-mint cannot send, and not for a safety reason: it needs signed bytes from a wallet, which a documentation page has no way to produce. Its builder shows exactly what to send.

Discovery

GET/

No parameters. Confirms the surface and its stability contract.

Sent

GET https://api.solanadeads.com/gravemint/v1/
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/'

Received

Nothing yet — press Send.

Read a drop

Address a drop by its short id or its on-chain address — never an internal uuid, which is not a public identifier and is refused.

GET/collections/{identifier}

Everything needed to build a mint page, in one call.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa'

Received

Nothing yet — press Send.

Where are the sales phases?

Inside that same response — there is deliberately no separate phases endpoint. /collections/{identifier} returns phases: { active, upcoming, all }, each with its window, supply, per-wallet cap, gating flag and resolved priceDisplay, alongside serverTime so a countdown does not drift.

That is the whole point of the read model: live drops average five phases (max 38), so a page needs the phase list, the active one and the next transition together. Splitting them across calls would mean rendering a page from several inconsistent snapshots.

Press Send on the console above and look for phases in the response.

Do not compute the active phase yourself

There are three notions of "active" in GraveMint's schema and they do not agree. v1 exposes exactly one — enabled plus the date window — and status on each phase is the server's verdict. Phase transitions are driven by server-side timers, not lazily on read, so a client cannot derive the next one either. Poll, or subscribe.

Check eligibility

Server-side verdict for one wallet against one phase. Gating criteria stay on our side — you receive the decision, never the allowlist.

GET/collections/{collectionId}/eligibility/{phaseId}/{walletAddress}

The verdict deliberately EXCLUDES the time window — it answers 'will this wallet qualify', with phase.hasStarted / hasEnded reported separately, so an upcoming phase does not read as 'not eligible'.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/eligibility/8426923e-0d64-480d-925f-90324c2dedf5/11111111111111111111111111111111
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/eligibility/8426923e-0d64-480d-925f-90324c2dedf5/11111111111111111111111111111111'

Received

Nothing yet — press Send.

The rest of the drop

Everything else a mint page is built from, scoped to the key the same way.

Two consoles here return an error on purpose

The key these consoles use is issued for one drop. Put deads in any Collection box and you get 403 COLLECTION_NOT_IN_SCOPE — that is the confinement working, not a fault. Likewise the uuid option above returns 404, because an internal id is not a public identifier.

Every console's default values return 200.

GET/collections/{identifier}/gallery

The drop's art. 867 items here, so paging is real.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/gallery?limit=3
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/gallery?limit=3'

Received

Nothing yet — press Send.
GET/collections/{identifier}/recently-minted

The live 'just minted' feed.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/recently-minted?limit=3
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/recently-minted?limit=3'

Received

Nothing yet — press Send.
GET/collections/{collectionId}/wallet-mints/{walletAddress}

How many this wallet minted and how many remain, per phase. Do NOT derive this from eligibility: it INCLUDES bonus mints (BOGO, bounty) and the eligibility engine excludes them, so a figure you compute yourself is wrong for any wallet that took one.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/wallet-mints/11111111111111111111111111111111
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/wallet-mints/11111111111111111111111111111111'

Received

Nothing yet — press Send.
GET/collections/{identifier}/bounty

Bounty summary. This drop has none configured, so you get the empty shape — which is the response you have to handle, not an error.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/bounty
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/bounty'

Received

Nothing yet — press Send.
GET/collections/{identifier}/bounty-prizes

The public prize table. Returns enabled: false when there is no bounty.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/bounty-prizes
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/bounty-prizes'

Received

Nothing yet — press Send.

Claim codes

17.6% of live drops use them, so a mint page that ignores claim codes cannot render one in six drops correctly.

GET/collections/{collectionId}/claim-codes

Does this drop use codes, and of what kind?

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes'

Received

Nothing yet — press Send.
GET/collections/{collectionId}/claim-codes/benefits/{walletAddress}

What a wallet is already entitled to from codes it has redeemed.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes/benefits/11111111111111111111111111111111
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes/benefits/11111111111111111111111111111111'

Received

Nothing yet — press Send.
POST/collections/{collectionId}/claim-codes/validate

READ-ONLY — checking a code does not redeem it or consume a use.

Sent

POST https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes/validate
X-API-Key: gm_test_47716d…
Content-Type: application/json

{
  "code": "TRY-A-CODE"
}
as curl
curl -s -X POST \
  -H 'X-API-Key: gm_test_47716d…' \
  -H 'Content-Type: application/json' \
  -d '{"code":"TRY-A-CODE"}' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/claim-codes/validate'

Received

Nothing yet — press Send.

Pricing

Never render phase.price for a pegged phase

It is Dutch-resolved but not peg-resolved, so on a pegged phase it is a stale cached token amount. One live phase carries 110000 TOUCHGRASS in that field. Use these.

GET/token-prices

Platform token prices — what an SPL-priced drop is worth in USD. Global: it names no collection, so it carries no drop data and needs no collection scope.

Sent

GET https://api.solanadeads.com/gravemint/v1/token-prices
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/token-prices'

Received

Nothing yet — press Send.
GET/phases/{phaseId}/pegged-price

The resolved amount for a USD- or native-pegged phase. Scoped through the phase's own collection — a phase id is not a way around collection scope.

Sent

GET https://api.solanadeads.com/gravemint/v1/phases/8426923e-0d64-480d-925f-90324c2dedf5/pegged-price
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/phases/8426923e-0d64-480d-925f-90324c2dedf5/pegged-price'

Received

Nothing yet — press Send.
GET/phases/{phaseId}/dutch-price

The live price of a dynamic Dutch phase, which moves with time.

Sent

GET https://api.solanadeads.com/gravemint/v1/phases/8426923e-0d64-480d-925f-90324c2dedf5/dutch-price
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/phases/8426923e-0d64-480d-925f-90324c2dedf5/dutch-price'

Received

Nothing yet — press Send.

Supply, traits and social proof

GET/collections/{collectionId}/availability

How much is actually mintable right now, accounting for held reservations.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/availability
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/availability'

Received

Nothing yet — press Send.
GET/collections/{identifier}/traits

Trait names and values, for filtering a gallery.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/traits
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/traits'

Received

Nothing yet — press Send.
GET/collections/{identifier}/all-minted

Every minted item in the drop.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/all-minted
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/all-minted'

Received

Nothing yet — press Send.
GET/collections/{identifier}/top-holders

Largest holders.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/top-holders
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/top-holders'

Received

Nothing yet — press Send.
GET/collections/{identifier}/top-minters

Who minted the most.

Sent

GET https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/top-minters
X-API-Key: gm_test_47716d…
as curl
curl -s -X GET \
  -H 'X-API-Key: gm_test_47716d…' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/top-minters'

Received

Nothing yet — press Send.

What your key can reach

Every route that names a collection is scoped to the collections your key was issued for. Measured against production with a key scoped to one drop, asking for another:

RequestResult
GET /collections/{other}403 COLLECTION_NOT_IN_SCOPE
GET /collections/{other}/gallery403
GET /collections/{other}/recently-minted403
GET /collections/{other}/bounty · /bounty-prizes403
GET /collections/{other}/wallet-mints/{wallet}403
GET /collections/{other}/eligibility/{phase}/{wallet}403
POST /prepare-mint with another drop's id403
GET / (version — names no collection)200

The refusal is deliberately identical whether or not the drop exists, so an unauthorised caller learns only that this key cannot read it — never whether the collection is real.

Two further bounds apply on top of scope: a key with an origin list is refused from any other origin (403 ORIGIN_NOT_ALLOWED, including a request with no Origin at all, which is why a browser key cannot be used server-side), and a sandbox key is refused on any production chain (403 SANDBOX_KEY_ON_MAINNET).

Prepare a mint

This one really sends, against the same devnet drop with the sandbox key.

With the default wallet you get 400 INSUFFICIENT_BALANCE — and that is the point: the balance pre-flight runs before the NFT lock, so the default request is a complete round-trip that reserves nothing. Click it as often as you like. Paste a funded devnet wallet to see real transactions and sessions come back; that request does take a three-minute lock on devnet supply, which is what prepare is for.

POST/prepare-mint

Returns transactions for your signer. You sign them; WE broadcast.

Sent

POST https://api.solanadeads.com/gravemint/v1/prepare-mint
X-API-Key: gm_test_47716d…
Content-Type: application/json

{
  "collectionId": "2e239364-fc9b-4191-ba62-b57dbac76394",
  "phaseId": "8426923e-0d64-480d-925f-90324c2dedf5",
  "walletAddress": "11111111111111111111111111111111",
  "quantity": "1"
}
as curl
curl -s -X POST \
  -H 'X-API-Key: gm_test_47716d…' \
  -H 'Content-Type: application/json' \
  -d '{"collectionId":"2e239364-fc9b-4191-ba62-b57dbac76394","phaseId":"8426923e-0d64-480d-925f-90324c2dedf5","walletAddress":"11111111111111111111111111111111","quantity":"1"}' \
  'https://api.solanadeads.com/gravemint/v1/prepare-mint'

Received

Nothing yet — press Send.

Execute a mint

POST/execute-mintnot sent

Send the SIGNED BYTES. A transactionHash here is refused with CLIENT_BROADCAST_NOT_ALLOWED.

Shown, not sent — execute needs SIGNED BYTES from a wallet, which a docs page cannot produce. Nothing unsafe; there is simply nothing to send.

Sent

POST https://api.solanadeads.com/gravemint/v1/execute-mint
X-API-Key: gm_test_47716d…
Content-Type: application/json

{
  "sessionId": "<from prepare-mint>",
  "signedTransaction": "<base64 signed tx>"
}
as curl
curl -s -X POST \
  -H 'X-API-Key: gm_test_47716d…' \
  -H 'Content-Type: application/json' \
  -d '{"sessionId":"<from prepare-mint>","signedTransaction":"<base64 signed tx>"}' \
  'https://api.solanadeads.com/gravemint/v1/execute-mint'

Received

This request is shown, not sent.

The console below issues a real request from your browser to the production API, using a publishable key scoped to one collection we own.

Why we can print a key on a public page

gm_pub_ keys are public by construction — they ship in a partner's bundle and anyone can read them. What bounds this one is the origin allow-list on the key (it only works from docs.solanadeads.com) and its collection scope (it can read exactly one drop, ours). Secrecy is not what protects a publishable key, so printing it costs nothing.

Reads only, and that is deliberate

There is no prepare-mint button here. prepare takes a 3-minute NFT lock and reserves against the BOGO and claim-code ledgers, so a public "Try it" on it would let anyone grief a live drop's supply. A devnet sandbox key is the right way to make the full mint flow explorable, and is tracked separately.

Discovery

Confirms the surface and its stability contract.

bash
curl -s -H 'X-API-Key: gm_pub_your_key' \
  'https://api.solanadeads.com/gravemint/v1/'
json
{
  "success": true,
  "version": "v1",
  "stability": "additive-only",
  "docs": "https://www.npmjs.com/package/@solanadeads/gravemint"
}

Reading a drop

One call returns everything needed to build a mint page: the collection, every phase with its resolved price, live supply, and our clock for countdowns.

Address a drop by its short id or on-chain address — never a uuid

/v1/collections/deads and /v1/collections/<collection_address> work. The internal database id is not a public identifier and is not accepted.

GET /v1/collections/:identifier

bash
curl -s -H 'X-API-Key: gm_pub_your_key' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa'
json
{
  "success": true,
  "collection": {
    "id": "2e239364-fc9b-4191-ba62-b57dbac76394",
    "shortId": "7zn5qa",
    "name": "DEAD DAWGS - ONCHAIN TEST",
    "symbol": "ONCHAIN1",
    "description": "Testing",
    "image": "https://gravemint-storage.s3.us-east-1.amazonaws.com/launchpad-collection-images/1774886146380/collection.png",
    "bannerImage": "https://gravemint-storage.s3.us-east-1.amazonaws.com/launchpad-collection-images/1774886157110/banner.png",
    "chain": "solana-devnet",
    "collectionAddress": "7mS96x56GoeE23R8DRXYusjecFY9udEw2NTnedqJ1Ed5",
    "contractAddress": null,
    "externalUrl": null,
    "twitter": null,
    "discord": null,
    "isVerified": false
  },
  "stats": {
    "totalSupply": 888,
    "mintedCount": 21,
    "availableCount": 867,
    "percentMinted": 2.364864864864865
  },
  "phases": {
    "all": [
      {
        "id": "8426923e-0d64-480d-925f-90324c2dedf5",
        "name": "Public",
        "status": "active",
        "startDate": "2026-03-30T16:02:00.000Z",
        "endDate": null,
        "priceDisplay": {
          "kind": "amount",
          "amount": 1,
          "currency": "USD",
          "isFree": false
        },
        "bogo": null,
        "isGated": false,
        "maxPerWallet": 100,
        "maxPerTransaction": 10,
        "phaseSupply": null,
        "msUntilStart": null,
        "msUntilEnd": null
      }
    ],
    "active": [
      {
        "id": "8426923e-0d64-480d-925f-90324c2dedf5",
        "name": "Public",
        "status": "active",
        "startDate": "2026-03-30T16:02:00.000Z",
        "endDate": null,
        "priceDisplay": {
          "kind": "amount",
          "amount": 1,
          "currency": "USD",
          "isFree": false
        },
        "bogo": null,
        "isGated": false,
        "maxPerWallet": 100,
        "maxPerTransaction": 10,
        "phaseSupply": null,
        "msUntilStart": null,
        "msUntilEnd": null
      }
    ],
    "upcoming": []
  },
  "capabilities": {
    "requiresFeatures": [
      "claim_codes",
      "nft_selection"
    ],
    "supportedBySurface": false,
    "mintUrl": "https://gravemint.io/mint/7zn5qa"
  },
  "serverTime": "2026-08-31T12:17:45.816Z"
}

Price is a shape, not a number

priceDisplay is a resolved value, computed by the same code that prices the real mint. Do not reassemble it from raw fields — the cascade has several stages that overwrite each other, and the last one that applies decides the charge.

kindmeaningrender
amounta settled figurethe number and its currency
rangevaries within the phase"from X"
hiddenthe creator chose to hide it until eligibledo not guess — say it is hidden
unknowngenuinely not computable for an anonymous callerdo not render 0

Never render 0 as "Free"

unknown and hidden are not zero. A drop that shows "Free" because a price could not be resolved is the failure this shape exists to prevent.

Eligibility

Server-side verdict for one wallet against one phase. Gating criteria stay on our side; you receive the decision, never the allowlist.

GET /v1/collections/:collectionId/eligibility/:phaseId/:wallet

bash
curl -s -H 'X-API-Key: gm_pub_your_key' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa/eligibility/8426923e-0d64-480d-925f-90324c2dedf5/11111111111111111111111111111111'
json
{
  "success": true,
  "gated": false,
  "phase": {
    "id": "8426923e-0d64-480d-925f-90324c2dedf5",
    "name": "Public",
    "startDate": "2026-03-30T16:02:00+00:00",
    "endDate": null,
    "hasStarted": true,
    "hasEnded": false
  },
  "address": "11111111111111111111111111111111",
  "message": "This phase is open to everyone — no allowlist or holder requirements."
}

Errors

Coded, and the code is the part you branch on. The human sentence may be reworded at any time; the code will not.

No credential at all

No Origin, no key.

bash
curl -s  \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa'
json
{
  "success": false,
  "error": "This request could not be authorised.",
  "code": "API_KEY_REQUIRED"
}

An origin we do not recognise

Distinguished from the above on purpose — this one is a configuration fix on our side, and conflating the two turns a five-minute answer into a support thread.

bash
curl -s -H 'Origin: https://partner.example' \
  'https://api.solanadeads.com/gravemint/v1/collections/7zn5qa'
json
{
  "success": false,
  "error": "This request could not be authorised.",
  "code": "ORIGIN_NOT_ALLOWED"
}
codestatusmeaning
API_KEY_REQUIRED401no key and no recognised origin
API_KEY_INVALID401the key did not match a live record
ORIGIN_NOT_ALLOWED403the origin is not on this key
COLLECTION_NOT_IN_SCOPE403this key is not issued for that drop
AUTH_UNAVAILABLE503we could not check — retry; never treat as denial
CLIENT_BROADCAST_NOT_ALLOWED400you sent a transaction hash. See below
CHAIN_NOT_SUPPORTED_BY_SURFACE400v1 mints Solana only

The one rule

You sign. We broadcast.

prepare-mint returns transactions. Your signer signs them. The signed bytes come back to us and we submit them.

Never broadcast a mint transaction yourself. /v1/execute-mint refuses a request carrying transactionHash with CLIENT_BROADCAST_NOT_ALLOWED, and there is no flag to turn that off — it is what lets us verify that what reaches the chain is what we priced and reserved.

SDK pages are generated from the published npm tarballs and cannot drift.