# GameTorch v2 API — full reference GameTorch generates game-ready sprites, sound effects and animations from text prompts, and organizes them into projects. This document is written for agents and developers. The machine-readable spec is at `/api/openapi.json` and an interactive reference is at `/api/docs`. ## Conventions - **Base URL:** `/api` (development: `http://localhost:8300/api`). - **Auth:** `Authorization: Bearer ` where the token is either a Clerk session JWT (browser) or a `gt2_...` API key (server-to-server). Every endpoint here requires auth unless noted. API keys are scoped: a personal key acts on its owner's personal account; an organization key acts on that organization. A key can additionally be **project-scoped** (see "Key scopes" below), in which case it may only reach its one bound project. - **Errors:** JSON body `{"error": ""}` with a 4xx/5xx status. `401` means missing/invalid credentials, `402` means insufficient credits or a spending/entitlement limit, `403` means the caller lacks permission for the scope, `404` means not found in the caller's scope, `409` means a request id conflict, `429` means rate limited. - **Credits:** 100 credits = $1. Jobs reserve credits up front; settlement charges the actual measured cost and releases the unused hold. A list response that reports `credits_consumed` uses credits (not dollars). - **Pagination:** list endpoints take `before=` (a cursor) and return `next_cursor` plus `total`; pass the cursor back to page. - **Archiving:** archiving hides an item from default lists; unarchiving restores it. Deletes are permanent, except project deletion: deleting a project removes its stored sprites, sounds and animations and hides the project, while retaining only anonymized generation metadata (which models were used), credit/debit usage records and billing records for audit. - **Attribution:** every generation and result (sprite, sound, animation, edits) records who created it: `user_id` (the Clerk user id, or the API-key owner), `source` (`UI` or `API key`), and — for API-key runs — `api_key_id` and `key_name`. Deleting a project anonymizes its generation records (`user_id` becomes `deleted-user`); the credit/debit and billing records are retained. ## Agents: use an API key, not MCP GameTorch is a plain HTTP API. There is deliberately **no MCP server**: an MCP layer sits between your agent and the API and adds latency, state and failure modes without giving the agent anything it can't already do with a key and a URL. It's a worse engineering decision, so we didn't make it. To wire up an agent: 1. Create an API key (`POST /keys`, or the dashboard) and copy it — it is shown only once. 2. Optionally set `max_spend_limit` and `spend_reset_cadence` on the key so it can never exceed a budget. 3. Give the agent this document and `/api/openapi.json`, and have it send `Authorization: Bearer ` on every request. Scale by adding keys, each with its own limit. ### Which tool to reach for - **Human + agent working together** — a person watches, inspects output and steers each step: use the official **`gametorch` CLI** (below). Every public API operation is a command with `--json` output and `--output` downloads, so the human can read and adjust each step. - **Programmatic, repeated, hands-off automation** — no human in the loop (a pipeline, CI job, scheduled task or long-running service): use an **SDK** (Python, TypeScript, Go or Rust). Typed models, retries, rate limiting and pagination are built in, and failures surface as typed errors you can branch on. ## Key scopes Every API key has a `key_scope` chosen at creation; it is fixed for the key's lifetime. Three scopes exist: - **`admin`** — the default. Full access to the owning account/organization, exactly like an admin session: all projects, usage, billing and key management. It carries no `project_id`. - **`project_write`** — bound to one `project_id`. It can read that project and write to it: generate sprites, sounds and animations; add/remove labels, names, metadata and archives. It cannot touch account surfaces (usage, billing, org members, key management) or create/rename/delete projects, and it cannot see any other project. - **`project_read`** — bound to one `project_id`. Read-only: it can list and download that project's content and run read-only animation exports, but no request that mutates anything. It cannot touch account surfaces or any other project. Enforcement is fail-closed: a project-scoped key hitting an account/admin route gets `403`; a request that names a different project (or a resource id that belongs to another project) gets `404`, never a `403` that would reveal the id exists. Project-scoped keys may call the model catalogs (`GET /sprite-models`, `GET /sound-models`, `GET /animation-models`), which carry no account data. `GET /projects` is narrowed to the single bound project. Deleting a project revokes every project-scoped key bound to it. ## Official CLI and SDKs The **`gametorch` CLI** is the recommended tool when a human is in the loop: every public API operation is a command, with `--json` output and `--output` downloads, so the human can inspect and steer each step. Install it from crates.io: ```sh cargo install gametorch-cli export GAMETORCH_API_KEY=gt2_... gametorch project list gametorch sprite generate --project my-game --prompt "a red fox" --mode single --image-model openai/gpt-image-2.5-flare --wait gametorch sprite asset content --output fox.png ``` Or install from GitHub source: `cargo install --git https://github.com/gametorch/gametorch-rs gametorch-cli` (or clone the repo and `cargo install --path cli`). For programmatic, repeated, hands-off automation, use an SDK. All are first-party, MIT-licensed, and share the same design: typed request/response models, client-side rate limiting that mirrors the published limits and concurrency caps, automatic retries with exponential backoff honoring `Retry-After`, cursor pagination helpers, exact decimal money handling (100 credits = $1), status-code-aware errors, and constructors for the `project_read` / `project_write` key scopes. Each reads `GAMETORCH_API_KEY` and `GAMETORCH_BASE_URL` from the environment, so a local deployment is just `GAMETORCH_BASE_URL=http://localhost:8300/api`. | Language | Package | Install | Repository | | --- | --- | --- | --- | | Python (3.11+) | `gametorch` | `pip install gametorch` | https://github.com/gametorch/pygametorch | | TypeScript / JavaScript (Node 20+) | `gametorch` | `npm install gametorch` | https://github.com/gametorch/gametorch-ts | | Go (1.27+) | `github.com/gametorch/gogametorch` | `go get github.com/gametorch/gogametorch` | https://github.com/gametorch/gogametorch | | Rust | `gametorch` | `gametorch = "0.1"` | https://github.com/gametorch/gametorch-rs | The Python SDK offers async (`AsyncClient`) and synchronous (`Client`) variants; the TypeScript SDK has zero runtime dependencies; the Go SDK uses only the standard library; the Rust SDK is async on `reqwest`/`tokio`. The developer hub with examples lives at https://github.com/gametorch. ## Rate limits Requests are rate limited per account (organization and personal contexts are independent). Exceeding a limit returns `429` with a JSON `{"error": ...}` body. Each operation in the OpenAPI document declares a `429` response. - **1 request/second per route:** creation of generations, sounds and animations, and all animation export routes. - **Reads — one token bucket per account:** every GET read (single-item and list reads, usage and usage histogram, and asset/audio/animation content) shares a bucket with a 10,000-request burst refilled at 1,000 requests/second. - **Writes — one token bucket per account:** creation, archive/unarchive and metadata writes for projects, API keys, labels, art styles and saved animations share a bucket with a 100-request burst refilled at 5 requests/second. - **Concurrency:** at most 25 concurrent holds (outstanding reserved-credit jobs) and 5 concurrent animation-frame generations per account. Jobs beyond the hold cap are rejected with `429`; idempotent replays of an existing `request_id` are exempt. ## Model warm-up A run can briefly wait (usually a few seconds) while one of GameTorch's own models starts up after a period of inactivity. Once the model is warm, it responds in about 0.2 seconds per run. While a run is waiting on a cold start, its `warming` field is `true`. ## Catalog ### GET /sprite-models Returns the image-model catalog, the prompt-enhancement text models, the supported modes (`single`, `multiple`), the reservation size and the `reservation_credits` hold. Sprites are delivered as transparent PNGs. ### GET /sound-models Returns the sound-model catalog and supported output formats. ### GET /animation-models Returns the animation models by their public names (`ash`, `birch`, `cedar`), each with a description, supported durations and resolutions. This is the only form of animation model the API uses. ## Projects ### GET /projects Lists the projects in the caller's scope. Response: `{ "projects": [{ "id", "name", "slug", "created_at" }] }`. ### POST /projects Body: `{ "name": "" }`. Creates a project owned by the caller's scope. Response: the created project (`{ "id", "name", "slug", "created_at" }`). ### PATCH /projects/{slug} Body: `{ "name": "" }`. Renames the project; the slug may change. Response: the updated project. ### DELETE /projects/{slug} Deletes the project and its stored sprites, sounds and animations; it is removed from listings and project-scoped access, and the name can be reused. Only anonymized generation metadata (which models were used), credit/debit usage records and billing records are retained for audit. Response `{"ok": true}`. ## Sprites ### POST /projects/{project_id}/generations Starts a sprite generation. Body: ```json { "request_id": "uuid", "prompt": "text", "mode": "single" | "multiple", "image_model": "id from GET /sprite-models", "text_model": "id | null", "quality": "string | null", "resolution": "string | null" } ``` `request_id` makes the call idempotent: reusing it with the same body returns the existing generation; reusing it with a different body returns `409`. `single` returns one sprite; `multiple` returns four different sprites from one 2x2 sheet. Response: `{ "id", "status", "created", "reserved_credits" }`. ### GET /projects/{project_id}/generations Query: `before`, `include_archived`. Lists generations (with their assets). Response: `{ "generations": [...], "next_cursor", "total" }`. ### GET /generations/{id} Query: `include_archived`. Returns one generation and its results. ### GET /projects/{project_id}/sprite-assets Query: `q` (search by name or label). Returns matching sprite assets. ### GET /assets/{id} Returns one sprite asset (metadata, dimensions, labels). `GET /assets/{id}/content` returns the PNG bytes; `GET /assets/{id}/original` returns the uncropped original. ### PATCH /assets/{id} Body: `{ "name": "|null" }`. Renames the asset. ### PUT /assets/{id}/metadata Body: `{ "metadata": { "": "" } }`. Replaces the asset's metadata. ### POST /assets/{id}/archive · POST /assets/{id}/unarchive Hide or restore the asset. ### DELETE /assets/{id} Permanently deletes the asset and its original. ### POST /generations/{id}/archive · POST /generations/{id}/unarchive · DELETE /generations/{id} Archive, restore or delete a whole generation. ## Sounds ### POST /projects/{project_id}/sound-generations Body: `{ "request_id", "prompt", "sound_model", "response_format"? }`. Idempotent on `request_id`. Response: `{ "id", "status", "created", "reserved_credits" }`. ### GET /projects/{project_id}/sound-generations Query: `before`, `include_archived`. Lists sound generations. Response: `{ "sound_generations": [...], "next_cursor", "total" }`. ### GET /sound-generations/{id} Query: `include_archived`. Returns one sound generation and its assets. ### GET /sound-assets/{id}/content Returns the audio bytes. `PATCH /sound-assets/{id}` renames; `PUT /sound-assets/{id}/metadata` replaces metadata; `DELETE /sound-assets/{id}` deletes; archive/unarchive behave like other assets. ## Animations ### POST /projects/{project_id}/animation-runs/estimate Body: `{ "animation_model", "duration"?, "base_asset_id"? }`. Read-only cost estimate. Response: `{ "animation_model", "duration", "resolution", "credits", "usd", "reserved_credits" }`. ### POST /projects/{project_id}/animation-runs Body: `{ "request_id", "prompt", "animation_model", "duration"?, "base_asset_id"? }`. `animation_model` is one of the public names from `GET /animation-models`. Idempotent on `request_id`. Response: `{ "id", "status", "created", "reserved_credits" }`. ### GET /projects/{project_id}/animation-runs Query: `before`, `include_archived`, `base_asset_id` (return only runs whose base image is that sprite asset id). Lists animation runs. Response: `{ "animations": [...], "next_cursor", "total" }`. Every run includes `base_asset_id` (null when generated from scratch) so callers can link back to the base image. ### GET /animation-runs/{id} Returns one animation run (including `base_asset_id`) and its frames. ### GET /animation-runs/{id}/content Returns the animation clip bytes. ### POST /animation-runs/{id}/archive · POST /animation-runs/{id}/unarchive · DELETE /animation-runs/{id} Archive, restore or delete a run. ### POST /projects/{project_id}/animation-runs/{id}/frames Body: `{ "request_id", "fps" }` (fps 1–30). Samples the finished animation run at `fps` into individual transparent PNG frames (this also happens automatically once a run succeeds). Response: `{ "id", "status", "created", "reserved_credits" }`. ### Frames `GET /animation-run-frames/{id}/content` and `GET /animation-runs/{id}/frames/{number}/content` return individual frame PNGs. The `frames/{number}/content` form addresses a frame by its 1-based number, so a range (e.g. frames 11–32) can be fetched without listing frame ids first. ## Animation exports All export endpoints take a frame range `{ "start_frame": , "end_frame": }` and return the packed result (JSON, a file blob, or a zip depending on the format). ### POST /animation-runs/{id}/export-plan Returns the export plan (frame rectangles and metadata) without building a file. ### POST /animation-runs/{id}/export/{format} Builds and returns a specific export. Formats: - `texturepacker` (JSON atlas), `texturepacker.zip` - `aseprite` (`.aseprite` file) - `godot` (`.tres` + PNG), `godot.zip` - `grid` (single grid-strip PNG) - `gamemaker` (strip PNG) - `sequence.zip` (numbered PNG sequence) ## Saved animations A saved animation is a named frame range taken from an animation run, reusable as a preset. ### GET /projects/{project_id}/saved-animations Query: `before`, `include_archived`. Lists saved animations. Response: `{ "saved_animations": [...], "next_cursor", "total" }`. ### POST /projects/{project_id}/saved-animations Body: `{ "generation_id", "start_frame", "end_frame", "name"? }`. Saves a range of an animation run. Response: the saved animation. ### GET /saved-animations/{id} Returns one saved animation. ### PATCH /saved-animations/{id} Body: `{ "name"? }`. Renames. ### PUT /saved-animations/{id}/metadata Body: `{ "metadata": { "": "" } }`. Replaces metadata. ### POST /saved-animations/{id}/archive · POST /saved-animations/{id}/unarchive · DELETE /saved-animations/{id} Archive, restore or delete. ## Labels ### GET /projects/{project_id}/labels Lists the project's labels. ### POST /projects/{project_id}/labels Body: `{ "name", "color"? }`. Creates a label. A project may have at most **10,000 labels**; exceeding that returns `400`. ### PATCH /labels/{label_id} Body: `{ "name"?, "color"? }`. Updates a label. ### DELETE /labels/{label_id} Deletes a label. ### GET /labels/{label_id}/items Lists the sprites/sounds/saved animations tagged with the label. ### POST /labels/{label_id}/thumbnail Body: `{ "asset_id": "|null" }`. Sets (or clears) the label's cover thumbnail. ### POST /assets/{asset_id}/labels Body: `{ "name" }`. Adds a label to a sprite asset. Sound and saved-animation variants are `POST /sound-assets/{asset_id}/labels` and `POST /saved-animations/{id}/labels`. ### DELETE /assets/{asset_id}/labels?name={name} Removes a label association (same pattern for sound assets and saved animations). ### DELETE /assets/{asset_id}/label-suggestions?name={name} Dismisses a suggested label for a sprite asset (same for `/sound-assets/{asset_id}/label-suggestions`). ## Art styles ### GET /projects/{project_id}/art-styles Lists the project's reusable art styles. ### POST /projects/{project_id}/art-styles Body: `{ "name" }`. Creates an art style. ### POST /projects/{project_id}/art-styles/generate Returns a fresh suggested art style for the user to review. ### DELETE /art-styles/{art_style_id} Deletes an art style. ## Usage ### GET /usage Query: `before` (cursor), `split` (`user` to break dashboard usage out per member). Returns the credit balance (admins), a per-source summary and the operation log. Response: `{ "balance_credits", "reserved_credits", "summary", "records", "next_cursor", "total" }`. ### GET /usage/histogram Query: `range` (`24h`|`7d`|`30d`|`365d`), `source`, `split` (`user`). Returns dense time buckets split by spend source. Response: `{ "range", "width_seconds", "sources", "buckets" }`. ## API keys ### POST /users/me Ensures a mirrored user row exists for the caller. ### GET /keys Lists keys in the caller's scope. ### POST /keys Body: `{ "name"?, "expires_at"?, "max_spend_limit"?, "spend_reset_cadence"?, "key_scope"?, "project_id"? }` (`spend_reset_cadence` is `daily`|`weekly`|`monthly`|`never`; `key_scope` is `admin`|`project_write`| `project_read`). `project_id` is required for the two project scopes and must be a project in the caller's scope; it must be omitted (or null) for `admin`. Returns the key once, including `key_full`, `key_scope` and `project_id`. ### PATCH /keys/{id} Body: the same fields as create except the scope and project (both fixed at creation). Updates a key. ### DELETE /keys/{id} Revokes a key. ## Support Questions, bugs, abuse reports or billing issues: support@gametorch.app.