GUIDES MCP OPEN APP

SpriteLab API

Generate clean, game-ready pixel art over REST: sprites from text, image conversion, exact-size micro sprites, themed reskins, full-frame backgrounds and animated spritesheets. JSON in, images out, with the full cleanup pipeline applied to every result.

Live for everyone. Create a key in the app under Account → API keys and start calling. Questions or feature requests? Join the Discord.
ON THIS PAGE

Overview

The SpriteLab API turns text and images into clean, game-ready pixel art over REST. Every result runs through the same cleanup pipeline as the app: background removal, a limited palette, and a rebuilt 1px outline, so what you get back drops straight into an engine.

Base URL

BASE  https://spritelab.dev/api/v1

JSON in, images out. Most endpoints return a JSON envelope with base64 images; /generate and /resize return raw PNG bytes (frozen for the Aseprite plugin). Everything is one round trip except animation, which is async.

Authentication

Every request needs a Bearer key in the Authorization header. Keys look like sl_live_… and are created in the app under Account → API keys. The plaintext is shown once at creation and never again, so store it somewhere safe.

Authorization: Bearer sl_live_YOUR_KEY

Rate limit: 60 requests per minute per key. Every response carries X-RateLimit-Limit and X-RateLimit-Reset; going over returns 429.

Billing & credits

API generation spends top-up (purchased) credits only. Your subscription's monthly credits are never touched by the API, so a runaway script can't eat your plan allowance. Top-ups never expire and are bought in the app under Billing.

Every mode is available over the API, including the ones that are subscriber-only in the web app (Micro, Reskin, Backgrounds, Tileset, Merge, packs above 3x3). Top-up credits are the only requirement, no plan needed.

CallCost
POST /generate1 CR epic / 6 CR mythic
POST /pack2 CR epic / 9 CR mythic — whole grid, any size
POST /convert3 CR
POST /micro3 CR epic / 10 CR mythic
POST /reskin2 CR epic / 6 CR mythic — per variant
POST /merge2 CR epic / 6 CR mythic — per variant
POST /backgrounds4 CR epic / 15 CR mythic
POST /tileset10 CR epic / 30 CR mythic
POST /animate20 CR
POST /resize, /enhance-prompt, GET /credits, /jobs/{id}Free

Failed generations refund automatically, including individual failed reskin variants and failed animation jobs. Every generation response carries your remaining balance (the credits_remaining field, or the X-SpriteLab-Credits-Remaining header on PNG responses), so you rarely need to poll /credits.

Optional auto top-up: flip it on in the app (Billing → API keys). When an API call leaves your top-up balance under 200 credits, we charge the card on file a fixed amount you choose and add the credits, so batch scripts never die on an empty bucket. At most one charge per 10 minutes, off by default.

Conventions

Two response shapes. /generate and /resize return raw image/png bytes with metadata in headers — pipe them straight to a file. Everything newer returns a JSON envelope with base64 images:

{ "id": "…", "mode": "…", "cost": 1, "credits_remaining": 419, "image_b64": "…" }

Image inputs (image_b64, sketch_b64) accept plain base64 or a full data URL. PNG, JPG or WebP, max 10MB decoded, max 4096px per axis.

Sizing. Send height only and the sprite comes out exactly that tall, width following its natural proportions. Sending width plus a matching height forces the legacy square canvas, which stretches non-square subjects. Micro is exact NxN by design.

Quality is a two-value knob everywhere it appears: "epic" (fast, cheap, the default) or "mythic" (finer detail, higher cost).

Errors

Errors are JSON: {"error": "human message", "code": "machine_code"}. Codes are stable — match on them, not the message.

StatusCodeMeaning
400bad_json / bad_prompt / bad_image / bad_size / bad_qualityMalformed body or a field out of range. The message says which
401auth_missing / auth_invalidNo Bearer header, or the key is wrong or revoked
402insufficient_purchased_creditsTop-up balance is empty. Buy credits in the app under Billing
403blockedAccount suspended
404not_found / job_not_foundDoesn't exist or isn't yours (deliberately the same response)
409not_resizable/resize: this row type can't go back through the sprite pipeline
429rate_limitedOver 60 requests per minute on this key
5xxupstream / pipelineGeneration failed on our side. Any credits charged are refunded

Async jobs

Animation takes 60 to 120 seconds, so POST /animate returns 202 with a job_id instead of blocking. Poll GET /jobs/{job_id} every few seconds until status is succeeded or failed. A failed job refunds automatically and says "refunded": true. Every other endpoint is synchronous.

Endpoints

POST /generate 1–6 CR

One cleaned pixel art sprite from a text prompt. Returns raw PNG bytes with the sprite id and remaining balance in headers. Full reference →

FieldTypeDescription
promptstringrequiredWhat to draw.
qualitystringepic (default, 1 CR) or mythic (6 CR).
heightint16–512, default 128. Sprite is exactly this tall, width follows natural proportions.
widthintLegacy: width + matching height forces an exact square (stretches). Skip for characters.
directionstringauto, front, left, right, back, top, angled. Default right. Ignored when style_reference_id is set.
detailint1–3 shading richness (1 flat, 3 rich). Default 1.
isometricbool2:1 dimetric isometric view (Stardew Valley / Habbo Hotel style).
style_notesstringFree-text style direction appended to the prompt, e.g. "muted earthy palette, thick outline".
style_reference_idstringAn earlier sprite id. New sprite matches its palette, proportions and outline — a whole cast in one style.
max_colours, outline_thickness, outline_colour, palette_lock, dithering, contrast, saturation, brightnessOptional tuning. contrast, saturation, brightness are 0–3 (1.0 = unchanged); max_colours 2–64; outline_thickness 0–4 (0 = none).

Request

curl -X POST https://spritelab.dev/api/v1/generate \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a knight in blue plate armor", "quality": "epic", "height": 64}' \
  -o knight.png
import requests

r = requests.post(
    "https://spritelab.dev/api/v1/generate",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "a knight in blue plate armor", "quality": "epic", "height": 64},
)
open("knight.png", "wb").write(r.content)
print("credits left:", r.headers["X-SpriteLab-Credits-Remaining"])
import { writeFileSync } from "fs";

const res = await fetch("https://spritelab.dev/api/v1/generate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sl_live_YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "a knight in blue plate armor", quality: "epic", height: 64 }),
});
writeFileSync("knight.png", Buffer.from(await res.arrayBuffer()));

Response

200 OK
Content-Type: image/png
X-SpriteLab-Sprite-Id: 3f9a…c1
X-SpriteLab-Credits-Remaining: 419

<binary PNG bytes>
POST /rotate 4–15 CR

One character facing front, right, back and left in a single call — each facing as its own frame plus a stitched spritesheet and an animated GIF. Ideal for top-down / RPG movement. Synchronous (typically 60–130s).

FieldTypeDescription
promptstringrequiredThe character or mob.
qualitystringepic (4 CR) or mythic (12 CR).
heightint16–512, default 128. Per-frame sprite height.
directionsint4 (default) front/right/back/left, or 8 which adds the diagonals and returns them in rotation order S,SE,E,NE,N,NW,W,SW. 8-dir costs 6 CR (epic) / 16 CR (mythic), ignores mirror_sides, and cannot be combined with isometric.
mirror_sidesbool4-dir only. Default true: draw 3 frames and mirror right→left for perfect L/R symmetry.
isometricboolIso facings (SE/SW/NW/NE) instead of cardinal.
detailint1–3 shading richness.
style_notesstringFree-text style direction appended to the prompt.
max_colours, outline_thickness, outline_colour, contrast, saturationOptional tuning subset honoured for rotation sheets.

Request

curl -X POST https://spritelab.dev/api/v1/rotate \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a village blacksmith", "quality": "epic", "height": 48}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/rotate",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "a village blacksmith", "quality": "epic", "height": 48},
).json()

for f in r["frames"]:
    open(f'smith_{f["dir"]}.png', "wb").write(base64.b64decode(f["image_b64"]))
open("smith_sheet.png", "wb").write(base64.b64decode(r["sheet_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/rotate", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "a village blacksmith", quality: "epic", height: 48 }),
}).then(x => x.json());
// r.frames[i].dir / .image_b64, r.sheet_b64, r.animated_gif_b64

Response

{
  "id": "7c2e…",
  "mode": "rotation",
  "cost": 4,
  "credits_remaining": 415,
  "directions": 4,
  "mirror_sides": true,
  "frames": [
    { "dir": "S", "image_b64": "…" },
    { "dir": "E", "image_b64": "…" },
    { "dir": "N", "image_b64": "…" },
    { "dir": "W", "image_b64": "…" }
  ],
  "sheet_b64": "…",
  "animated_gif_b64": "…",
  "sheet_download_url": "/api/v1/sprites/7c2e…/download?kind=sheet",
  "gif_download_url": "/api/v1/sprites/7c2e…/download?kind=animated"
}
POST /pack 2–9 CR

A whole grid of distinct sprites in one call, cleaned and split server-side into ready-to-use PNGs. Cheapest per-sprite path in the API. Full reference →

FieldTypeDescription
promptstringrequiredTheme for the set, e.g. "fantasy potions".
gridstring2x2 to 8x8, default 3x3. Any size up to 8x8.
variancestringvariants, family or wild — how different the cells are.
qualitystringepic (2 CR) or mythic (9 CR) for the whole grid.

Request

curl -X POST https://spritelab.dev/api/v1/pack \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "fantasy potions", "grid": "3x3", "variance": "family"}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/pack",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "fantasy potions", "grid": "3x3", "variance": "family"},
).json()

for cell in r["cells"]:
    open(f'potion_{cell["index"]}.png', "wb").write(base64.b64decode(cell["image_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/pack", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "fantasy potions", grid: "3x3", variance: "family" }),
}).then(res => res.json());

console.log(r.cells.length, "sprites,", r.credits_remaining, "credits left");

Response

{
  "id": "…", "mode": "pack", "cost": 2, "credits_remaining": 417,
  "grid": "3x3",
  "sheet_b64": "…",
  "cells": [
    { "index": 0, "width": 42, "height": 58, "image_b64": "…" },
    { "index": 1, "width": 40, "height": 61, "image_b64": "…" }
  ]
}
POST /convert 3 CR

Any image in, a faithful pixel art redraw out. Transparent background by default, or keep the original. Full reference →

FieldTypeDescription
image_b64stringrequiredThe source image (base64 or data URL).
remove_backgroundboolDefault true → transparent sprite. false keeps the original background.
heightintOutput height, default 128. Plus the usual tuning fields.

Request

curl -X POST https://spritelab.dev/api/v1/convert \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_b64": "iVBORw0K…", "remove_background": true, "height": 128}'
import requests, base64

src = base64.b64encode(open("photo.png", "rb").read()).decode()
r = requests.post(
    "https://spritelab.dev/api/v1/convert",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"image_b64": src, "remove_background": True, "height": 128},
).json()
open("pixelated.png", "wb").write(base64.b64decode(r["image_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/convert", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ image_b64: srcBase64, remove_background: true, height: 128 }),
}).then(res => res.json());

Response

{ "id": "…", "mode": "convert", "cost": 3, "credits_remaining": 414, "image_b64": "…" }
POST /micro 3–10 CR

Exact-size 16 to 64px sprites, every pixel deliberately on the grid. Icons, items, tiny characters. The NxN output is the artifact — not resizable after. Full reference →

FieldTypeDescription
promptstringrequiredWhat to draw.
sizeint16–64, default 32. The exact output dimensions.
qualitystringepic (3 CR) or mythic (10 CR).
detailstringclean (default) or detailed. Forced clean below 24px.

Request

curl -X POST https://spritelab.dev/api/v1/micro \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a red health potion", "size": 32, "detail": "clean"}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/micro",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "a red health potion", "size": 32, "detail": "clean"},
).json()
open("potion.png", "wb").write(base64.b64decode(r["image_b64"]))
print("closed outline:", r["outline_gaps"] == 0)
const r = await fetch("https://spritelab.dev/api/v1/micro", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "a red health potion", size: 32, detail: "clean" }),
}).then(res => res.json());

Response

{ "id": "…", "mode": "micro", "cost": 3, "credits_remaining": 411, "size": 32, "outline_gaps": 0, "image_b64": "…" }
POST /reskin 2–6 CR / variant

Pixel-perfect themed variants of a sprite you already have. Output dimensions equal the input. Each variant is billed and refunded independently. Full reference →

FieldTypeDescription
image_b64stringrequiredThe source sprite to re-theme.
themesstring[]Themes to apply, e.g. ["lava", "ice"]. Omit and we pick a spread.
variantsint1–11, default 4. Billed per variant.
qualitystringepic (2 CR/variant) or mythic (6 CR/variant).
match_silhouetteboolSnap every variant's alpha to the source exactly. Default off.

Request

curl -X POST https://spritelab.dev/api/v1/reskin \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_b64": "iVBORw0K…", "themes": ["lava", "ice", "gold"], "variants": 3}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/reskin",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"image_b64": src, "themes": ["lava", "ice", "gold"], "variants": 3},
).json()
for v in r["variants"]:
    open(f'{v["theme"]}.png', "wb").write(base64.b64decode(v["image_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/reskin", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ image_b64: src, themes: ["lava", "ice", "gold"], variants: 3 }),
}).then(res => res.json());

Response

{
  "id": "…", "mode": "reskin", "cost": 6, "credits_remaining": 405,
  "variants": [
    { "theme": "lava", "image_b64": "…" },
    { "theme": "ice",  "image_b64": "…" },
    { "theme": "gold", "image_b64": "…" }
  ]
}
POST /merge 2–6 CR / variant

Add a donor item onto a base sprite — armor on a hero, a gem on a sword, wings on a ship. The item is redrawn in the base sprite's style, in place; output dimensions equal the base's. Each variant is billed and refunded independently. Worn items with a clear shape (armor, outfits, helmets) land best.

FieldTypeDescription
target_b64stringrequiredThe base sprite.
donor_b64stringrequiredThe item to add.
apply_asstringattach (default), armor, outfit, weapon, hat, accessory, style or custom. Armor/outfit/hat re-dress the character; attach adds the item as-is.
instructionsstringOptional extra guidance, e.g. "held on the left arm".
variantsint1–6, default 2. Billed per variant.
qualitystringepic (2 CR/variant) or mythic (6 CR/variant).

Request

curl -X POST https://spritelab.dev/api/v1/merge \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_b64": "iVBORw0K…", "donor_b64": "iVBORw0K…", "apply_as": "armor", "variants": 2}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/merge",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"target_b64": hero, "donor_b64": armor, "apply_as": "armor", "variants": 2},
).json()
for i, v in enumerate(r["variants"]):
    open(f"merged_{i}.png", "wb").write(base64.b64decode(v["image_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/merge", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ target_b64: hero, donor_b64: armor, apply_as: "armor", variants: 2 }),
}).then(res => res.json());

Response

{
  "mode": "merge", "apply_as": "armor", "cost": 4, "credits_remaining": 405,
  "source_b64": "…", "donor_b64": "…",
  "variants": [
    { "image_b64": "…", "width": 64, "height": 64 },
    { "image_b64": "…", "width": 64, "height": 64 }
  ]
}
POST /backgrounds 4–15 CR

Full-frame pixel art scenes up to 8K, from a prompt or a rough sketch. Returns the final image plus its native low-res grid. Full reference →

FieldTypeDescription
promptstringrequired*The scene. *Or send sketch_b64 to redraw a rough layout.
width, heightintDefault 1920×1080, up to 7680×4320.
qualitystringepic (4 CR) or mythic (15 CR).
detailint1–3. Plus max_colours, contrast, saturation, style_notes.

Request

curl -X POST https://spritelab.dev/api/v1/backgrounds \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a misty pine forest at dawn", "width": 1920, "height": 1080}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/backgrounds",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "a misty pine forest at dawn", "width": 1920, "height": 1080},
).json()
open("forest.png", "wb").write(base64.b64decode(r["image_b64"]))
const r = await fetch("https://spritelab.dev/api/v1/backgrounds", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "a misty pine forest at dawn", width: 1920, height: 1080 }),
}).then(res => res.json());

Response

{ "id": "…", "mode": "scene", "cost": 4, "credits_remaining": 401, "image_b64": "…", "native_b64": "…" }
POST /tileset 10–30 CR

A full 15-piece corner (dual-grid) or 17-piece edge autotile atlas from one prompt. Uniquely, the response also carries drop-in Godot 4 .tres and Tiled .tsx files with the autotiling pre-wired (verified on Godot 4.7 + Tiled 1.12) — write each next to the atlas PNG and import, zero hand-assignment.

FieldTypeDescription
promptstringrequiredThe surface material, e.g. "mossy cobblestone".
base_promptstringOptional second material for a two-terrain pair (e.g. "dirt"). Same price.
layoutstring15 corner/dual-grid (terrain) or 17 edge/standard-grid (paths, walls). Default 15.
tile_sizeint16 or 32 (default 32). Plus edge_style, detail.
qualitystringepic (10 CR) or mythic (30 CR).

Request

curl -X POST https://spritelab.dev/api/v1/tileset \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "lush grass", "base_prompt": "dirt", "layout": "15"}'
import requests, base64

r = requests.post(
    "https://spritelab.dev/api/v1/tileset",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "lush grass", "base_prompt": "dirt", "layout": "15"},
).json()

# the atlas + the drop-in engine files
open(r["atlas_filename"], "wb").write(base64.b64decode(r["atlas_b64"]))
open("terrain.tres", "w").write(r["godot_tres"])  # Godot 4
open("terrain.tsx", "w").write(r["tiled_tsx"])    # Tiled
const r = await fetch("https://spritelab.dev/api/v1/tileset", {
  method: "POST",
  headers: { Authorization: "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "lush grass", base_prompt: "dirt", layout: "15" }),
}).then(r => r.json());
// r.atlas_b64, r.godot_tres, r.tiled_tsx

Returns atlas_b64, preview_b64, godot_tres, tiled_tsx, atlas_filename, plus tile_size, atlas_w/atlas_h, layout, pair, cost and credits_remaining.

POST /animate 20 CRasync

A sprite plus a motion prompt in, an animated spritesheet and GIF out. Async: returns a job id, then poll GET /jobs/{id}. Full reference →

FieldTypeDescription
image_b64stringrequiredThe sprite to animate. Max 256px per axis, aspect up to 2:1.
promptstringrequiredThe motion, e.g. "gentle idle bounce".
framesintFrame count, default 8.

Request

# 1. submit
curl -X POST https://spritelab.dev/api/v1/animate \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_b64": "iVBORw0K…", "prompt": "gentle idle bounce", "frames": 8}'
# -> { "job_id": "a1b2…", "status": "queued" }

# 2. poll until succeeded
curl https://spritelab.dev/api/v1/jobs/a1b2… \
  -H "Authorization: Bearer sl_live_YOUR_KEY"
import requests, time, base64

h = {"Authorization": "Bearer sl_live_YOUR_KEY"}
job = requests.post("https://spritelab.dev/api/v1/animate", headers=h,
    json={"image_b64": src, "prompt": "gentle idle bounce", "frames": 8}).json()

while True:
    s = requests.get(f'https://spritelab.dev/api/v1/jobs/{job["job_id"]}', headers=h).json()
    if s["status"] in ("succeeded", "failed"):
        break
    time.sleep(3)

if s["status"] == "succeeded":
    open("anim.gif", "wb").write(base64.b64decode(s["gif_b64"]))
const h = { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" };
const job = await fetch("https://spritelab.dev/api/v1/animate", {
  method: "POST", headers: h,
  body: JSON.stringify({ image_b64: src, prompt: "gentle idle bounce", frames: 8 }),
}).then(r => r.json());

let s;
do {
  await new Promise(r => setTimeout(r, 3000));
  s = await fetch(`https://spritelab.dev/api/v1/jobs/${job.job_id}`, { headers: h }).then(r => r.json());
} while (!["succeeded", "failed"].includes(s.status));

Response

// POST /animate
202 Accepted
{ "job_id": "a1b2…", "status": "queued" }

// GET /jobs/{id}  (poll)
{ "job_id": "a1b2…", "status": "succeeded", "id": "…",
  "sheet_b64": "…", "gif_b64": "…", "frame_count": 8, "fps": 8, "refunded": false }
POST /resize Free

Re-run the local pipeline on a sprite you already generated at new dimensions or tuning. No model call, so it's free. Returns PNG bytes. Owner-only. Full reference →

FieldTypeDescription
sprite_idstringrequiredId of a sprite you generated (from a prior response).
width, heightintrequiredNew dimensions.
max_colours, outline_thickness, palette_lock, …Any tuning field, same as /generate.

Request

curl -X POST https://spritelab.dev/api/v1/resize \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sprite_id": "3f9a…c1", "height": 32, "max_colours": 8}' \
  -o small.png
import requests

r = requests.post(
    "https://spritelab.dev/api/v1/resize",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"sprite_id": "3f9a…c1", "height": 32, "max_colours": 8},
)
open("small.png", "wb").write(r.content)
const res = await fetch("https://spritelab.dev/api/v1/resize", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ sprite_id: "3f9a…c1", height: 32, max_colours: 8 }),
});
writeFileSync("small.png", Buffer.from(await res.arrayBuffer()));

Response

200 OK
Content-Type: image/png
X-SpriteLab-Credits-Remaining: 401

<binary PNG bytes>
POST /enhance-prompt Free

Rewrite a rough prompt into a richer sprite description. Free.

FieldTypeDescription
promptstringrequiredYour rough prompt.

Request

curl -X POST https://spritelab.dev/api/v1/enhance-prompt \
  -H "Authorization: Bearer sl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "knight"}'
import requests
r = requests.post("https://spritelab.dev/api/v1/enhance-prompt",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"},
    json={"prompt": "knight"}).json()
print(r["enhanced"])
const r = await fetch("https://spritelab.dev/api/v1/enhance-prompt", {
  method: "POST",
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "knight" }),
}).then(res => res.json());

Response

{ "enhanced": "a stoic knight in weathered steel plate armor, blue tabard, longsword…" }
GET /credits Free

Your remaining top-up balance and tier. A cheap probe before a batch.

No parameters.

Request

curl https://spritelab.dev/api/v1/credits \
  -H "Authorization: Bearer sl_live_YOUR_KEY"
import requests
r = requests.get("https://spritelab.dev/api/v1/credits",
    headers={"Authorization": "Bearer sl_live_YOUR_KEY"}).json()
print(r["credits"], r["tier"])
const r = await fetch("https://spritelab.dev/api/v1/credits", {
  headers: { "Authorization": "Bearer sl_live_YOUR_KEY" },
}).then(res => res.json());

Response

{ "credits": 401, "tier": "ranger" }

OpenAPI spec

A machine-readable OpenAPI 3.1 spec lives at GET /api/v1/openapi.json (same Bearer key while the beta is private). Grab it and import into Postman, Insomnia or a client generator:

curl -H "Authorization: Bearer sl_live_YOUR_KEY" \
  https://spritelab.dev/api/v1/openapi.json -o spritelab-openapi.json

MCP & Aseprite

Every endpoint here is also an MCP tool for Claude Code, Cursor and any MCP client (one config line, same key), and powers the SpriteLab Aseprite plugin (coming soon, not released yet). Integrating at real volume? Message us on Discord — we do flat-rate bulk pricing.