Security & credentials

Authentication

Snowcone has one public credential and a few secret ones. This is the whole map — which credential to reach for, what it looks like, and where it goes.

The one rule

Exactly one credential is safe in a browser or a URL: the publishable Shop ID. Everything that starts with sk_ or scsec_ is a secret — it lives on your server and never ships to the client.

The credential map

CredentialLooks likeLivesUsed forGet it from
Publishable Shop IDab3dPq7Rmspublic
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 keysk_…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 secretscsec_…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.

html
<!-- 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.

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

tsx
// 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.

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