Docs menu

Brand kits (styles)

Save your palette, logo, background and caption tone once and reuse them with styleId in any generation or screenshot set.

View as Markdown

A brand kit (a style in the API) saves the look of your store images once: colours, a logo, a background and a caption tone. Send its id as styleId when you create a generation or a screenshot set instead of repeating them. Brand kits are free. Creating, changing and deleting one needs generations:write; reading needs generations:read or generations:write. Brand kits belong to the account: every key of the account sees them, in either environment.

The Node.js and Python samples on this page continue from the first one (api, headers, logo).

What a brand kit holds

FieldWhat it is
name1–100 characters, required. Not unique.
paletteUp to 8 colours ({ "hex": "#0F766E", "name": "Teal" }).
logoA copy of one of your uploads (PNG, JPEG or WebP, at most 5 MB and 4096 × 4096 pixels, from either environment). Deleting the upload later keeps the logo.
backgroundMoodminimal, light, dark, vibrant, gradient or textured.
backgroundPromptWhat the background looks like, in at most 200 characters on one line (no line breaks or control characters). It is passed to the model as a quoted description of the look, never as instructions.
captionToneThe voice of the headline: professional, friendly, playful, bold or premium. A caption you send is kept word for word; the tone guides its typography and any wording the model writes.
referenceGenerationIdOptional: a generation to copy colours and choices from (below).

Every field except name is optional; a brand kit with only a name applies nothing.

Create one

Upload the logo first, like any image (Uploads). Here home.png stands in for your logo file.

curl

LOGO=$(curl -sS https://api.storeshot.art/v1/uploads \
  -H "Authorization: Bearer $STORESHOT_API_KEY" -F file=@home.png | jq -r .id)
echo "$LOGO"

Node.js

import { readFile } from 'node:fs/promises';

const api = 'https://api.storeshot.art/v1';
const headers = { Authorization: `Bearer ${process.env.STORESHOT_API_KEY}` };
const jsonHeaders = { ...headers, 'Content-Type': 'application/json' };

const uploadFile = async (file) => {
  const form = new FormData();
  form.append('file', new Blob([await readFile(file)]), file);
  return (await fetch(`${api}/uploads`, { method: 'POST', headers, body: form })).json();
};
const logo = await uploadFile('home.png');
console.log(logo.id, logo.status);

Python

import os, requests

api = "https://api.storeshot.art/v1"
headers = {"Authorization": f"Bearer {os.environ['STORESHOT_API_KEY']}"}

def upload_file(name):
    with open(name, "rb") as f:
        return requests.post(f"{api}/uploads", headers=headers, files={"file": f}).json()

logo = upload_file("home.png")
print(logo["id"], logo["status"])

Then create the brand kit with an Idempotency-Key. The answer is 201 with the brand kit and its id; logo is now the brand kit’s own copy (url, signed for 15 minutes, and expiresAt).

curl

STYLE_BODY='{"name":"Trailmate brand",
  "palette":[{"hex":"#0F766E","name":"Teal"},{"hex":"#F8FAFC","name":"Off White"}],
  "logo":{"uploadId":"'"$LOGO"'"},
  "backgroundMood":"textured",
  "backgroundPrompt":"soft paper texture with mountain line art",
  "captionTone":"friendly"}'

STYLE=$(curl -sS https://api.storeshot.art/v1/styles \
  -H "Authorization: Bearer $STORESHOT_API_KEY" \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: brand-kit-1' \
  -d "$STYLE_BODY" | jq -r .id)
echo "$STYLE"

Node.js

const created = await fetch(`${api}/styles`, {
  method: 'POST',
  headers: { ...jsonHeaders, 'Idempotency-Key': 'brand-kit-1' },
  body: JSON.stringify({
    name: 'Trailmate brand',
    palette: [
      { hex: '#0F766E', name: 'Teal' },
      { hex: '#F8FAFC', name: 'Off White' },
    ],
    logo: { uploadId: logo.id },
    backgroundMood: 'textured',
    backgroundPrompt: 'soft paper texture with mountain line art',
    captionTone: 'friendly',
  }),
});
const style = await created.json();
console.log(created.status, style.id, style.name);

Python

created = requests.post(
    f"{api}/styles",
    headers={**headers, "Idempotency-Key": "brand-kit-1"},
    json={
        "name": "Trailmate brand",
        "palette": [
            {"hex": "#0F766E", "name": "Teal"},
            {"hex": "#F8FAFC", "name": "Off White"},
        ],
        "logo": {"uploadId": logo["id"]},
        "backgroundMood": "textured",
        "backgroundPrompt": "soft paper texture with mountain line art",
        "captionTone": "friendly",
    },
)
style = created.json()
print(created.status_code, style["id"], style["name"])

Repeating the request with the same key and body creates nothing: it answers the brand kit as it is now with 200 and Idempotent-Replayed: true. The same key with a different body is 409 IDEMPOTENCY_CONFLICT with details.styleId.

curl -sS -o /dev/null -D - https://api.storeshot.art/v1/styles \
  -H "Authorization: Bearer $STORESHOT_API_KEY" \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: brand-kit-1' \
  -d "$STYLE_BODY" | grep -i '^idempotent-replayed'

GET /styles lists your brand kits, most recently updated first, 20 per page (limit up to 100; pass nextCursor back as cursor). GET /styles/{id} reads one.

curl

curl -sS 'https://api.storeshot.art/v1/styles?limit=10' \
  -H "Authorization: Bearer $STORESHOT_API_KEY" \
  | jq -r '.data[] | "\(.id) \(.name) \(.backgroundMood) \(.captionTone)"'

Node.js

const styles = await (await fetch(`${api}/styles?limit=10`, { headers })).json();
for (const s of styles.data) console.log(s.id, s.name, s.backgroundMood, s.captionTone);

Python

styles = requests.get(f"{api}/styles", params={"limit": 10}, headers=headers).json()
for s in styles["data"]:
    print(s["id"], s["name"], s["backgroundMood"], s["captionTone"])

Use it

Send styleId when you create a generation. This one sends no palette and no logo, so both come from the brand kit, together with its background mood, description and caption tone.

curl

SCREEN=$(curl -sS https://api.storeshot.art/v1/uploads \
  -H "Authorization: Bearer $STORESHOT_API_KEY" -F file=@screen-1.png | jq -r .id)

GENERATION=$(curl -sS https://api.storeshot.art/v1/generations \
  -H "Authorization: Bearer $STORESHOT_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: home-brand-kit' -H 'Prefer: wait=60' \
  -d '{"inputs":[{"uploadId":"'"$SCREEN"'"}],"store":"app_store","device":"app_store_1290x2796",
    "app":{"name":"Trailmate"},"caption":"Find every trail, even offline",
    "styleId":"'"$STYLE"'"}')
GENERATION_ID=$(echo "$GENERATION" | jq -r .id)
echo "$GENERATION" | jq '{id, status, style, palette}'

Node.js

const screen = await uploadFile('screen-1.png');
const response = await fetch(`${api}/generations`, {
  method: 'POST',
  headers: { ...jsonHeaders, 'Idempotency-Key': 'home-brand-kit', Prefer: 'wait=60' },
  body: JSON.stringify({
    inputs: [{ uploadId: screen.id }],
    store: 'app_store',
    device: 'app_store_1290x2796',
    app: { name: 'Trailmate' },
    caption: 'Find every trail, even offline',
    styleId: style.id,
  }),
});
const generation = await response.json();
console.log(response.status, generation.status, generation.style);

Python

screen = upload_file("screen-1.png")
response = requests.post(
    f"{api}/generations",
    headers={**headers, "Idempotency-Key": "home-brand-kit", "Prefer": "wait=60"},
    json={
        "inputs": [{"uploadId": screen["id"]}],
        "store": "app_store",
        "device": "app_store_1290x2796",
        "app": {"name": "Trailmate"},
        "caption": "Find every trail, even offline",
        "styleId": style["id"],
    },
)
generation = response.json()
print(response.status_code, generation["status"], generation.get("style"))

The generation’s style names the brand kit it was made with and what it supplied: { "id": "…", "name": "Trailmate brand", "applied": ["palette", "logo", "backgroundMood", "backgroundPrompt", "captionTone"] } (null when no brand kit was used). palette shows the colours used; logo stays null when the logo came from the brand kit. With a test key the result is a sample image as usual (Test mode); its colour differs from an unstyled one because the sample is seeded from the prompt, which now carries the brand kit.

A screenshot set takes the same styleId and applies it to every image. It also works with ?dryRun=true: the brand kit is checked and nothing is copied. Brand kits are free, so the quote is the same as without one.

curl

curl -sS 'https://api.storeshot.art/v1/screenshot-sets?dryRun=true' \
  -H "Authorization: Bearer $STORESHOT_API_KEY" -H 'Content-Type: application/json' \
  -d '{"app":{"name":"Trailmate"},
    "screens":[{"input":{"uploadId":"'"$SCREEN"'"},"caption":"Find every trail"}],
    "stores":[{"store":"app_store","devices":["app_store_1290x2796"]}],
    "styleId":"'"$STYLE"'"}' | jq '{images, credits, sufficient}'

Node.js

const quote = await (
  await fetch(`${api}/screenshot-sets?dryRun=true`, {
    method: 'POST',
    headers: jsonHeaders,
    body: JSON.stringify({
      app: { name: 'Trailmate' },
      screens: [{ input: { uploadId: screen.id }, caption: 'Find every trail' }],
      stores: [{ store: 'app_store', devices: ['app_store_1290x2796'] }],
      styleId: style.id,
    }),
  })
).json();
console.log(quote.images, quote.credits, quote.sufficient);

Python

quote = requests.post(
    f"{api}/screenshot-sets",
    params={"dryRun": "true"},
    headers=headers,
    json={
        "app": {"name": "Trailmate"},
        "screens": [{"input": {"uploadId": screen["id"]}, "caption": "Find every trail"}],
        "stores": [{"store": "app_store", "devices": ["app_store_1290x2796"]}],
        "styleId": style["id"],
    },
).json()
print(quote["images"], quote["credits"], quote["sufficient"])

What wins: your fields

The brand kit only fills what the request leaves out.

InputYou send itYou leave it out, the brand kit has itNeither
paletteyoursthe brand kit’sextracted from the screenshot (a set: from screen 1)
logoyoursthe brand kit’sno logo
background mood, description, caption tone(no request field)the brand kit’snot used
app, caption, screens, devices, metadata, projectIdyours(a brand kit holds none of these)as without a brand kit

style.applied lists exactly what came from the brand kit; fields you sent yourself are not listed. A request that wants no logo or no brand kit colours does not send styleId.

Change or delete it

PATCH /styles/{id} changes only the fields you send: null clears logo, backgroundMood, backgroundPrompt or captionTone, and palette: [] clears the colours. A new logo is copied like on create. DELETE /styles/{id} answers 204.

A generation or set keeps a copy of the brand kit as it was when it was created, including its own copy of the logo. Changing or deleting the brand kit never changes images you already made, or their retries; only new generations and sets use the new values. Once the brand kit is deleted, their style.id is null and style.name and style.applied stay.

curl

curl -sS -X PATCH "https://api.storeshot.art/v1/styles/$STYLE" \
  -H "Authorization: Bearer $STORESHOT_API_KEY" -H 'Content-Type: application/json' \
  -d '{"captionTone":"bold"}' | jq '{name, backgroundMood, captionTone, updatedAt}'

Node.js

const updated = await (
  await fetch(`${api}/styles/${style.id}`, {
    method: 'PATCH',
    headers: jsonHeaders,
    body: JSON.stringify({ captionTone: 'bold' }),
  })
).json();
console.log(updated.name, updated.captionTone);

Python

updated = requests.patch(
    f"{api}/styles/{style['id']}", headers=headers, json={"captionTone": "bold"}
).json()
print(updated["name"], updated["captionTone"])

curl

curl -sS -X DELETE "https://api.storeshot.art/v1/styles/$STYLE" \
  -H "Authorization: Bearer $STORESHOT_API_KEY" -o /dev/null -w '%{http_code}\n'
curl -sS "https://api.storeshot.art/v1/generations/$GENERATION_ID" \
  -H "Authorization: Bearer $STORESHOT_API_KEY" | jq .style

Node.js

const deleted = await fetch(`${api}/styles/${style.id}`, { method: 'DELETE', headers });
console.log(deleted.status);
const after = await (await fetch(`${api}/generations/${generation.id}`, { headers })).json();
console.log(after.style);

Python

deleted = requests.delete(f"{api}/styles/{style['id']}", headers=headers)
print(deleted.status_code)
after = requests.get(f"{api}/generations/{generation['id']}", headers=headers).json()
print(after.get("style"))

Copy a look from a generation

referenceGenerationId (on create or PATCH) names a succeeded generation of yours, from either environment. Its colours, and its background and caption choices when it was made with a brand kit, are copied once into the fields the same request leaves out. Its image and its logo are never used. The id stays on the brand kit as referenceGenerationId, for reference only: later changes to the generation change nothing in the brand kit. Sending an id again copies again; referenceGenerationId: null forgets where the values came from and keeps them.

The generation above was made with the brand kit before the PATCH, so this copies its colours, mood and description and the tone it used then (friendly), although the brand kit itself is deleted by now:

curl -sS https://api.storeshot.art/v1/styles \
  -H "Authorization: Bearer $STORESHOT_API_KEY" \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: brand-kit-from-home' \
  -d '{"name":"Trailmate from home","referenceGenerationId":"'"$GENERATION_ID"'"}' \
  | jq '{id, palette, backgroundMood, captionTone, referenceGenerationId}'

Limits and errors

  • At most 50 brand kits per account.
  • A logo is at most 5 MB and 4096 × 4096 pixels (PNG, JPEG or WebP).
  • backgroundPrompt is at most 200 characters on one line.
SituationAnswer
styleId unknown or not yours (create or dry run)400 INVALID_INPUT, issues[0].path ["styleId"], “No such style.”
GET, PATCH or DELETE of an unknown, deleted or another account’s brand kit404 NOT_FOUND
50 brand kits already403 FORBIDDEN, details.reason style_limit_reached, details.limit 50
Logo upload unknown, not ready, expired or another account’s400 INVALID_INPUT, issues[0].path ["logo","uploadId"]
Logo over 5 MB or 4096 × 4096 pixels400 INVALID_INPUT, issues[0].path ["logo","uploadId"]
referenceGenerationId not a succeeded generation of yours400 INVALID_INPUT, issues[0].path ["referenceGenerationId"]
Same Idempotency-Key, different body409 IDEMPOTENCY_CONFLICT, details.styleId
Set retry after its copy of the brand kit logo was deleted409 UPLOAD_NOT_READY, details.reason expired, details.path ["styleId"]

Every code is described in Errors.

How long it is kept

  • A brand kit and its copy of your logo are kept until you delete the brand kit. Deleting it deletes its logo copy at once.
  • Generations and screenshot sets made with a brand kit keep their own copy of its logo. That copy is deleted 30 days after the last generation or screenshot set that used it was created; a set retry after that answers 409 UPLOAD_NOT_READY (see the table above).
  • Closing your account deletes your brand kits and those logo copies.

See also the data retention policy.