Skip to content

Troubleshooting

Ordered by how often they come up.

403 ORIGIN_NOT_ALLOWED

Your key is valid; the origin is not registered.

  • Register every origin you serve from — production, staging, preview deploys. Vercel and Netlify preview URLs are distinct origins and each needs registering, or ask us for a wildcard on your preview domain.
  • A request with no Origin header is refused too. That is not a bug: a gm_pub_ key is bound to an origin, so using it server-side (where browsers do not set Origin) has nothing to bind. Use a gm_live_ key on your server.
  • The refusal is deliberately distinct from API_KEY_REQUIRED so you can tell "I forgot my key" from "my domain is not registered" — the second is a one-line fix on our side.

403 COLLECTION_NOT_IN_SCOPE

Your key was issued for specific drops and this is not one of them.

The refusal is identical whether or not the collection exists — you learn only that this key cannot read it, never whether the drop is real. So this is also what you get for a typo'd identifier.

An unscoped key reaches nothing at all. A partner key with no collections is a configuration error, not a wildcard.

403 SANDBOX_KEY_ON_MAINNET / LIVE_KEY_ON_TESTNET

Key class and chain must match. A gm_test_ sandbox key works only on test networks; a production key works only on production chains.

The symmetry is deliberate. A live key on devnet is not dangerous, but silently allowing it is how you end up testing with your production credential and believing the sandbox works.

404 COLLECTION_NOT_FOUND on an id you know exists

You are almost certainly passing the internal uuid. That is not a public identifier and is refused on purpose.

Use the short id or the on-chain address.

The price is wrong, or shows 0

You are reading a raw field instead of priceDisplay.

  • priceDisplay is a union, not a number — see the read model.
  • phase.price is Dutch-resolved but not peg-resolved, so on a pegged phase it is a stale cached token amount.
  • Rendering 0 as "Free" is the exact bug the resolver exists to prevent. Check kind === 'amount' && isFree instead.

"Mints remaining" is wrong for some wallets

You derived it from the eligibility verdict. walletMints includes bonus mints (BOGO, bounty); the eligibility engine excludes them. Call walletMints and use what it returns.

CLIENT_BROADCAST_NOT_ALLOWED

Your code broadcast the transaction itself, or sent a transactionHash. Sign the bytes prepare returned and send them back as signedTransaction — see Wallets and signing.

TX_MODIFIED

The bytes we were asked to broadcast differ from the bytes we built: different recipient, different amount, or added instructions.

Never retry this

Re-submitting the same bytes cannot succeed and should not. Discard the session and start over with a fresh prepare. If it recurs, something is modifying transactions between us and the wallet, and that is worth telling us about.

SESSION_EXPIRED, or a mint that 409s

prepare sessions are short-lived and single-use. An expired signature needs a fresh prepare; a replayed session loses to an atomic claim and returns 409. Neither is retryable as-is.

err.code is undefined on a failed mint

You minted more than one, which is a batch — every Solana standard mints one NFT per transaction. Batch codes live in results[].errorCode:

ts
const code = err.code ?? err.results?.find((r) => r.errorCode)?.errorCode;

429, or mints are throttled

The SDK honours Retry-After and retries automatically. Two sharing details bite in practice:

  • prepare and execute share one 60/min bucket per key, and a mint needs one of each — so the sustained ceiling is roughly 30 mints per minute per key.
  • The per-collection prepare cap is shared with gravemint.io. On a hot drop your prepares compete with every first-party collector's, so 60/min is a ceiling you may not reach, not a reservation.

Ask before engineering around a limit.

503 AUTH_UNAVAILABLE

We could not check your key, so we failed closed. This is not a rejection of your credential — retry with backoff.

Nothing appears in your network tab at all

A blocked CORS preflight never leaves the browser, so it looks like an outage rather than a refusal. Check that the origin is registered, and note that a 204 on a preflight is not evidence of success — only an access-control-allow-origin header is.

Still stuck

Tell us the error code, the key prefix (gm_pub_ / gm_live_ / gm_test_, never the key itself), the origin, and the collection identifier. Those four make almost every case answerable immediately.

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