A UI mockup or a description in, a complete game UI kit out: matching panels, frames, buttons with their states, bars, slots and icons, each sliced, named and given 9-slice borders, packaged with a Godot theme and a Unity package. Runs asynchronously, typically 45 to 90 seconds.
403 not_entitled and are not charged.Send either a mockup or a description. A mockup skips the concept step and its charge, and is the most faithful way to get the kit you have in mind (a screenshot, a paintover, or a mockup from another image tool all work).
| Field | Type | Notes |
|---|---|---|
| image_b64 | string | UI mockup or screenshot to rebuild as a kit. Skips the concept charge |
| prompt | string | The screen to design, e.g. "inventory for a cozy farming game". Required without a mockup (or send style) |
| style | string | The look, e.g. "warm walnut wood frames, cream parchment panels, moss green accents". Defaults to the prompt |
| screen | string | main menu, HUD, pause, settings, inventory, dialogue, shop, quest log, character select, map, game over or custom |
| game | string | One line about your game. Without it, concepts drift toward generic fantasy |
| specific | string | Extra direction; it wins over everything else |
| scope | string | Only build part of the screen, e.g. "only the buttons" |
| quality | string | epic (default) or mythic (richer materials) |
| max_sheets | int | 1 to 3, default 2. The cost cap: busy screens split across sheets so every piece stays large. You are charged only for sheets that are planned and succeed |
| states | bool | Add hover, pressed and disabled for every button, tab and slot. Default false (only what the mockup shows) |
| pixel | bool | Convert the whole kit to pixel art with one shared palette. Default false (HD) |
| pixel_size | int | 2 to 12, default 3. Source pixels per art pixel when pixel is on; smaller keeps more detail |
| colours | int | 8 to 64, default 24. Kit palette size when pixel is on |
| name | string | Kit name, shown in the app |
The worst case (concept plus max_sheets sheets) is reserved upfront. Sheets the plan doesn't need, sheets that fail, and failed jobs are refunded to the credits that paid. Returns 202:
{"kit_id": "9f56…", "status": "queued", "credits_reserved": 12, "credits_remaining": 488}
Poll every 5 seconds. Statuses: queued → running (with stage: concept, planning, sheets, slicing, pixel, naming) → succeeded or failed. A job interrupted by a restart on our side fails and refunds on your next poll.
On success:
{
"kit_id": "9f56…",
"status": "succeeded",
"name": "Seed Shop",
"pixel": false,
"sheets": 2,
"credits_charged": 12, "refunded": 0,
"pieces": [
{"name": "buy_button_hover", "family": "buy_button", "kind": "button", "state": "hover",
"sheet": 0, "x": 412, "y": 610, "width": 238, "height": 74,
"nine_slice": false, "borders": {"l": 22, "t": 18, "r": 22, "b": 18}, "middle": "stretch"},
…
],
"download_url": "https://spritelab.dev/api/v1/ui-kit/9f56…/download"
}
The same zip the app exports: png/ (every piece at the export scale) and png_native/ (1x), a packed atlas with TexturePacker-style JSON carrying the 9-slice borders, a Godot 4 StyleBoxTexture per 9-slice piece plus a Theme, a Unity .unitypackage with sprite borders set, and the concept as a layout reference. Send the same Authorization header.
import base64, time, requests
KEY = {"Authorization": "Bearer sl_live_YOUR_KEY"}
BASE = "https://spritelab.dev/api/v1"
mockup = base64.b64encode(open("shop_mockup.png", "rb").read()).decode()
r = requests.post(f"{BASE}/ui-kit", headers=KEY, json={
"image_b64": mockup,
"screen": "shop",
"game": "cozy farming sim",
"max_sheets": 2,
})
r.raise_for_status()
kit_id = r.json()["kit_id"]
while True:
time.sleep(5)
k = requests.get(f"{BASE}/ui-kit/{kit_id}", headers=KEY).json()
print(k["status"], k.get("stage", ""))
if k["status"] in ("succeeded", "failed"):
break
if k["status"] == "succeeded":
for p in k["pieces"]:
print(p["name"], p["kind"], p["state"], "9-slice" if p["nine_slice"] else "fixed")
open("ui_kit.zip", "wb").write(requests.get(k["download_url"], headers=KEY).content)
else:
print("failed:", k.get("error"), "| refunded:", k.get("refunded"))
429 too_many_jobs otherwise).