API reference
Full Sprite AI REST API reference: endpoints for generating sprites and animations, request and response formats, parameters, and example calls.
Every endpoint below takes a bearer key, sent as Authorization: Bearer sai_sk_.... Generate one here, or manage your existing keys on the API keys page.
API key
Working in Claude Code, Cursor or another MCP client? The same key connects it in one step, and you get these endpoints as tools instead of HTTP calls. See Sprite AI on MCP.
GET/api/me
Your account: plan, balance, and rate limit.
curl https://www.sprite-ai.art/api/me \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch("https://www.sprite-ai.art/api/me", {
headers: { Authorization: "Bearer sai_sk_your_key_here" },
});
const account = await res.json();import requests
account = requests.get(
"https://www.sprite-ai.art/api/me",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json(){
"user_id": "...",
"email": "you@example.com",
"plan": "studio",
"rate_limit_per_minute": 60,
"monthly_token_allotment": 1000,
"monthly_tokens_used": 340,
"purchased_token_balance": 0,
"total_token_balance": 660,
"monthly_renews_at": "2026-07-01T00:00:00.000Z",
"billing_period": "monthly"
}GET/api/balance
Token balance only, a subset of /api/me.
curl https://www.sprite-ai.art/api/balance \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch("https://www.sprite-ai.art/api/balance", {
headers: { Authorization: "Bearer sai_sk_your_key_here" },
});
const { total_token_balance } = await res.json();import requests
balance = requests.get(
"https://www.sprite-ai.art/api/balance",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json(){
"plan": "studio",
"total_token_balance": 660,
"purchased_token_balance": 0,
"monthly_token_allotment": 1000,
"monthly_tokens_used": 340,
"monthly_renews_at": "2026-07-01T00:00:00.000Z",
"billing_period": "monthly"
}GET/api/asset-types
Asset types with their token cost and size range.
curl https://www.sprite-ai.art/api/asset-types \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch("https://www.sprite-ai.art/api/asset-types", {
headers: { Authorization: "Bearer sai_sk_your_key_here" },
});
const { asset_types } = await res.json();import requests
asset_types = requests.get(
"https://www.sprite-ai.art/api/asset-types",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json()["asset_types"]| Type | Token cost | Size |
|---|---|---|
character | 1 | 32–400 px |
item | 1 | 32–400 px |
creature | 1 | 32–400 px |
tile | 1 | 8–400 px |
background | 1 | 8–400 px |
ui | 1 | 8–400 px |
effect | 1 | 8–400 px |
GET/api/limits
Request bounds and token costs for your key.
curl https://www.sprite-ai.art/api/limits \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch("https://www.sprite-ai.art/api/limits", {
headers: { Authorization: "Bearer sai_sk_your_key_here" },
});
const limits = await res.json();import requests
limits = requests.get(
"https://www.sprite-ai.art/api/limits",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json(){
"rate_limit_per_minute": 60,
"sprite": { "min_size": 8, "max_size": 400, "prompt_max_length": 1500 },
"animation": { "min_frames": 4, "max_frames": 24, "frames_must_be_even": true }
}POST/api/sprites
Generate a still sprite. Synchronous: the PNG comes back inline. Costs 1 token.
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | What to draw. 3–1500 chars. |
asset_type | string | No | Inferred from the prompt when omitted. |
width | integer | No | 32–400 px. Default 64. |
height | integer | No | 32–400 px. Default 64. |
direction | string | No | Facing direction, e.g. south. |
camera_perspective | string | No | e.g. side, top_down, isometric. |
public | boolean | No | Add to the public gallery. Default false. |
reference_asset_id | string | No | UUID of a sprite you own, used as a style reference. |
palette_id | string | No | A palette from your library (see Palettes). The sprite is drawn in it, held to it, and remembers it, so animations, restyles and combines keep the same colours. |
curl https://www.sprite-ai.art/api/sprites \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "prompt": "a knight with a blue cape", "asset_type": "character" }'const res = await fetch("https://www.sprite-ai.art/api/sprites", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "a knight with a blue cape",
asset_type: "character",
}),
});
const data = await res.json(); // data.image.pngBase64import base64, requests
res = requests.post(
"https://www.sprite-ai.art/api/sprites",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"prompt": "a knight with a blue cape", "asset_type": "character"},
)
data = res.json()
with open("knight.png", "wb") as f:
f.write(base64.b64decode(data["image"]["pngBase64"])){
"id": "a1b2c3d4-...",
"status": "completed",
"width": 64,
"height": 64,
"assetType": "character",
"tokensSpent": 1,
"image": { "format": "png", "pngBase64": "iVBORw0KGgoAAAANS..." }
}The PNG is inline in image.pngBase64. There is no hosted URL.
POST/api/sprites/import
Add a sprite you already have (a local PNG, or any pixel sprite) and get back an id you can animate (source_generation_id) or use as a style reference (reference_asset_id). Free: importing spends no tokens and runs no AI.
The image must be a single sprite (PNG, JPEG, or WebP), 16–256px per side, not a sprite sheet. Imported sprites are always private.
| Field | Type | Required | Description |
|---|---|---|---|
image | string | Yes | The sprite, as base64 or a data:image/...;base64 URL. |
title | string | No | Label for the sprite. Default "Imported sprite". |
asset_type | string | No | Asset-type tag (metadata only). |
palette_id | string | No | A palette from your library. The sprite is stamped with it so later tools hold to it; the pixels are not changed. |
curl https://www.sprite-ai.art/api/sprites/import \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "image": "<png base64>", "title": "hero" }'import { readFileSync } from "node:fs";
const image = readFileSync("hero.png").toString("base64");
const res = await fetch("https://www.sprite-ai.art/api/sprites/import", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ image, title: "hero" }),
});
const { id } = await res.json(); // animate with source_generation_id: idimport base64, requests
image = base64.b64encode(open("hero.png", "rb").read()).decode()
res = requests.post(
"https://www.sprite-ai.art/api/sprites/import",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"image": image, "title": "hero"},
).json()
sprite_id = res["id"] # animate with source_generation_id{
"id": "a1b2c3d4-...",
"status": "completed",
"width": 64,
"height": 64,
"title": "hero"
}GET/api/sprites/{id}
Fetch one of your past sprites.
curl https://www.sprite-ai.art/api/sprites/a1b2c3d4-... \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch(
"https://www.sprite-ai.art/api/sprites/a1b2c3d4-...",
{ headers: { Authorization: "Bearer sai_sk_your_key_here" } },
);
const sprite = await res.json();import requests
sprite = requests.get(
"https://www.sprite-ai.art/api/sprites/a1b2c3d4-...",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json()GET/api/sprites
List your sprites and animations, newest first.
| Parameter | Type | Description |
|---|---|---|
limit | integer | 1–100. Default 20. |
offset | integer | For pagination. Default 0. |
status | string | Filter by status. |
asset_type | string | Filter by type, e.g. animation. |
curl "https://www.sprite-ai.art/api/sprites?limit=20" \
-H "Authorization: Bearer sai_sk_your_key_here"const res = await fetch(
"https://www.sprite-ai.art/api/sprites?limit=20",
{ headers: { Authorization: "Bearer sai_sk_your_key_here" } },
);
const { sprites } = await res.json();import requests
sprites = requests.get(
"https://www.sprite-ai.art/api/sprites?limit=20",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
).json()["sprites"]POST/api/palettes/generate
A few palette options for a description, nothing saved. Show them to the person, let them pick one or ask again, then save the pick with POST /api/palettes. The prompt is read into a theme, a main colour and moods; a number in the text sets the colour count. Free.
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | What the palette is for, e.g. "mossy dungeon, 16 colours". |
count | integer | No | Colours per option, 4-64. Default: the number in the prompt. With no number anywhere, the options come one per size, 8, 16, 24 and 32. |
options | integer | No | How many options, 2-4. Default 4. |
{
"prompt": "mossy dungeon, 16 colours",
"read_as": "theme forest, main colour green, mood dark",
"options": [
{ "option": 1, "colors": ["#1a2a1c", "..."], "recipe": { "count": 16, "seed": 91827364, "theme": "forest", "primary": "green", "moods": ["dark"] } },
{ "option": 2, "colors": ["..."], "recipe": { "count": 16, "seed": 5550123, "theme": "forest", "primary": "green", "moods": ["dark"] } }
]
}POST/api/palettes
Save the pick. Pass the chosen option's recipe exactly as returned, or explicit colors, and a name. A recipe is regenerated server-side, so the saved palette is exactly the option that was shown. The id that comes back is the palette_id for POST /api/sprites and POST /api/sprites/import. Free.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A short name, up to 80 characters. |
recipe | object | One of | The chosen option's recipe from /api/palettes/generate. |
colors | string[] | One of | 2-256 #rrggbb colours instead of a recipe. |
{
"id": "5f0c2a7e-...",
"name": "Mossy dungeon",
"colors": ["#1a2a1c", "..."],
"count": 16,
"source": "generated",
"created_at": "2026-09-11T22:00:00.000Z"
}GET/api/palettes
Your palettes, newest first, as { "palettes": [ ... ] } with the same shape as above. GET /api/palettes/{id} returns one. Every sprite object carries paletteId, null when the sprite has no palette.
POST/api/animations
Start an animation job from a source sprite. Asynchronous: returns an id immediately, then you poll until it finishes (~15s). Animations run one at a time per account, so a batch of N finishes in about N x 15s. The token cost scales with frame_count.
Reference the source by source_generation_id: the id of a sprite you own, from POST /api/sprites (generate) or POST /api/sprites/import. No image bytes travel through this call; we fetch them server-side. The source must be a single still sprite, no larger than 256px on either side, not a sprite sheet or an existing animation.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a sprite you generated or imported. |
preset | string | Yes | Motion preset (Walk, Run, Idle, ...) or Custom. |
frame_count | integer | No | Even, 4–16. Default 8. Drives the token cost. |
custom_prompt | string | Custom only | Free-form motion. Required when preset is Custom. |
palette_extras | integer | No | Colours the animation may add beyond the source sprite's own, 0-256. 0 keeps its palette exact. Raise it only when the motion brings colours the sprite lacks (fire, magic glow, sparks). Omit for the preset's tuned default: Attack 12, Custom 12, every other preset 0. |
framing_margin | integer | No | Percentage the canvas grows by before animating, 0-25, so a jump or a projected effect has room instead of being cropped at the sprite's edge. The result comes back at the grown size. Omit for in-place motion like walks and idles. Default 0. |
framing_anchor | string | No | Where the sprite sits in that grown canvas: top-left, top, top-right, left, center, right, bottom-left, bottom or bottom-right. bottom keeps feet grounded in a jump; a bottom corner opposite the way the sprite faces opens room in front for a projected effect. Default center. |
public | boolean | No | Add to the public gallery. Default false. |
curl https://www.sprite-ai.art/api/animations \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-...", "preset": "Walk", "frame_count": 8 }'const res = await fetch("https://www.sprite-ai.art/api/animations", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: spriteId, // from generate or import
preset: "Walk",
frame_count: 8,
}),
});
const { id } = await res.json(); // poll GET /api/animations/{id}import requests
job = requests.post(
"https://www.sprite-ai.art/api/animations",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"source_generation_id": sprite_id, "preset": "Walk", "frame_count": 8},
).json()
job_id = job["id"] # poll GET /api/animations/{id}{ "id": "a1b2c3d4-...", "status": "processing" }GET/api/animations/{id}
Poll an animation job. While running it returns processing with an optional progress (0–1). When status is completed, the sheet is in image.spritesheetBase64. On failed, tokens are refunded automatically. Poll about every 5 seconds.
# poll every ~5s until status is completed or failed
curl https://www.sprite-ai.art/api/animations/a1b2c3d4-... \
-H "Authorization: Bearer sai_sk_your_key_here"const headers = { Authorization: "Bearer sai_sk_your_key_here" };
while (true) {
await new Promise((r) => setTimeout(r, 5000));
const job = await (await fetch(
"https://www.sprite-ai.art/api/animations/" + id, { headers },
)).json();
if (job.status === "completed") { console.log(job.image.spritesheetBase64); break; }
if (job.status === "failed") { console.error("render failed"); break; }
}import time, requests
headers = {"Authorization": "Bearer sai_sk_your_key_here"}
while True:
time.sleep(5)
job = requests.get(
"https://www.sprite-ai.art/api/animations/" + job_id, headers=headers,
).json()
if job["status"] == "completed":
print(job["image"]["spritesheetBase64"]); break
if job["status"] == "failed":
print("render failed"); break{
"id": "a1b2c3d4-...",
"status": "completed",
"frameWidth": 64,
"frameHeight": 64,
"frameCount": 8,
"fps": 12,
"image": { "format": "png", "spritesheetBase64": "iVBORw0KGgoAAAANS..." }
}A single horizontal-strip PNG. Slice it with frameCount, frameWidth, frameHeight.
POST/api/animations/{id}/cancel
Cancel a running job and refund its tokens. If the job already completed or failed, it returns an error since there is nothing to cancel.
curl -X POST https://www.sprite-ai.art/api/animations/a1b2c3d4-.../cancel \
-H "Authorization: Bearer sai_sk_your_key_here"await fetch(
"https://www.sprite-ai.art/api/animations/" + id + "/cancel",
{ method: "POST", headers: { Authorization: "Bearer sai_sk_your_key_here" } },
);import requests
requests.post(
"https://www.sprite-ai.art/api/animations/" + job_id + "/cancel",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
){ "id": "a1b2c3d4-...", "status": "cancelled" }POST/api/restyles
Change a sprite you own and keep the character. Restyle its look, put it in a new pose, or swap the item it holds — mode says which. Asynchronous like animations: returns an id immediately, then you poll until it finishes (~15s on the normal tier, up to a minute on pro). Cost depends on the tier: 2 tokens on normal, 9 on pro. Presets and custom prompts cost the same.
Reference the source by source_generation_id: the id of a sprite you own, from POST /api/sprites (generate) or POST /api/sprites/import. No image bytes travel through this call; we fetch them server-side. The source must be 32-256px on each side; the result comes back at the source's size, unless framing_margin grows the canvas first.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a sprite you generated or imported, 32-256px per side. |
preset | string | One of preset / custom_prompt | Locked style: fire, ice, gold, zombie, holy, cursed, poison, royal, shadow, steampunk. A preset always means mode restyle. |
custom_prompt | string | One of preset / custom_prompt | What to change, in your own words, up to 500 chars. Read together with mode. |
mode | string | No | restyle (repaint, colours may change), pose (move the character, keep everything else), item (swap one named object), custom. Default restyle. |
tier | string | No | normal or pro. Pro holds the character closer and wins item swaps; normal wins poses. CHANGES THE PRICE. Default normal. |
palette_extras | number | No | Colours the edit may use beyond the sprite's own, 0-256. 0 keeps its palette exact. Defaults per mode: restyle 16, pose 0, item 12, custom 12. |
framing_margin | number | No | Percentage the canvas grows by before the edit, 0-25, so a pose has room. The result comes back at the grown size. Default 0. |
framing_anchor | string | No | Where the sprite sits in that grown canvas, e.g. bottom to keep a character's feet planted. Default center. |
public | boolean | No | Add to the public gallery. Default false. |
curl https://www.sprite-ai.art/api/restyles \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-...", "preset": "gold" }'const res = await fetch("https://www.sprite-ai.art/api/restyles", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: spriteId, // from generate or import
preset: "gold", // or custom_prompt: "obsidian armor, glowing runes"
}),
});
const { id } = await res.json(); // poll GET /api/restyles/{id}import requests
job = requests.post(
"https://www.sprite-ai.art/api/restyles",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"source_generation_id": sprite_id, "preset": "gold"},
).json()
job_id = job["id"] # poll GET /api/restyles/{id}{ "id": "a1b2c3d4-...", "status": "processing" }GET/api/restyles/{id}
Poll a restyle job. While running it returns processing; when status is completed, the sprite is in image.pngBase64 at native resolution. On failed, tokens are refunded automatically. Poll about every 3 seconds.
# poll every ~3s until status is completed or failed
curl https://www.sprite-ai.art/api/restyles/a1b2c3d4-... \
-H "Authorization: Bearer sai_sk_your_key_here"const headers = { Authorization: "Bearer sai_sk_your_key_here" };
while (true) {
await new Promise((r) => setTimeout(r, 3000));
const job = await (await fetch(
"https://www.sprite-ai.art/api/restyles/" + id, { headers },
)).json();
if (job.status === "completed") { console.log(job.image.pngBase64); break; }
if (job.status === "failed") { console.error("restyle failed"); break; }
}import time, requests
headers = {"Authorization": "Bearer sai_sk_your_key_here"}
while True:
time.sleep(3)
job = requests.get(
"https://www.sprite-ai.art/api/restyles/" + job_id, headers=headers,
).json()
if job["status"] == "completed":
print(job["image"]["pngBase64"]); break
if job["status"] == "failed":
print("restyle failed"); break{
"id": "a1b2c3d4-...",
"status": "completed",
"width": 64,
"height": 64,
"isPublic": false,
"slug": null,
"image": { "format": "png", "pngBase64": "iVBORw0KGgoAAAANS..." }
}POST/api/restyles/{id}/cancel
Cancel a running restyle and refund its tokens. If the job already completed or failed, it returns an error since there is nothing to cancel.
curl -X POST https://www.sprite-ai.art/api/restyles/a1b2c3d4-.../cancel \
-H "Authorization: Bearer sai_sk_your_key_here"await fetch(
"https://www.sprite-ai.art/api/restyles/" + id + "/cancel",
{ method: "POST", headers: { Authorization: "Bearer sai_sk_your_key_here" } },
);import requests
requests.post(
"https://www.sprite-ai.art/api/restyles/" + job_id + "/cancel",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
){ "id": "a1b2c3d4-...", "status": "cancelled" }POST/api/combines
Put an item from one sprite you own into another sprite you own. The first sprite keeps its identity and its exact dimensions; the second is where the item is copied from, so its shape and its colours are what you get. The result is locked to both sprites' palettes, so an item never comes back repainted in the character's colours. Asynchronous like restyles: returns an id immediately, then you poll until it finishes (~20s on normal, 3-4 minutes on pro). Cost depends on the tier: 3 tokens on normal, 9 on pro.
Reach for this when the item exists as a sprite. To swap for something you only have words for, use POST /api/restyles with mode: "item" instead.
Both sprites are referenced by id, from POST /api/sprites (generate) or POST /api/sprites/import. No image bytes travel through this call; we fetch them server-side, which also means both sprites must be yours. Each must be 32-256px on each side, and the two ids must differ. The result comes back at the first sprite's size even when framing_margin grows the canvas first.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of the sprite that keeps its identity. Its dimensions are the output's. |
item_generation_id | string | Yes | UUID of the sprite the item is copied from. Must differ from source_generation_id. |
prompt | string | Yes | Where the item goes, up to 500 chars. Say the placement, not the item: the item comes from the image. |
tier | string | No | normal or pro. Normal puts the item in the character's hands and is the default. Pro holds the character closer but places the item worse. CHANGES THE PRICE. |
palette_extras | number | No | Colours the combine may use beyond the two sprites' union, 0-256. 0 locks it to exactly their palettes. Default 4. |
framing_margin | number | No | Percentage the canvas grows by before the edit, 0-25, so a reaching item has room. The sprite still returns at the first sprite's size. Default 0. |
framing_anchor | string | No | Where the sprite sits in that grown canvas, e.g. bottom to keep a character's feet planted. Default center. |
public | boolean | No | Add to the public gallery. Default false. |
curl https://www.sprite-ai.art/api/combines \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-...", "item_generation_id": "e5f6a7b8-...", "prompt": "holding this shield in his free hand" }'const res = await fetch("https://www.sprite-ai.art/api/combines", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: characterId, // keeps its identity and its size
item_generation_id: shieldId, // where the item comes from
prompt: "holding this shield in his free hand",
}),
});
const { id } = await res.json(); // poll GET /api/combines/{id}import requests
job = requests.post(
"https://www.sprite-ai.art/api/combines",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={
"source_generation_id": character_id,
"item_generation_id": shield_id,
"prompt": "holding this shield in his free hand",
},
).json()
job_id = job["id"] # poll GET /api/combines/{id}{ "id": "a1b2c3d4-...", "status": "processing" }GET/api/combines/{id}
Poll a combine job. While running it returns processing; when status is completed, the sprite is in image.pngBase64 at the first sprite's size. On failed, tokens are refunded automatically. Poll about every 3 seconds. A normal-tier run is usually done on the first or second poll; pro takes minutes.
curl https://www.sprite-ai.art/api/combines/a1b2c3d4-... \
-H "Authorization: Bearer sai_sk_your_key_here"let job;
do {
await new Promise((r) => setTimeout(r, 3000));
job = await (await fetch(
"https://www.sprite-ai.art/api/combines/" + id, { headers },
)).json();
if (job.status === "failed") { console.error("combine failed"); break; }
} while (job.status === "processing");import time, requests
while True:
time.sleep(3)
job = requests.get(
"https://www.sprite-ai.art/api/combines/" + job_id, headers=headers,
).json()
if job["status"] != "processing":
if job["status"] == "failed":
print("combine failed")
break{
"id": "a1b2c3d4-...",
"status": "completed",
"width": 64,
"height": 64,
"isPublic": false,
"slug": null,
"image": { "format": "png", "pngBase64": "iVBORw0KGgo..." }
}POST/api/combines/{id}/cancel
Cancel a running combine and refund its tokens. If the job already completed or failed, it returns an error since there is nothing to cancel.
curl -X POST https://www.sprite-ai.art/api/combines/a1b2c3d4-.../cancel \
-H "Authorization: Bearer sai_sk_your_key_here"await fetch(
"https://www.sprite-ai.art/api/combines/" + id + "/cancel",
{ method: "POST", headers: { Authorization: "Bearer sai_sk_your_key_here" } },
);import requests
requests.post(
"https://www.sprite-ai.art/api/combines/" + job_id + "/cancel",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
){ "id": "a1b2c3d4-...", "status": "cancelled" }POST/api/touch-ups
Repair one part of a sprite you own and leave every other pixel alone. Give it a region and a sentence, and the selected area is redrawn in the sprite's own colors while everything outside it comes back byte-identical. The result is saved as a new sprite id you can animate, rotate or restyle. Synchronous, about 15 to 30 seconds. Cost: 1 token.
This repairs a detail. It is not an item-swap tool: asked to change a weapon it drew the new one once in four tries, while POST /api/restyles with the item preset did it every time. Use touch-ups for eyes, a face, a hand, a dent in a helmet. The sprite comes back at its original size, and the price is the same whether it is 64px or 512px.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a still sprite you own, 32-1024px per side. |
prompt | string | Yes | What to fix, 1-300 characters. E.g. "glowing red eyes in the visor". |
region | object | Yes | The area to repair, in sprite pixels: { x, y, width, height }, origin top-left. |
public | boolean | No | Opt the result into the public gallery. Default false. |
The region is clipped to the sprite, and the clipped box is echoed back in the response so you can draw it over the result. Roughly 200x200 pixels is the ceiling on the region, whatever the sprite's own size: a window is cropped around it and composited back, so a large sprite is fine but a large region is refused. To change a whole sprite, use POST /api/restyles.
curl https://www.sprite-ai.art/api/touch-ups \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"source_generation_id": "a1b2c3d4-...",
"prompt": "glowing red eyes in the visor",
"region": { "x": 32, "y": 10, "width": 12, "height": 11 }
}'const res = await fetch("https://www.sprite-ai.art/api/touch-ups", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: spriteId,
prompt: "glowing red eyes in the visor",
region: { x: 32, y: 10, width: 12, height: 11 },
}),
});
const fixed = await res.json(); // completed inline, no polling
// fixed.id is a new sprite; fixed.region is what was actually repairedimport base64, requests
fixed = requests.post(
"https://www.sprite-ai.art/api/touch-ups",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={
"source_generation_id": sprite_id,
"prompt": "glowing red eyes in the visor",
"region": {"x": 32, "y": 10, "width": 12, "height": 11},
},
).json()
with open("sprite_fixed.png", "wb") as f:
f.write(base64.b64decode(fixed["image"]["pngBase64"])){
"id": "f7a8b9c0-...",
"status": "completed",
"width": 64,
"height": 64,
"region": { "x": 32, "y": 10, "width": 12, "height": 11 },
"isPublic": false,
"slug": null,
"image": { "format": "png", "pngBase64": "iVBORw0KGgoAAAANS..." }
}POST/api/rotations
Turn one still sprite you own into a full 8-direction set: S, SE, E, NE, N, NW, W, SW, same character and same palette. Returns a horizontal strip of 8 equal frames in that spin order plus a fidelity score: the sheet is calibrated against the source and a low-scoring roll is redone automatically. Synchronous: one request, the finished strip inline, about a minute and up to 2.5 minutes when the quality gate forces a second roll. Cost: 12 tokens.
The source must be a still sprite, 32-256px per side, not an animation. The result is saved as an animation row, so GET /api/sprites/{id} and the download URL treat the strip like a spritesheet. Slice it every frameWidth pixels for the individual directions.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a still sprite you own, 32-256px per side. |
public | boolean | No | Opt the set into the public gallery. Default false. |
curl https://www.sprite-ai.art/api/rotations \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-..." }'const res = await fetch("https://www.sprite-ai.art/api/rotations", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ source_generation_id: spriteId }), // a still, 32-256px
});
const set = await res.json(); // completed inline, ~1-2 minutes
// set.directions lists the 8 frames left to right: s, se, e, ne, n, nw, w, swimport base64, requests
rotation = requests.post(
"https://www.sprite-ai.art/api/rotations",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"source_generation_id": sprite_id},
).json()
with open("knight_8dir.png", "wb") as f:
f.write(base64.b64decode(rotation["image"]["spritesheetBase64"])){
"id": "c9d0e1f2-...",
"status": "completed",
"frameWidth": 64,
"frameHeight": 64,
"frameCount": 8,
"fps": 6,
"directions": ["s", "se", "e", "ne", "n", "nw", "w", "sw"],
"calibrationScore": 0.94,
"isPublic": false,
"image": { "format": "png", "spritesheetBase64": "iVBORw0KGgoAAAANS..." }
}POST/api/normal-maps
Generate a tangent-space normal map for a sprite or animation you own, for dynamic lighting in a game engine. Our own model, trained on real 3D geometry rather than edge-detect heuristics: the output encodes the subject's actual form, and its alpha channel is a pixel-exact copy of the source silhouette. Synchronous: the finished PNG comes back inline in a few seconds, no job to poll. Cost: 1 token per frame.
An animation source is normal-mapped per frame automatically (the model is deterministic, so frames never flicker) and comes back as the same-layout horizontal strip. Load it in your engine exactly like the animation itself.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a sprite or animation you own. Frames must be 32-256px per side. |
strength | number | No | Detail injection, 0-5, default 0. 0 is truest to the geometry; ~2.5 re-adds the sprite's drawn shading for a hand-crafted look. |
flip_green | boolean | No | Flip green for DirectX / +Y-down engines (Unreal). Default false = OpenGL / +Y-up (Godot, Unity, WebGL). |
columns | number | No | Only for a still that is really a sprite sheet: its column count. Ignored for animations (layout is known). columns x rows <= 24. |
rows | number | No | Sheet row count, with columns. Default 1. |
curl https://www.sprite-ai.art/api/normal-maps \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-..." }'const res = await fetch("https://www.sprite-ai.art/api/normal-maps", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: spriteId, // a sprite OR an animation
}),
});
const map = await res.json(); // completed inline, no polling
// convention most engines auto-detect: save as <sprite-name>_n.pngimport base64, requests
map = requests.post(
"https://www.sprite-ai.art/api/normal-maps",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"source_generation_id": sprite_id},
).json()
with open("sprite_n.png", "wb") as f:
f.write(base64.b64decode(map["image"]["pngBase64"])){
"id": "e5f6a7b8-...",
"status": "completed",
"width": 64,
"height": 64,
"frameCount": 1,
"tokensSpent": 1,
"image": { "format": "png", "pngBase64": "iVBORw0KGgoAAAANS..." }
}POST/api/background-removals
Remove a flat, single-color background from a sprite or animation you own and get back a transparent PNG, saved as a new sprite id you can animate, restyle or normal-map. Two methods, the same two the studio's Remove background tool runs: standard is a chroma key and is free, refined is palette matting against the sprite's own colors and costs 1 token. Refined gives the cleaner edge on AI-generated pixel art. The background color is sampled from the four corners unless you pass matte_color. Synchronous, well under a second.
Sprites generated here already have transparent backgrounds. This endpoint is for imported screenshots and renders on a flat color, and a source whose corners are already transparent is refused. An animation is keyed as one strip and comes back as an animation. Cutouts are always private.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | UUID of a sprite or animation you own. |
method | string | No | standard (free, chroma key) or refined (1 token, palette matting). Default standard. |
matte_color | string | No | Background color to remove, as #rrggbb. Default: sampled from the corners. |
tolerance | number | No | standard only. How far from the background color a pixel may be and still be removed, 0-300. Default 200. |
palette_size | number | No | refined only. Cap on the sprite colors used for matting, 16-256. Default 64. |
alpha_threshold | number | No | refined only. Coverage above which an edge pixel stays opaque, 0.3-0.9. Default 0.7. |
curl https://www.sprite-ai.art/api/background-removals \
-H "Authorization: Bearer sai_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "source_generation_id": "a1b2c3d4-...", "method": "refined" }'const res = await fetch("https://www.sprite-ai.art/api/background-removals", {
method: "POST",
headers: {
Authorization: "Bearer sai_sk_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
source_generation_id: spriteId, // an imported screenshot or render
method: "refined", // omit for the free standard method
}),
});
const cutout = await res.json(); // completed inline, no polling
// cutout.id is a new sprite: animate or restyle it like any otherimport base64, requests
cutout = requests.post(
"https://www.sprite-ai.art/api/background-removals",
headers={"Authorization": "Bearer sai_sk_your_key_here"},
json={"source_generation_id": sprite_id, "method": "refined"},
).json()
with open("sprite_cutout.png", "wb") as f:
f.write(base64.b64decode(cutout["image"]["pngBase64"])){
"id": "f7a8b9c0-...",
"status": "completed",
"method": "refined",
"width": 64,
"height": 64,
"frameCount": null,
"matteColor": "#ff00ff",
"tokensSpent": 1,
"image": { "format": "png", "pngBase64": "iVBORw0KGgoAAAANS..." }
}Errors
Every error returns the same JSON shape. Branch on code, not message.
{ "code": "invalid_request", "message": "...", "details": {} }| HTTP | Code | Cause |
|---|---|---|
400 | invalid_request | Body failed validation. |
401 | invalid_api_key | Key missing or not recognized. |
402 | insufficient_tokens | Not enough tokens. |
402 | upgrade_required | Needs a paid plan. |
403 | api_key_revoked | The key was revoked. |
404 | not_found | Not found, or not yours. |
422 | nsfw_prompt | Prompt rejected by the filter. |
429 | rate_limited | Too many requests. |
500 | provider_failure | Render failed. Tokens refunded. |
Asset packs
One prompt can plan a whole pack: the anchor sprite, its facing directions, an animation per direction, restyles and lighting maps, priced step by step and run only after you approve the total. The four endpoints (POST /api/agent, POST /api/agent/{id}/run, GET /api/agent/{id}, POST /api/agent/{id}/cancel) are on the Sprite Agent page.
Last updated October 9, 2026