Keys and origins
Partner keys are invite-only. Tell us the drops you are integrating and the domains you serve from, and we issue a key scoped to exactly that.
The three classes
| prefix | where it lives | bounded by | |
|---|---|---|---|
| Publishable | gm_pub_ | your browser bundle | origin allow-list + collection scope |
| Secret | gm_live_ | your server only | collection scope. Nothing bounds the origin |
| Sandbox | gm_test_ | browser, test networks | origin + scope + refused on production chains |
The prefix is deliberate: a leaked key is identifiable on sight, in a log, a paste or a support thread. Someone asking "why does my key 403 on mainnet" can see the answer in the key itself.
A publishable key is public by construction
It ships in your bundle and anyone can read it. That is fine, and it is why we print one on the API reference.
Secrecy is not what protects it. Three server-side bounds are:
- Origin — it works only from the domains on its allow-list
- Collection scope — it reads only the drops it was issued for
- Chain — a sandbox key is refused on any production chain
Take any one away and it is no longer safe to publish. So keep all three accurate.
gm_live_ never reaches a browser
Public build prefixes inline into the bundle
Anything named NEXT_PUBLIC_*, VITE_*, REACT_APP_* or EXPO_PUBLIC_* is inlined into the client bundle by definition. A secret key there is a full partner credential published to every visitor, and no origin bounds a gm_live_ key.
Server-side: process.env.GRAVEMINT_API_KEY, unprefixed.
The mirror-image mistake is using a gm_pub_ key from your server. Browsers set Origin; servers do not, and a key with an origin list refuses a request that carries none. That is correct behaviour, not a bug — origin binding is the whole point.
Registering origins
Register every origin you serve from:
- production —
https://yourdrop.io www.and apex if both resolve — they are different origins- staging —
https://staging.yourdrop.io - preview deploys — Vercel and Netlify give each build its own hostname. Ask us for a wildcard on your preview domain rather than registering them one at a time.
An origin is scheme + host + port. http:// and https:// are different origins, and so is a non-default port.
Scope
A key is issued for specific drops. Reading another returns 403 COLLECTION_NOT_IN_SCOPE, and the refusal is identical whether or not that drop exists — you learn only that this key cannot read it.
An unscoped key reaches nothing. A partner key with no collections is a configuration error, not a wildcard, and it is treated as one.
Scope covers every route that names a collection, including reads. There is no endpoint that returns another partner's drop.
Rate limits
Published floors, not reserved ceilings — we will not lower them without telling you, because a pinned client cannot be asked to redeploy.
| limit | scope | |
|---|---|---|
| reads | 600 / min | per IP |
prepare + execute | 60 / min combined | per key |
prepare, per collection | 30 / 10s | per collection, shared with gravemint.io |
A mint needs one prepare and one execute from the same bucket, so the sustained ceiling is roughly 30 mints per minute per key. And on a hot drop the per-collection cap binds first, because you are competing with every first-party collector.
If a key leaks
Tell us and we revoke it. Revocation is immediate.
It does not undo mints already made with it — but we can tell you exactly what those were, because every mint records the key that made it.
Rotating is issue-new → deploy → revoke-old, in that order, so you never have a window with no working key.
Sandbox keys
Ask for a gm_test_ key to build against a devnet drop. It is refused on any production chain, so it cannot touch a real mint even by mistake — which is what makes it safe to put a live mint console on a public page, as the API reference does.
