AI art generation

Generate art from a text prompt with one HTTP call, get image URLs hosted on your shop, and drop them straight into a mockup URL. No model keys, no infrastructure of your own — just an HTTP call to api.snowcone.app.

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.

The model behind a basic prompt is FLUX.2 [klein] (fast, returns several images at once). Add a reference image and the call upgrades to Nano Banana 2 Pro. You can name a model explicitly with the 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:

code
# 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):

code
{
  "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:

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

Calling this from your own server keeps your key off the browser. If you instead proxy generation to the client (a chat / “design it live” flow), put a gate in front so it isn’t an open, billed funnel — the same Turnstile / render-session gate you use for signing. See Gate AI generation & writes.

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:

code
# 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"
  }'
Reference images must be reachable URLs. The easiest source is a prior generation (already on your storage origin) or an asset you uploaded with an uploads:write key.

More AI endpoints

Background removal — clean up a generated or uploaded image to a transparent PNG (ai:bg-remove):

code
# 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):

code
# 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):

code
# 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)
What to draw. The only required field.
numberResultsnumber
How many images to return (default 4). Applies to the fast FLUX.2 [klein] path; reference-guided generation returns a single image.
width · heightnumber
Output dimensions in pixels (default 1024 × 1024).
referenceImageUrlsstring[]
One or more reference images. Providing any switches the call to Nano Banana 2 Pro (reference-guided, one image).
modelstring
Override the model. Omit for FLUX.2 [klein] (fast, multi-image, the default). google:4@2 Nano Banana 2 Pro. fal-ai/flux/dev FLUX dev.

Response

imagesarray
The generated images, each { id, imageUrl, width, height }. The imageUrl is hosted on your shop’s storage origin.
imageUrlstring
Convenience: the first image's URL (same as images[0].imageUrl).
generationIdstring
The batch id. When persistence is "ok" you can fetch the batch via GET /ai-generations/:id.
persistence"ok" | "failed"
Whether the generation records were saved. "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 | null
What this batch cost, in cents (null when no price is resolvable). The source of truth for what you were charged.

Scopes

ai:generatescope
POST /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-removescope
POST /ai-generations/remove-background.
ai:videoscope
POST /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.

Every call bills to the organization the calling key belongs to — all of an org’s keys draw on one shared balance, enforced at the org level. The full deduction order, and where your per-user limits fit, is below in Billing & limits.

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.

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

Where it leads