Skip to content
AI Sprite Maker

Docs

REST API reference

Updated November 6, 2026
On this page
  1. Quickstart
  2. Authentication
  3. Conventions
  4. Credits and pricing
  5. Endpoints
  6. GET /me
  7. GET /motions
  8. POST /jobs
  9. GET /jobs/:id
  10. GET /jobs
  11. POST /jobs/:id/cancel
  12. POST /uploads
  13. GET /assets
  14. GET /assets/:id/file/:name
  15. API keys: GET, POST and DELETE /keys
  16. OpenAPI spec
  17. Polling a job to completion
  18. Errors
  19. Rate limits
  20. Webhooks
  21. Using the API from AI agents
  22. Changelog and versioning

The AI Sprite Maker API does everything the web generator does. You send a job, we reserve its credits, run it in the background, and hand back a PNG sprite sheet with JSON metadata your engine can read. It uses the same credit balance and the same prices as the website.

At a glance

Base URL https://aispritemaker.com/api/v1 · JSON in and out · Authorization: Bearer asm_live_… · jobs are asynchronous: POST /jobs, then poll GET /jobs/:id · included with every plan, Free too. Machine-readable spec: /openapi.json (OpenAPI 3.1).

Quickstart

  1. Sign in (any plan works, including Free) and create a key at Dashboard → API keys. The key is shown once; store it in an environment variable.
  2. Queue a job. This one animates a character with the library walk motion in 4 directions, which costs 4 × 10 = 40 credits (drawing the character from the prompt is included):
curl https://aispritemaker.com/api/v1/jobs \
  -H "Authorization: Bearer $ASM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "animation",
    "prompt": "small fox knight, blue cape, round shield",
    "motion_id": "walk",
    "style": "16-bit",
    "view": "topdown",
    "directions": 4,
    "size": 64
  }'
const res = await fetch("https://aispritemaker.com/api/v1/jobs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ASM_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "animation",
    prompt: "small fox knight, blue cape, round shield",
    motion_id: "walk",
    style: "16-bit",
    view: "topdown",
    directions: 4,
    size: 64,
  }),
});
const { job } = await res.json();
console.log(job.id, job.status, job.credits_reserved);
import os, requests

res = requests.post(
    "https://aispritemaker.com/api/v1/jobs",
    headers={"Authorization": f"Bearer {os.environ['ASM_API_KEY']}"},
    json={
        "type": "animation",
        "prompt": "small fox knight, blue cape, round shield",
        "motion_id": "walk",
        "style": "16-bit",
        "view": "topdown",
        "directions": 4,
        "size": 64,
    },
)
job = res.json()["job"]
print(job["id"], job["status"], job["credits_reserved"])
  1. Poll the job every 2 seconds until status is succeeded, failed or canceled, then download the files in outputs. A full polling loop is in Polling a job to completion.

Authentication

Every request needs an API key in the Authorization header:

Authorization: Bearer asm_live_3f9c2a…
  • Keys start with asm_live_. We store only a SHA-256 hash, so a lost key cannot be recovered; revoke it and make a new one.
  • Keys belong to your account and spend your credits. Use one key per app or environment so you can revoke one without breaking the others.
  • API access is included with every plan, including Free. Free accounts are limited by their credits (10 a day plus 30 on sign-up), 128 px frames and one job at a time.
  • Never put a key in client-side code or a public repo. Call the API from your server, build script or editor tool.

Conventions

  • Base URL: https://aispritemaker.com/api/v1
  • Format: JSON request and response bodies, UTF-8. Send Content-Type: application/json.
  • IDs: jobs use sortable ULIDs (01JC8Y…). Treat all IDs as opaque strings.
  • Times: ISO 8601 in UTC, e.g. 2026-11-06T09:41:07.118Z.
  • Asset URLs are signed and short-lived. Download outputs when the job finishes, or fetch the job again to get fresh URLs.
  • Idempotency: send an Idempotency-Key header on POST /jobs (any unique string up to 64 characters, e.g. a UUID). Retrying with the same key returns the original job instead of creating and charging a second one.

Credits and pricing

Each job reserves the most it can cost when you create it, charges what it actually used when it finishes, and releases the rest. Failed and cancelled jobs release everything. credits_reserved and credits_charged on the job show both numbers.

JobtypeCredits
Still sprite, character, item or UI piecesprite2 per image (count: 4 = 4 × 2)
Edit, inpaint or recolour an imageedit2
Library motion, per directionanimation10 × directions
4-direction turnarounddirections8
8-direction turnarounddirections14
Seamless tileset (up to 16 tiles)tileset6
Custom motion not in the librarycustom_motion30
Video or GIF to sprite, AI cleanupvideo_to_sprite5 per second of footage

An animation costs a flat 10 credits per direction, including drawing the character when you only pass a prompt. A directions or custom_motion job given only a text prompt (no character_asset_id or reference image) first draws the character, which adds 2 credits. On Pro one credit costs about $0.008, so a 4-direction walk cycle is roughly $0.076 per direction. The pricing page has the full table.

Endpoints

MethodPathWhat it does
GET/meYour account, plan, credit balance and concurrency
GET/motionsThe motion library (public, no key needed)
POST/jobsCreate a job
GET/jobs/:idGet one job and its outputs
GET/jobsList your jobs, newest first
POST/jobs/:id/cancelCancel a queued or running job
POST/uploadsGet a URL to upload a reference image, GIF or video
GET/assetsList your output files, newest first
GET/assets/:id/file/:nameDownload an output file
GET POST DELETE/keys, /keys/:idManage API keys

GET /me

Returns who the key belongs to and what they can spend.

curl https://aispritemaker.com/api/v1/me \
  -H "Authorization: Bearer $ASM_API_KEY"
const me = await fetch("https://aispritemaker.com/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.ASM_API_KEY}` },
}).then((r) => r.json());
me = requests.get(
    "https://aispritemaker.com/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['ASM_API_KEY']}"},
).json()

Response:

{
  "user": { "id": "u_7Hq2…", "email": "you@studio.dev", "name": "Mara", "image": null },
  "plan": "pro",
  "credits": { "total": 2361, "daily": 10, "subscription": 2251, "packs": 100 },
  "concurrency": 4
}

credits.total is what you can spend right now. Daily credits are used first, then plan credits, then sign-up and promotional credits, then packs.

GET /motions

Lists the motion library. No authentication required, so you can build a motion picker without exposing a key.

curl https://aispritemaker.com/api/v1/motions
{
  "motions": [
    {
      "id": "walk",
      "name": "Walk",
      "category": "locomotion",
      "loop": true,
      "frames": 8,
      "fps": 10,
      "views": ["side", "topdown"],
      "preview_url": "https://aispritemaker.com/showcase/motions/walk.png"
    }
  ]
}

Pass id as motion_id when you create an animation job. frames and fps are the defaults the output will use. Browse them visually in the motion library.

POST /jobs

Creates a job, reserves its credits and returns 202 Accepted with the job in queued state.

Body parameters

FieldTypeUsed byDescription
typestringallsprite, animation, directions, tileset, edit, custom_motion or video_to_sprite
promptstring ≤ 1000mostWhat to draw: subject, outfit, colours, props. Leave out style and camera words; style and view handle those
stylestringallStyle preset: 16-bit (default), 8-bit, gba, snes, game-boy, hd-pixel, chibi, isometric, 1-bit, pico-8. Older aliases 16bit, 8bit, nes, gameboy, 1bit and pico8 also work
viewstringallside, topdown or isometric
sizeintegerallFrame size in px: 32, 48, 64, 96, 128, 256, 512. Default 64 (32 for tilesets). Capped by plan: Free 128, Starter 256, Pro and Studio 512
paletteinteger 2–256allMaximum colours. Defaults to the style preset (32 for 16-bit, 24 for gba, 4 for 8-bit). game-boy, 1-bit and pico-8 always use their fixed palettes and ignore this field
count1 or 4spriteNumber of variations
motion_idstringanimationA motion from GET /motions
directions1, 4 or 8animation, directionsDirections to render. Side-view-only motions accept 1
character_asset_idstringanimation, directions, edit, custom_motion, spriteReuse a sprite from an earlier job so every animation shows the same character
reference_upload_idstringsame as aboveAn uploaded PNG, JPEG or WebP to use as the character or style reference
motion_promptstring ≤ 600custom_motionDescribe a motion that is not in the library
video_upload_idstringvideo_to_spriteAn uploaded GIF or video

Required fields by type

typeNeeds
spriteprompt, or a reference
tilesetprompt
editprompt (the instruction) and a reference (reference_upload_id or character_asset_id)
animationmotion_id, and prompt or a reference
directionsprompt or a reference; directions must be 4 or 8
custom_motionmotion_prompt, and prompt or a reference
video_to_spritevideo_upload_id

Example: draw a character, then animate that exact character

# 1. Four variations of a still sprite
curl https://aispritemaker.com/api/v1/jobs \
  -H "Authorization: Bearer $ASM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1d2c1e-hero-v1" \
  -d '{"type":"sprite","prompt":"dwarf miner, orange beard, pickaxe, leather apron","style":"16-bit","view":"side","size":64,"count":4}'

# 2. Pick one output asset id and animate it
curl https://aispritemaker.com/api/v1/jobs \
  -H "Authorization: Bearer $ASM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"animation","character_asset_id":"as_01JC9…","motion_id":"attack-slash","view":"side","directions":1}'
const api = (path, body) =>
  fetch(`https://aispritemaker.com/api/v1${path}`, {
    method: body ? "POST" : "GET",
    headers: {
      Authorization: `Bearer ${process.env.ASM_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: body && JSON.stringify(body),
  }).then((r) => r.json());

const { job: still } = await api("/jobs", {
  type: "sprite",
  prompt: "dwarf miner, orange beard, pickaxe, leather apron",
  style: "16-bit",
  view: "side",
  size: 64,
  count: 4,
});
// ...wait for it to succeed, then:
const { job: anim } = await api("/jobs", {
  type: "animation",
  character_asset_id: chosenAssetId,
  motion_id: "attack-slash",
  view: "side",
  directions: 1,
});
API = "https://aispritemaker.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['ASM_API_KEY']}"}

still = requests.post(f"{API}/jobs", headers=H, json={
    "type": "sprite",
    "prompt": "dwarf miner, orange beard, pickaxe, leather apron",
    "style": "16-bit", "view": "side", "size": 64, "count": 4,
}).json()["job"]
# ...wait for it to succeed, then:
anim = requests.post(f"{API}/jobs", headers=H, json={
    "type": "animation",
    "character_asset_id": chosen_asset_id,
    "motion_id": "attack-slash",
    "view": "side",
    "directions": 1,
}).json()["job"]

Response 202 Accepted

{
  "job": {
    "id": "01JC9A6Q8W3R0V5N2TGBX4K7MD",
    "type": "sprite",
    "status": "queued",
    "step": "queued",
    "progress": 0,
    "credits_reserved": 8,
    "credits_charged": 0,
    "created_at": "2026-11-06T09:41:07.118Z",
    "outputs": []
  }
}

GET /jobs/:id

Returns the job. While it runs, step names the current stage and progress goes from 0 to 1. When status is succeeded, outputs lists the files.

{
  "job": {
    "id": "01JC9B2M4F…",
    "type": "animation",
    "status": "succeeded",
    "step": "done",
    "progress": 1,
    "credits_reserved": 40,
    "credits_charged": 40,
    "created_at": "2026-11-06T09:42:10.002Z",
    "finished_at": "2026-11-06T09:43:31.540Z",
    "outputs": [
      {
        "id": "as_01JC9B…",
        "kind": "sheet",
        "url": "https://aispritemaker.com/api/v1/assets/as_01JC9B…/file/sheet.png?sig=…",
        "width": 512,
        "height": 256,
        "meta": {
          "frames": 8, "columns": 8, "rows": 4, "fps": 10,
          "frame_w": 64, "frame_h": 64,
          "directions": ["down", "left", "right", "up"]
        }
      },
      { "id": "as_01JC9C…", "kind": "gif", "url": "…/file/preview.gif?sig=…", "width": 64, "height": 64 },
      { "id": "as_01JC9D…", "kind": "json", "url": "…/file/sheet.json?sig=…", "width": 512, "height": 256 },
      { "id": "as_01JC9E…", "kind": "frames_zip", "url": "…/file/frames.zip?sig=…", "width": 64, "height": 64 }
    ]
  }
}

Output kinds

kindWhat it is
sheetPNG sprite sheet. One row per direction, in the order given by meta.directions, meta.columns frames per row
spriteA single transparent PNG (still sprites, turnaround cells)
gifAnimated preview GIF
jsonFrame rectangles, fps and direction names for engines and packers
frames_zipEvery frame as its own PNG
previewA small preview image for listings

Job status values

statusMeaning
queuedAccepted, waiting for a worker slot (Free runs 1 job at a time, Starter 2, Pro 4, Studio 10)
runningGenerating. Check step and progress
succeededDone. outputs is filled and credits_charged is final
failedStopped with error.code and error.message. Reserved credits were released
canceledCancelled by you before it finished. Reserved credits were released

GET /jobs

Lists your jobs, newest first. Query parameters: limit (1–100, default 20) and cursor (the next_cursor from the previous page).

curl "https://aispritemaker.com/api/v1/jobs?limit=20" \
  -H "Authorization: Bearer $ASM_API_KEY"
{ "jobs": [ { "id": "01JC9B2M4F…", "type": "animation", "status": "succeeded", "…": "…" } ], "next_cursor": "01JC97…" }

When next_cursor is null there are no more pages.

POST /jobs/:id/cancel

Cancels a job that is queued or running. Credits for work that has not produced output are released. Cancelling a finished job returns it unchanged.

curl -X POST https://aispritemaker.com/api/v1/jobs/01JC9B2M4F…/cancel \
  -H "Authorization: Bearer $ASM_API_KEY"

POST /uploads

Reference images and footage are uploaded straight to storage in two steps: ask for an upload URL, then PUT the bytes to it.

content_typeMax sizeUse for
image/png, image/jpeg, image/webp10 MBCharacter or style references, images to edit
image/gif20 MBvideo_to_sprite
video/mp4, video/webm, video/quicktime50 MBvideo_to_sprite
# 1. Ask for an upload slot
curl https://aispritemaker.com/api/v1/uploads \
  -H "Authorization: Bearer $ASM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"image/png","bytes":48213}'
# -> {"upload_id":"up_01JC…","put_url":"https://…"}

# 2. Send the file
curl -X PUT "$PUT_URL" -H "Content-Type: image/png" --data-binary @hero.png
import { readFile } from "node:fs/promises";

const file = await readFile("hero.png");
const { upload_id, put_url } = await api("/uploads", {
  content_type: "image/png",
  bytes: file.byteLength,
});
await fetch(put_url, { method: "PUT", headers: { "Content-Type": "image/png" }, body: file });
// now use { reference_upload_id: upload_id } in POST /jobs
data = open("hero.png", "rb").read()
up = requests.post(f"{API}/uploads", headers=H,
                   json={"content_type": "image/png", "bytes": len(data)}).json()
requests.put(up["put_url"], data=data, headers={"Content-Type": "image/png"})
# now use {"reference_upload_id": up["upload_id"]} in POST /jobs

Uploads that are never used expire automatically. Uploaded files are deleted 14 days after the job that used them finishes.

Rolling out: custom motions and server-side video conversion

video_to_sprite jobs accept GIF uploads today. MP4, WebM and MOV uploads are accepted by /uploads, but server-side conversion of them, and custom_motion jobs, depend on a video pipeline that is still being switched on. Until then those requests return 503 feature_unavailable and nothing is charged; watch the changelog. The free Video to Sprite Sheet tool converts video in your browser in the meantime.

GET /assets

Lists files your jobs produced, newest first, with fresh signed URLs. Query parameters: limit (1–100, default 20), cursor, and kind to filter (for example sheet or sprite). Use it to find a character_asset_id to reuse.

curl "https://aispritemaker.com/api/v1/assets?kind=sprite&limit=10" \
  -H "Authorization: Bearer $ASM_API_KEY"

GET /assets/:id/file/:name

Downloads an output file. The url on each output already points here with a signature, so you normally just GET that URL. Without a valid signature, the request must carry your key. Responses are either the bytes or a redirect to a short-lived storage URL, so follow redirects (curl -L).

curl -L -o walk.png "https://aispritemaker.com/api/v1/assets/as_01JC9B…/file/sheet.png?sig=…"

API keys: GET, POST and DELETE /keys

List and revoke keys from code, for example when rotating them in CI. Creating a key (POST /keys) only works from a signed-in browser session in the dashboard, never with another API key; a key-authenticated POST gets 403 session_required. An account can have up to 20 active keys.

# list (only prefixes are returned, never full keys)
curl https://aispritemaker.com/api/v1/keys -H "Authorization: Bearer $ASM_API_KEY"

# revoke
curl -X DELETE https://aispritemaker.com/api/v1/keys/key_01JC… -H "Authorization: Bearer $ASM_API_KEY"

OpenAPI spec

The full API is described in an OpenAPI 3.1 document at https://aispritemaker.com/openapi.json: every endpoint, request body, response shape, enum (job types, styles, sizes) and error status. Import it into Postman, Insomnia or Bruno, or generate a typed client:

npx openapi-typescript https://aispritemaker.com/openapi.json -o aispritemaker.d.ts

Polling a job to completion

Jobs usually take from a few seconds (still sprites) to a couple of minutes (multi-direction animations). Poll every 2 seconds; polling is cheap and does not count against your credits.

JOB=01JC9B2M4F…
while :; do
  STATUS=$(curl -s https://aispritemaker.com/api/v1/jobs/$JOB \
    -H "Authorization: Bearer $ASM_API_KEY" | jq -r .job.status)
  echo "$STATUS"
  case $STATUS in succeeded|failed|canceled) break;; esac
  sleep 2
done
async function waitForJob(id, { interval = 2000, timeout = 10 * 60_000 } = {}) {
  const started = Date.now();
  for (;;) {
    const { job } = await api(`/jobs/${id}`);
    if (job.status === "succeeded") return job;
    if (job.status === "failed" || job.status === "canceled") {
      throw new Error(`${job.status}: ${job.error?.code} ${job.error?.message ?? ""}`);
    }
    if (Date.now() - started > timeout) throw new Error("timed out");
    await new Promise((r) => setTimeout(r, interval));
  }
}

const done = await waitForJob(anim.id);
const sheet = done.outputs.find((o) => o.kind === "sheet");
console.log(sheet.url, sheet.meta);
import time

def wait_for_job(job_id, interval=2, timeout=600):
    started = time.time()
    while True:
        job = requests.get(f"{API}/jobs/{job_id}", headers=H).json()["job"]
        if job["status"] == "succeeded":
            return job
        if job["status"] in ("failed", "canceled"):
            raise RuntimeError(f"{job['status']}: {job.get('error')}")
        if time.time() - started > timeout:
            raise TimeoutError(job_id)
        time.sleep(interval)

done = wait_for_job(anim["id"])
sheet = next(o for o in done["outputs"] if o["kind"] == "sheet")
open("attack.png", "wb").write(requests.get(sheet["url"]).content)

Errors

Errors use standard HTTP status codes and a consistent body:

{ "error": { "code": "insufficient_credits", "message": "This needs 40 credits; you have 12." } }

Match on code, which is stable. message is for humans and may change. Validation errors also include an issues array with the field path and message for each problem.

StatuscodeWhat happenedWhat to do
400invalid_jsonBody is not valid JSONCheck quoting and Content-Type
400invalid_requestA field is missing, the wrong type, or not allowed for this job typeRead issues; the table under POST /jobs lists required fields
400unknown_motionmotion_id is not in the libraryFetch GET /motions
400invalid_upload / unsupported_typeWrong kind of file for this job, or an unsupported content typeUse PNG, JPEG or WebP for references and GIF or video for footage
401unauthorizedNo key or sessionSend Authorization: Bearer asm_live_…
401invalid_api_keyKey is wrong or revokedCreate a new key
402insufficient_creditsBalance is lower than the job’s reservationWait for daily credits, buy a pack, or upgrade
403plan_limitsize is above your plan’s maximumUse a smaller size or upgrade
404job_not_found / asset_not_found / upload_not_found / not_foundThe ID does not exist or belongs to another accountCheck the ID
409upload_incompleteThe job references an upload whose bytes never arrivedPUT the file to put_url first
413too_largeUpload exceeds the size limitSee the upload table
429rate_limitedToo many requestsBack off and retry after a minute
429concurrency_limitYour plan’s parallel job limit is fullWait for a running job to finish
429too_many_uploadsMore than 50 pending uploadsUse or let pending uploads expire
500internal_errorOur bugRetry with the same Idempotency-Key; email us if it keeps happening
503feature_unavailableThe feature is temporarily off or not yet enabledRetry later or use the alternative in the message

A job can also fail after it was accepted, for example if moderation blocks the prompt or every model provider is down. Then GET /jobs/:id returns status: "failed" with an error object, and the reserved credits are already back in your balance.

Rate limits

LimitFreeStarterProStudio
API requests per key120 / min120 / min120 / min120 / min
Job creations (POST /jobs)20 / min20 / min20 / min20 / min
Jobs running at the same time12410
Maximum size128 px256 px512 px512 px
Priority queue———Yes

Requests over a limit get 429 and cost nothing. Extra jobs beyond your concurrency are rejected rather than queued, so keep your own queue and submit the next job when one finishes. If you need higher limits for a production pipeline, email hello@aispritemaker.com with your expected volume.

Webhooks

Coming soon

Job-completion webhooks (an HTTPS POST to your URL, signed with HMAC-SHA256, when a job succeeds or fails) are in development. Until they ship, poll GET /jobs/:id as shown above. We will announce the payload format and signing details in the changelog before turning them on.

Using the API from AI agents

The same operations are exposed as tools on our MCP server, so Claude Code, Cursor, Codex and other MCP clients can generate sprites straight into your project. See the MCP server docs. A machine-readable summary of the product and API lives at /llms.txt.

Changelog and versioning

The API is versioned in the path (/api/v1). We add fields and endpoints without changing the version, so ignore fields you don’t recognise. Breaking changes get a new version, and the old one keeps working for at least 90 days. Changes are listed in the changelog.

Next → MCP server

Found a mistake or something out of date? Email hello@aispritemaker.com and we will fix it.