AI art generation
What this is
Snowcone hosts a text-to-image API. You send a prompt, we run the model, and you get back image URLs already hosted on your shop’s storage origin (your-shop-id.storage.snowcone.app). Because that origin is a trusted asset origin for your shop, you can feed a generated image straight into a mockup URL and onto a product — no upload step in between.
It is plain HTTP. There is no model API key to manage and no SDK to install — your Snowcone secret key on a plain HTTP request is the whole integration, in any language. Alongside image generation there are endpoints for background removal, image-to-video, and a chat orchestrator that chains generation, catalog search, and mockups from one prompt.
model field — see Reference images & models.Every call needs a key
There is no anonymous tier. POST /ai-generations/generate (and every other endpoint on this page) requires a shop-scoped secret API key — guest and user-session callers are rejected up front, before any model work runs. The key must carry the endpoint’s scope (ai:generate for generation and chat). Why shop-scoped: every generated image is stored under the calling key’s shop (your-shop-id.storage.snowcone.app), so a call without a shop has nowhere to land and no organization to bill.
To get a key: minting a sandbox shop returns one — the api_key in the POST /shops/sandbox response carries ai:generate (plus ai:bg-remove, uploads:write, and mockups), so you can generate immediately, no human needed. Once a human claims the shop that key is revoked, and they issue a replacement with the ai:generate scope at snowcone.app/studio/api-keys — the full flow is on Secret API keys. The scsec_… shop secret from the sandbox mint is for signing image URLs, not for AI calls.
Generate art
Authorize with a shop-scoped secret API key that carries the ai:generate scope, passed as an x-api-key (or Authorization: Bearer) header:
# Authorize with a shop-scoped secret API key that has the ai:generate scope.
curl -X POST https://api.snowcone.app/ai-generations/generate \
-H "x-api-key: $SNOWCONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a minimalist line drawing of a mountain, black on white",
"numberResults": 4
}'The response (this is the real shape — dogfooded against the live endpoint):
{
"images": [
{
"id": "res_CysigBn7FrG1",
"imageUrl": "https://your-shop-id.storage.snowcone.app/ai-generations/…-bddxf2.avif",
"width": 1024,
"height": 1024
}
],
"imageUrl": "https://your-shop-id.storage.snowcone.app/ai-generations/…-bddxf2.avif",
"width": 1024,
"height": 1024,
"generationId": "gen_hLMMv3v7z9vn",
"persistence": "ok",
"costCents": 1
}Put it on a product
Take the imageUrl from the response, URL-encode it, and use it as the asset of a mockup URL. That is the whole handoff — generate, then render:
<!-- The image lives on YOUR shop's storage origin, which is already a
trusted asset origin for that shop — so you can drop the generated
imageUrl straight into a mockup URL as the (URL-encoded) asset. -->
<img src="https://img.snowcone.app/AR2P3G?asset=https%3A%2F%2Fyour-shop-id.storage.snowcone.app%2Fai-generations%2F…-bddxf2.avif&shop=YOUR_SHOP_ID" />From there it is the normal flow: the same product + asset go into a buy link to sell it.
Reference images & models
Pass referenceImageUrls (or set model: "google:4@2") to use Nano Banana 2 Pro — it conditions on your image, ideal for putting a logo, character, or photo into a generated scene. This path returns a single image:
# Pass referenceImageUrls (or model "google:4@2") to switch to Nano Banana
# 2 Pro — reference-guided generation, one image per call.
curl -X POST https://api.snowcone.app/ai-generations/generate \
-H "x-api-key: $SNOWCONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "put this logo on a vintage enamel pin, studio lighting",
"referenceImageUrls": ["https://your-shop-id.storage.snowcone.app/logo.png"],
"model": "google:4@2"
}'uploads:write key.More AI endpoints
Background removal — clean up a generated or uploaded image to a transparent PNG (ai:bg-remove):
# Background removal (BiRefNet v2). Needs the ai:bg-remove scope.
curl -X POST https://api.snowcone.app/ai-generations/remove-background \
-H "x-api-key: $SNOWCONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "imageUrl": "https://your-shop-id.storage.snowcone.app/ai-generations/…avif" }'
# → { "imageUrl": "…-nobg.png", "width": 1024, "height": 1024 }Image to video — animate a still into a 6-second MP4 (ai:video):
# Image → 6s MP4 (MiniMax Hailuo 2.3 Fast). Needs the ai:video scope.
curl -X POST https://api.snowcone.app/ai-generations/generate-video \
-H "x-api-key: $SNOWCONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "imageUrl": "https://…/art.png", "prompt": "slow zoom, gentle drift", "resolution": 720 }'
# → { "videoUrl": "…mp4", "duration": 6, "width": …, "height": … }Chat orchestrator — one natural-language request, and the server chains the tools (generate art → search the catalog → build a mockup) for you. Building a chat UI? Use POST /ai-generations/chat/stream — it speaks the AI SDK’s standard UI Message Stream over SSE, so useChat consumes it directly; the full integration is the Add an AI design chat guide. Prefer raw HTTP? Generation takes tens of seconds, so the polling API is async: chat/start returns a job id immediately and you poll chat/jobs/:id until it’s done (the stage field makes a nice progress label):
# The orchestrator: one prompt, it chains generate → search catalog → mockup.
# Same auth as /generate: a shop-scoped key with the ai:generate scope.
# Async: start returns a job id immediately; poll until status is "done".
curl -X POST https://api.snowcone.app/ai-generations/chat/start \
-H "x-api-key: $SNOWCONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "messages": [{ "role": "user", "content": "make a cozy cabin sticker and put it on a mug" }] }'
# → { "jobId": "…" }
curl https://api.snowcone.app/ai-generations/chat/jobs/$JOB_ID \
-H "x-api-key: $SNOWCONE_API_KEY"
# → { "status": "running", "stage": "generating image" } # poll every ~1.5s
# → { "status": "done", "response": { "text": …, "images": …, "mockup": … } }Parameters & response
For POST /ai-generations/generate:
Request body
promptstring (required)numberResultsnumber4). Applies to the fast FLUX.2 [klein] path; reference-guided generation returns a single image.width · heightnumber1024 × 1024).referenceImageUrlsstring[]modelstringgoogle:4@2 → Nano Banana 2 Pro. fal-ai/flux/dev → FLUX dev.Response
imagesarray{ id, imageUrl, width, height }. The imageUrl is hosted on your shop’s storage origin.imageUrlstringgenerationIdstringpersistence is "ok" you can fetch the batch via GET /ai-generations/:id.persistence"ok" | "failed""ok" → the ids resolve. "failed" → fail-open: you still keep the images, but the ids are non-resolvable (a transient DB failure never costs you a paid generation).costCentsnumber | nullScopes
ai:generatescopePOST /ai-generations/generate and the chat orchestrator (POST /ai-generations/chat/stream, plus the polling pair POST /ai-generations/chat/start + GET /ai-generations/chat/jobs/:id).ai:bg-removescopePOST /ai-generations/remove-background.ai:videoscopePOST /ai-generations/generate-video.Pricing & quota
AI calls draw on your plan’s monthly usage budget (real dollars, no abstract credits), then your wallet, then a 429. Each response reports its own costCents so you never have to guess what you paid. Representative prices: image generation is roughly a cent per image; background removal is $0.010; video is $0.247 (720p) / $0.429 (1080p). See Pricing & margins for the budget model.
Billing & limits — who meters whom
Two meters run on every billable call, owned by two different parties. Mixing them up is how budgets get drained — so here is the contract, explicitly.
Tier 1 — Snowcone meters your shop. Every billable operation — AI generation, background removal, realtime mockup renders, asset writes — draws on your organization’s balance, in a fixed order: plan API credits first, then your wallet, then a hard 429 ResourceExhausted (“API credits exhausted and no wallet balance”; the realtime grant surfaces it as shop quota exceeded). Each operation’s price is recorded to your usage ledger and each response carries its own costCents. Enforcement is org-level: all keys belonging to the organization share one balance — there is no per-key budget.
Tier 2 — you meter your users. Snowcone sees your shop, not your end-users — it cannot tell one of your customers from another, so per-user fairness is yours to enforce. The gate hook on every @snowcone-app/sdk/server handler (createImageSearchHandler, createMockupSignHandler, createRealtimeGrantHandler) — and your own generate / bg-remove proxy routes — is where your per-user auth, rate limits, and abuse checks belong. A proxy route with no gate is an open funnel: anyone who finds the URL spends your credits.
// app/api/generate-image/route.ts — YOUR per-user meter (tier 2).
// Snowcone enforces the org balance (tier 1); only you can enforce
// per-user fairness, because only you know who the user is.
const DAILY_LIMIT = 20;
export async function POST(req: Request) {
const session = await getSession(req); // your auth: cookie, JWT, …
if (!session) return new Response('Sign in to generate', { status: 401 });
const used = await incrementDailyCount(session.userId); // your counter: KV, Redis, DB
if (used > DAILY_LIMIT) {
return Response.json({ error: 'Daily generation limit reached' }, { status: 429 });
}
// Authenticated, metered caller — now spend the server-held key.
return fetch('https://api.snowcone.app/ai-generations/generate', {
method: 'POST',
headers: {
'x-api-key': process.env.SNOWCONE_API_KEY!, // ai:generate scope, server-only
'content-type': 'application/json',
},
body: await req.text(),
});
}The two tiers compose: your gate decides who may spend; Snowcone’s meter decides how much the organization may spend in total. A per-user limit at your gate keeps any single user from draining the shared balance — the org-level 429 is the backstop, not the first line of defense. The same gate shape protects signing and realtime grants — see Gate AI generation & writes.

