Security & credentials
Authentication
The one rule
Exactly one credential is safe in a browser or a URL: the publishable Shop ID. Everything that starts withsk_ or scsec_ is a secret — it lives on your server and never ships to the client.The credential map
| Credential | Looks like | Lives | Used for | Get it from |
|---|---|---|---|---|
| Publishable Shop ID | ab3dPq7Rms | public Anywhere (browser, URL) | The &shop= on every render/buy URL. Safe to expose. | Mint a sandbox shop — or it was created at signup: copy it at snowcone.app/studio/api-keys. |
| Secret API key | sk_… | secret Your server only | x-api-key (or Authorization: Bearer) header on api.snowcone.app calls — Orders, grants, uploads, AI. Scoped. | snowcone.app/studio/api-keys. |
| Shop secret | scsec_… | secret Your server only | HMAC-signs a mockup URL (the signature rides in the URL — it’s never sent as a header). | The mint response, or signing at scale. |
| Realtime grant | (opaque, 60s) | public Browser, briefly | Opens the render socket: wss://cdn.snowcone.app/realtime?token=…. Expires fast. | POST /realtime/grant — realtime render. |
| Claim token | (opaque) | secret Wherever you minted | Polls/claims a sandbox shop (binds it to a human’s account for payouts). | The mint response’s claim object. |
Which one do I need?
- Render or sell a mockup → just the Shop ID. No key, no signup. Get started.
- Call api.snowcone.app (Orders, uploads, AI, mint a realtime grant) → secret API key (
sk_) with the right scope. Secret API keys. - Stop a leaked Shop ID from rendering arbitrary art → shop secret (
scsec_) to sign URLs. Signed URLs. - Live canvas preview over a socket → a realtime grant minted server-side. Realtime render.
Publishable Shop ID
The Shop ID is your public identity — it routes a sale to your account and resolves pricing. It is meant to be visible, exactly like a Cloudinary cloud name or a Stripe publishable key. A leaked Shop ID can’t move money or read your data; the worst it does is render art on your catalog, which signing and asset-origin limits shut down.
<!-- The Shop ID is public — it goes straight in the URL, like a Cloudinary cloud name. -->
<img src="https://img.snowcone.app/BEEB77?asset=https%3A%2F%2Fcdn.example.com%2Fart.png&shop=YOUR_SHOP_ID" />Secret API key (sk_)
A server-side key, Stripe-shaped (sk_…), that authorizes calls to api.snowcone.app. Each key is owned by your organization (the billing boundary), is shop-scoped, and carries scopes that gate exactly which endpoints it may call. Send it as x-api-key or Authorization: Bearer. Never put it in client code.
# Secret API key (sk_…) — server-side only, in the x-api-key header.
curl https://api.snowcone.app/orders -X POST \
-H "x-api-key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{ "json": { "items": [] } }'Shop secret (scsec_)
The shop secret is the odd one out: it is a secret, but you never send it to Snowcone. You use it locally to compute an HMAC signature over a mockup URL; that signature rides in the URL and the edge verifies it. So scsec_ and sk_ are both secret, but a shop secret signs while a secret key authenticates a request. Full how-to on signing at scale.
// Shop secret (scsec_…) — server-side only. It HMAC-signs a mockup URL;
// it is NOT sent as a header. The signature travels in the URL.
import { getMockupUrl } from "@snowcone-app/sdk";
const src = getMockupUrl(artwork, "BEEB77", {
shop: process.env.SNOWCONE_SHOP_ID,
secret: process.env.SNOWCONE_SHOP_SECRET, // appends &signature=…
});Realtime grant
A short-lived (~60s) token you mint on your server and pass to the browser to open the realtime render socket. It exists so a long-lived secret never touches the client. Sandbox shops can mint a grant from the publishable shop.id alone; production shops authenticate the mint with an sk_ key scoped mockups:realtime.
# Realtime grant — a short-lived token you mint server-side, then hand to the
# browser to open the render socket. The publishable shop.id alone works for
# sandbox shops; production shops send an sk_ key with the mockups:realtime scope.
curl https://api.snowcone.app/realtime/grant -X POST \
-H "Content-Type: application/json" \
-d '{ "shop": "YOUR_SHOP_ID" }'
# → { "token": "…", "expiresAt": 1749… } → wss://cdn.snowcone.app/realtime?token=…
# expiresAt is epoch SECONDS (an absolute time), not a TTL — renew before it.Claim token
When you mint a sandbox shop you get back a claim object — its token is a capability that lets you poll claim status (GET /shops/claim) and hand the shop to a human to bind it to their account for payouts. It’s only relevant during the mint-and-claim handshake; see the flow on Get started and the endpoints in the API reference.

