@snowcone-app/sdk
Building with an AI agent? Read the complete agent-facing docs in one fetch — https://developers.snowcone.app/llms-full.txt — before integrating. It's the source of truth; grep it before assuming a field or behavior is undocumented.
JavaScript/TypeScript SDK for product mockups and print-on-demand
A small, isomorphic SDK for fetching product data and building real-time merchandise mockup URLs. Use it to visualize artwork on t-shirts, posters, mugs, and more — in the browser or on the server.
Full documentation: developers.snowcone.app/sdk
Installation
npm install @snowcone-app/sdk
# or
yarn add @snowcone-app/sdk
# or
pnpm add @snowcone-app/sdkQuick Start
import { getProduct, listProducts, getMockupUrl } from '@snowcone-app/sdk';
// 1. Find a product (public catalog read — no key needed)
const products = await listProducts({ limit: 10 });
const product = await getProduct('BEEB77'); // by id or slug
// 2. Build a mockup URL. A mockup is a PUBLIC image URL — getMockupUrl is a
// pure, synchronous builder (no await, no fetch, no secret). The result
// drops straight into an <img>. Code-first: productCode, then options.
const url = getMockupUrl('hoodie-black', {
shop: shop.id, // your Shop ID (publishable, = shop.id)
asset: 'https://example.com/art.png' // your artwork
});
// <img src={url} />The
shopID is your publishable token — public and safe to expose (like Cloudinary's cloud name). It defaults to yourshop.id. See Public or signed below if you want to lock things down.
Core Functions
Product Data
// Get a single product
const product = await getProduct(productId: string, options?: {
endpoint?: string;
mode?: 'mock' | 'live' | 'meilisearch';
});
// List products
const products = await listProducts(options?: {
limit?: number;
offset?: number;
endpoint?: string;
mode?: 'mock' | 'live' | 'meilisearch';
});Product Object Structure:
{
id: string; // Product ID (e.g., "BEEB77")
name: string; // Product name
description?: string; // Product description
basePrice: number; // Base price in cents
variants: Variant[]; // Color/size variants
placements: Placement[]; // Print areas (Front, Back, etc.)
mockup: {
blankUrl: string; // Blank product image URL
};
}Mockup URLs
A mockup is a public image URL on img.snowcone.app:
https://img.snowcone.app/{productCode}?asset={assetUrl}&shop={shopId}
You can hand-write it, or use getMockupUrl — a pure, synchronous,
isomorphic builder (browser + server, no await). It never hand-rolls the query
string; it delegates to @snowcone-app/mockup-url's buildPublicMockupUrl
(the single source of truth shared byte-for-byte with the edge resolver).
import { getMockupUrl } from '@snowcone-app/sdk';
// Code-first form: productCode, then options.
const url = getMockupUrl(productCode: string, opts: {
shop: string; // Required: your Shop ID (publishable, = shop.id)
asset?: string; // Single default-placement image (get-started shorthand)
design?: Design; // Multi-placement: { [placementKey]: Fill } — takes precedence over `asset`
options?: Record<string, string>; // Variant picks: { size: "m" } → opt.size=m
secret?: string; // Optional: per-shop secret → appends a signed &signature (server only)
base?: string; // Optional: override the host (default img.snowcone.app)
width?: number; // Optional: display width
view?: string; // Optional: camera view / mockup scene (alias: `mockup`)
variant?: string; // Optional: specific resolved variant (gvid)
placement?: string; // Optional: specific print area (single-asset only)
aspect?: '16:9' | '2:3';// Optional: canvas aspect ratio
}): string;Fill (re-exported from the SDK) is what fills one placement:
type Fill =
| string // image URL (shorthand for { src })
| { src: string; align?: ImageAlignment; tile?: 0.25 | 0.5 | 1 | 2 | 4 }
| { color: string }; // a color placement (e.g. a cap's Crown)
type Design = Record<string, Fill>; // placement key → fillMulti-placement + variant options
// A front + back tee, size M.
const url = getMockupUrl('KMYKUK', {
shop: shop.id,
options: { size: 'm' },
design: {
front: 'https://cdn.example.com/front.png',
back: { src: 'https://cdn.example.com/back.png', tile: 2, align: 'top' },
},
});
// → …/KMYKUK?shop=…&asset.back=…&tile.back=2&align.back=top&asset.front=…&opt.size=m
// A cap with a printed front + chosen Crown/Strap colors (color placements).
const cap = getMockupUrl('RQNU68', {
shop: shop.id,
design: { front: 'https://cdn.example.com/logo.png', crown: { color: '#001f3f' } },
});
// → …/RQNU68?shop=…&color.crown=%23001f3f&asset.front=…A legacy positional form
getMockupUrl(assetUrl, productCode, opts)is also accepted (it builds the same URL asgetMockupUrl(productCode, { asset, … })). Prefer the code-first form above for new code.
Public or signed
The default is open and frictionless. There's one decision to make up front:
- Public — the Shop ID alone, safe to expose. Lock it to your asset origins so a stolen ID can only re-render your own catalog.
- Signed — your server appends an
&signature(HMAC of a per-shop secret), so only you can mint valid URLs. The secret stays server-side.
To sign, pass secret (server-side only) and getMockupUrl appends the
verified &signature:
// On your server/BFF — the secret never ships to the browser.
const url = getMockupUrl('hoodie-black', {
shop: shop.id,
asset: assetUrl,
secret: process.env.SNOWCONE_SHOP_SECRET,
});See Public or signed for the full picture.
Realtime: render a saved canvas, no pixel upload
getMockupUrl composites one artwork onto a product. For a full multi-layer
design — the kind the Snowcone canvas editor produces — use a RenderSession:
the browser sends a ~1 KB description of the canvas (or just a saved design's id)
over a WebSocket, and the server renders the mockup and fetches the referenced assets
itself. No per-placement PNG is ever uploaded, so it's fast on thin/mobile
clients. This is exactly how the Snowcone PDP renders mockups.
Authorize the session with a short-lived grant. Mint it server-side with a
secret API key (sk_…, scope mockups:realtime) so the key never reaches the
browser. Create the key — and find your publishable shop.id — on the
API keys page:
// YOUR backend route, e.g. POST /api/realtime/grant
import { mintRealtimeGrant } from '@snowcone-app/sdk';
export async function POST(req: Request) {
const { shop } = await req.json();
const grant = await mintRealtimeGrant({
apiKey: process.env.SNOWCONE_API_KEY!, // secret — server only
shop, // = shop.id (publishable)
});
return Response.json(grant); // { token, expiresAt }
}In the browser, point a RenderSession at that proxy and render. Render a saved
design by id (no canvas JSON needed) or a live serializeStateForServer(...) state:
import { RenderSession } from '@snowcone-app/sdk';
const session = new RenderSession({
shop: 'YOUR_SHOP_ID', // publishable, like Stripe pk_
grantUrl: '/api/realtime/grant', // your proxy above
// mockupIds are catalog SCENE CODES (product.mockups[].id) — not placement
// names. variantId is required for products with a color/size option.
product: { productId: 'BEEB77', mockupIds: ['FV1qjO'], variantId: 'Pv1sLC' },
});
session.onMockups((results) => { img.src = results[0].imageUrl; });
// (a) render a SAVED canvas by id — ideal for fulfillment / agents
await session.renderSavedState('Front', 'design-state-id');
// (b) or render a live canvas state from @snowcone-app/canvas
// import { serializeStateForServer } from '@snowcone-app/canvas';
// await session.renderState('Front', serializeStateForServer(canvasState));React apps can use the lower-level useRealtimeMockup({ getToken }) hook from
@snowcone-app/sdk/react instead. Full walkthrough:
Realtime server-side render.
TypeScript Support
Full TypeScript definitions are included. Import types:
import type {
CatalogProduct,
Variant,
Placement,
ImageAlignment
} from '@snowcone-app/sdk';API Reference
See full API documentation at: https://developers.snowcone.app/sdk
React Components
For pre-built React components, install @snowcone-app/ui:
npm install @snowcone-app/ui
Examples
Support
- Docs: developers.snowcone.app/sdk
- Issues: GitHub Issues
License
MIT Snowcone
Built for developers. Optimized for LLMs.
This SDK is designed to work seamlessly with AI coding assistants like Claude, GitHub Copilot, and Cursor. Clear types, explicit examples, and predictable patterns make it easy for LLMs to generate correct code.