Skip to API documentation

Build with Snowpixel.

Turn a prompt into images, audio or video. Submit a job, poll its status, and use the output URLs in your own application.

API availability: Key restrictions, REST job listing, REST usage reports, cancellation and completion webhooks are not yet available. See current availability before integrating.

API base URLhttps://api.snowpixel.app
On this page

Your first generation

Start with one image request. You’ll need a Snowpixel account, an API key and an available credit.

1. Create a key

Sign in to API Keys and create a key. Copy the complete key when it appears: you can’t reveal it again. Add credits from Usage & Billing if needed.

2. Set your environment

Run these examples in a terminal or backend environment. Replace the placeholder with your key; keep it out of browser code and source control.

macOS / Linux
export SNOWPIXEL_API_KEY="sk_live_REPLACE_ME"
Windows PowerShell
$env:SNOWPIXEL_API_KEY="sk_live_REPLACE_ME"

3. Download and run

Choose Python 3.12+ or Node.js 22+. Neither example requires additional packages. Run the command from the folder where you saved the file.

Python
Download
python3 generate.py --prompt "A snowy pixel cabin"

On Windows, use python instead of python3.

JavaScript · Node.js
Download
node generate.mjs --prompt "A snowy pixel cabin"

4. Keep the job ID

The script prints the ID immediately, then waits for completion and prints available output URLs. Example output below is illustrative; your ID and URLs will differ.

Example terminal output
Job: AbCdEfGhIjKlMnOpQr01
https://cdn.example/generated.png

Polling stops after ten minutes by default. A timeout doesn’t cancel the job. Resume with its ID to avoid another submission and charge:

Resume an existing job
python3 generate.py --job-id AbCdEfGhIjKlMnOpQr01
# Or, with Node.js:
node generate.mjs --job-id AbCdEfGhIjKlMnOpQr01

Examples you can build on

These are the complete downloadable scripts. Both send a single POST, poll every five seconds, and exit with an error on a failed job or request. They do not retry submissions automatically.

Python · view complete source
generate.py
Download
"""Generate media with Snowpixel. Python 3.12+, no dependencies.

Set SNOWPIXEL_API_KEY; optionally set SNOWPIXEL_BASE_URL.
"""
import argparse
import json
import math
import os
import re
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
import uuid

DEFAULT_BASE_URL = "https://api.snowpixel.app"


class ApiError(Exception):
    pass


class NoRedirects(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None  # Never forward the key to a redirected destination.


def request_json(base, key, path, method="GET", body=None, timeout=30, idempotency_key=None):
    # Preserve any base URL path prefix when appending the relative /v1 polling path.
    request = urllib.request.Request(
        base + path, method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json",
                 "User-Agent": "Snowpixel-Example/1.0 (Python)",
                 **({'Idempotency-Key': idempotency_key} if idempotency_key is not None else {})},
    )
    try:
        response = urllib.request.build_opener(NoRedirects).open(request, timeout=timeout)
    except urllib.error.HTTPError as error:
        response = error
    except (OSError, ValueError) as error:
        raise ApiError("Request failed or timed out. No automatic retry was made.") from error
    with response:
        status = response.code
        try:
            data = json.load(response)
        except (ValueError, UnicodeError) as error:
            raise ApiError(f"HTTP {status}: expected a JSON response.") from error
    if not isinstance(data, dict):
        raise ApiError("Expected a JSON object.")
    if status != (202 if method == "POST" else 200):
        detail = data.get("error", {})
        code = detail.get("code", "unknown_error") if isinstance(detail, dict) else "unknown_error"
        raise ApiError(f"HTTP {status}: {code}")
    return data


def positive_number(value):
    number = float(value)
    if not math.isfinite(number) or number <= 0:
        raise argparse.ArgumentTypeError("must be a positive finite number")
    return number


def run():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--model", default="snowpixel-image-v1")
    parser.add_argument("--prompt", default="A snowy pixel cabin")
    parser.add_argument("--job-id", help="Poll an existing job without submitting again")
    parser.add_argument("--idempotency-key", help="Reuse this key and the same request to recover an interrupted submission")
    parser.add_argument("--timeout", type=positive_number, default=600, help="Polling limit in seconds")
    parser.add_argument("--poll-interval", type=positive_number, default=5)
    parser.add_argument("--request-timeout", type=positive_number, default=30)
    args = parser.parse_args()
    key = os.environ.get("SNOWPIXEL_API_KEY", "").strip()
    if not key:
        raise ApiError("Set SNOWPIXEL_API_KEY before running this script.")
    base = os.environ.get("SNOWPIXEL_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
    url = urllib.parse.urlsplit(base)
    if (url.scheme not in ("http", "https") or not url.hostname
            or url.username or url.password or url.query or url.fragment
            or (url.scheme == "http" and url.hostname not in ("localhost", "127.0.0.1", "::1"))):
        raise ApiError("Use HTTPS, or HTTP on localhost for offline tests.")
    job_id = args.job_id
    if job_id is not None and not re.fullmatch(r"[A-Za-z0-9]{20}", job_id):
        raise ApiError("Job ID must contain exactly 20 ASCII letters or digits.")
    if job_id is None:
        submission_key = args.idempotency_key if args.idempotency_key is not None else str(uuid.uuid4())
        if not re.fullmatch(r'[\x21-\x7e]{1,255}', submission_key):
            raise ApiError('Idempotency key must contain 1 to 255 visible ASCII characters without whitespace.')
        print(f'Idempotency-Key: {submission_key}; retry with --idempotency-key {submission_key} and the same request.', file=sys.stderr, flush=True)
        accepted = request_json(base, key, "/v1/generations", "POST", {
            "model": args.model, "prompt": args.prompt,
        }, timeout=args.request_timeout, idempotency_key=submission_key)
        job_id = accepted.get("id")
        if not isinstance(job_id, str) or not re.fullmatch(r"[A-Za-z0-9]{20}", job_id):
            raise ApiError("The API returned an invalid job ID.")
        if accepted.get("pollUrl") != f"/v1/generations/{job_id}":
            raise ApiError("The API returned an unexpected polling path.")
    print(f"Job: {job_id}", flush=True)
    deadline = time.monotonic() + args.timeout
    recovery_announced = False
    while True:
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            raise ApiError(f"Polling timed out. Resume with --job-id {job_id}; do not resubmit.")
        job = request_json(base, key, f"/v1/generations/{job_id}",
                           timeout=min(args.request_timeout, remaining))
        if job.get("status") == "cancelled":
            raise ApiError(f"Job {job_id} was cancelled; inspect the job for retained outputs and billing.")
        if job.get("status") == "failed":
            raise ApiError(f"Job {job_id} failed: {(job.get('error') or {}).get('code', 'unknown_error')}")
        if job.get("status") in ("completed", "partial_failed"):
            outputs = job.get("outputs")
            if not isinstance(outputs, list):
                raise ApiError("The API returned invalid outputs.")
            urls = [item.get("url") or item.get("originalUrl") for item in outputs if isinstance(item, dict)]
            urls = [url for url in urls if isinstance(url, str) and url]
            for output_url in urls:
                print(output_url)
            if job.get("status") == "partial_failed":
                raise ApiError(f"Job {job_id} partially failed: partial_generation_failed")
            if not urls:
                print("Completed with no output URLs.")
            return
        if job.get("status") != "pending":
            raise ApiError("The API returned an unknown job status.")
        if job.get('recoveryStatus') == 'outcome_unknown' and not recovery_announced:
            print('Checking generation status; continuing to poll the same job.', file=sys.stderr)
            recovery_announced = True
        time.sleep(min(args.poll_interval, max(0, deadline - time.monotonic())))


if __name__ == "__main__":
    try:
        run()
    except (ApiError, OSError, ValueError) as error:
        message = str(error).replace(os.environ.get("SNOWPIXEL_API_KEY", "").strip() or "\0", "[redacted]")
        print(message, file=sys.stderr)
        sys.exit(1)
JavaScript · view complete source
generate.mjs
Download
/** Node.js 22+. Set SNOWPIXEL_API_KEY before running this script. */
import { parseArgs } from 'node:util';
import { randomUUID } from 'node:crypto';
import { setTimeout as sleep } from 'node:timers/promises';

const DEFAULT_BASE_URL = 'https://api.snowpixel.app';

async function requestJson(base, key, path, method = 'GET', body, timeout = 30, idempotencyKey) {
  let response;
  let data;
  try {
    // Preserve any base URL path prefix when appending the relative /v1 polling path.
    response = await fetch(base + path, {
      method,
      headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json',
        'User-Agent': 'Snowpixel-Example/1.0 (Node.js)',
        ...(idempotencyKey === undefined ? {} : { 'Idempotency-Key': idempotencyKey }) },
      body: body === undefined ? undefined : JSON.stringify(body),
      signal: AbortSignal.timeout(Math.max(1, Math.ceil(timeout * 1000))),
      redirect: 'error',
    });
    data = await response.json();
  } catch (error) {
    if (error instanceof SyntaxError) throw new Error(`HTTP ${response.status}: expected a JSON response.`);
    throw new Error('Request failed or timed out. No automatic retry was made.');
  }
  if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Expected a JSON object.');
  if (response.status !== (method === 'POST' ? 202 : 200)) {
    throw new Error(`HTTP ${response.status}: ${data.error?.code ?? 'unknown_error'}`);
  }
  return data;
}

async function run() {
  const { values } = parseArgs({ options: {
    model: { type: 'string', default: 'snowpixel-image-v1' },
    prompt: { type: 'string', default: 'A snowy pixel cabin' },
    'job-id': { type: 'string' },
    'idempotency-key': { type: 'string' },
    timeout: { type: 'string', default: '600' },
    'poll-interval': { type: 'string', default: '5' },
    'request-timeout': { type: 'string', default: '30' },
    help: { type: 'boolean', default: false },
  } });
  if (values.help) {
    console.log('node generate.mjs [--model MODEL] [--prompt PROMPT] [--job-id ID] [--idempotency-key KEY]\n' +
      '[--timeout 600] [--poll-interval 5] [--request-timeout 30] (seconds)\n' +
      'Set SNOWPIXEL_API_KEY; optionally set SNOWPIXEL_BASE_URL.');
    return;
  }
  const key = process.env.SNOWPIXEL_API_KEY?.trim();
  if (!key) throw new Error('Set SNOWPIXEL_API_KEY before running this script.');
  const base = (process.env.SNOWPIXEL_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
  const url = new URL(base);
  if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash ||
      (url.protocol === 'http:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))) {
    throw new Error('Use HTTPS, or HTTP on localhost for offline tests.');
  }
  const timeout = Number(values.timeout);
  const interval = Number(values['poll-interval']);
  const requestTimeout = Number(values['request-timeout']);
  if (![timeout, interval, requestTimeout].every(value => Number.isFinite(value) && value > 0)) {
    throw new Error('Timeouts and polling interval must be positive finite numbers.');
  }
  let jobId = values['job-id'];
  if (jobId !== undefined && !/^[A-Za-z0-9]{20}$/.test(jobId)) throw new Error('Job ID must contain exactly 20 ASCII letters or digits.');
  if (jobId === undefined) {
    const submissionKey = values['idempotency-key'] ?? randomUUID();
    if (!submissionKey.length || submissionKey.length > 255 || /[^\x21-\x7e]/.test(submissionKey)) throw new Error('Idempotency key must contain 1 to 255 visible ASCII characters without whitespace.');
    console.error(`Idempotency-Key: ${submissionKey}; retry with --idempotency-key ${submissionKey} and the same request.`);
    const accepted = await requestJson(base, key, '/v1/generations', 'POST', {
      model: values.model, prompt: values.prompt,
    }, requestTimeout, submissionKey);
    jobId = accepted.id;
    if (typeof jobId !== 'string' || !/^[A-Za-z0-9]{20}$/.test(jobId)) throw new Error('The API returned an invalid job ID.');
    if (accepted.pollUrl !== `/v1/generations/${jobId}`) throw new Error('The API returned an unexpected polling path.');
  }
  console.log(`Job: ${jobId}`);
  const deadline = performance.now() + timeout * 1000;
  let recoveryAnnounced = false;
  while (true) {
    const remaining = (deadline - performance.now()) / 1000;
    if (remaining <= 0) throw new Error(`Polling timed out. Resume with --job-id ${jobId}; do not resubmit.`);
    const job = await requestJson(base, key, `/v1/generations/${jobId}`, 'GET', undefined, Math.min(requestTimeout, remaining));
    if (job.status === 'cancelled') throw new Error(`Job ${jobId} was cancelled; inspect the job for retained outputs and billing.`);
    if (job.status === 'failed') throw new Error(`Job ${jobId} failed: ${job.error?.code ?? 'unknown_error'}`);
    if (job.status === 'completed' || job.status === 'partial_failed') {
      if (!Array.isArray(job.outputs)) throw new Error('The API returned invalid outputs.');
      const urls = job.outputs.map(item => item?.url || item?.originalUrl).filter(url => typeof url === 'string' && url);
      for (const outputUrl of urls) console.log(outputUrl);
      if (job.status === 'partial_failed') throw new Error(`Job ${jobId} partially failed: partial_generation_failed`);
      if (!urls.length) console.log('Completed with no output URLs.');
      return;
    }
    if (job.status !== 'pending') throw new Error('The API returned an unknown job status.');
    if (job.recoveryStatus === 'outcome_unknown' && !recoveryAnnounced) {
      console.error('Checking generation status; continuing to poll the same job.');
      recoveryAnnounced = true;
    }
    await sleep(Math.min(interval * 1000, Math.max(0, deadline - performance.now())));
  }
}

run().catch(error => {
  console.error(String(error.message).replaceAll(process.env.SNOWPIXEL_API_KEY?.trim() || '\0', '[redacted]'));
  process.exitCode = 1;
});

Use --model and --prompt to change the request. --timeout sets the polling limit; --poll-interval sets the delay between checks; --request-timeout sets the per-request limit (30 seconds by default). All durations are in seconds. Use --help to see the options.

The scripts use the API base URL above. For local testing, set SNOWPIXEL_BASE_URL to your local server’s API base, including its path prefix.

Models & inputs

The existing presets use the same create-and-poll flow and cost one credit each. Discover current contracts and prices with authenticated GET /v1/models. Output metadata varies by model; consume the returned outputs without assuming a fixed count.

Supported models and example prompts
ModelExample prompt
snowpixel-image-v1
preset · 8 outputs · 1 credits
snowpixel-image-v1
{
  "model": "snowpixel-image-v1",
  "prompt": "A snowy cabin",
  "params": {}
}
snowpixel-audio-v1
preset · 5 outputs · 1 credits
snowpixel-audio-v1
{
  "model": "snowpixel-audio-v1",
  "prompt": "A snowy cabin",
  "params": {}
}
snowpixel-video-v1
preset · 1 outputs · 1 credits
snowpixel-video-v1
{
  "model": "snowpixel-video-v1",
  "prompt": "A snowy cabin",
  "params": {}
}

NFC text with LF line endings and trimmed edge whitespace. Requiredness and code-point limits belong to the selected model revision.

Scroll the table horizontally to read all model details.

Settings declared by the selected model revision; defaults and conditions are applied by the server. Music generation is not exposed by this API.

Direct models use the exact normalized prompt without expansion. The development image fixture produces one image, accepts width 512 or 1024, and fixes height at 1024. Fixture IDs and prices are not production offers; current availability comes from discovery.

Download generated model request examples

API keys belong on your server

Send the full key on every create and polling request. A Firebase login token is not an API key.

Authentication header
Authorization: Bearer sk_live_REPLACE_ME

Keys belong to your Snowpixel account. Any active key on that account can poll its API Generation Jobs. A key from another account receives 404 not_found for those jobs.

Create and revoke keys in API Keys. Only creation reveals the full key; listing and revocation return safe metadata. Revoking a key prevents future API requests with it, including polling. To keep polling an existing job, use another active key from the same account.

Operation scopes, expiry dates and monthly credit caps are not enforced. Keep keys private and revoke any you no longer need.

Store keys as environment variables or server-side secrets. Do not place them in client-side JavaScript, browser storage, logs or analytics.

Create a job

POST/v1/generations

Send JSON with Content-Type: application/json. An Idempotency-Key header is optional but strongly recommended for safe retries: use 1–255 visible ASCII characters without whitespace (a UUID is recommended). Save it with the request before sending. The same account, key, and normalized request return the original job; changed requests return 409 idempotency_conflict. Keys never expire. Use a new key for an intentional new generation. Submissions without a key do not receive retry deduplication.

To prevent submission at a changed price, include quote with the displayed pricingVersion and whole-credit amount from discovery. A valid mismatch returns 409 quote_changed and a refreshed error.quote before debit. Omitting the quote remains supported.

Create request fields
FieldTypeDetails
quoteOptional GenerationQuote
revisionOptional string Optional immutable direct model revision. Presets reject explicit revisions with unsupported_revision.
modelRequired string Select an enabled ID from GET /v1/models. Development fixtures are gated and have no commercial release price.
promptOptional string NFC text with LF line endings and trimmed edge whitespace. Requiredness and code-point limits belong to the selected model revision.
paramsOptional object Settings declared by the selected model revision; defaults and conditions are applied by the server.
inputsOptional object Named files and ordered reference arrays using ready asset IDs.

Scroll horizontally to read all fields.

Request body
{
  "model": "snowpixel-image-v1",
  "prompt": "A snowy pixel cabin"
}

A successful response is 202 Accepted. It means the submission was accepted; it does not mean generation has completed.

202 · Accepted
{
  "id": "AbCdEfGhIjKlMnOpQr01",
  "model": "snowpixel-image-v1",
  "status": "pending",
  "pollUrl": "/v1/generations/AbCdEfGhIjKlMnOpQr01"
}
Create response fields
FieldTypeDetails
idRequired string Unique 20-character ASCII alphanumeric API Generation Job ID.
modelRequired string
statusRequired string pending
pollUrlRequired string Append this path to the API base URL, preserving any configured path prefix. Poll with the same Bearer authentication.

Scroll horizontally to read all fields.

Append pollUrl to the base URL. The public polling URL is https://api.snowpixel.app/v1/generations/{id}. If a custom base URL includes a path prefix such as /api, preserve that prefix when appending pollUrl.

Poll a job

GET/v1/generations/{id}

Use the 20-character ASCII alphanumeric ID returned by the create response. A malformed ID returns 400; a valid ID that isn’t in your account returns 404. Successful polling returns 200 with one of these states:

pending
Keep polling while children are running. Outputs are empty and completedAt is null.
completed
All children succeeded. Read the available outputs. URLs and timestamps can be null.
partial_failed
All children settled, with some failures. Stop polling, keep the available outputs, and inspect error.code.
failed
Stop polling and inspect error.code.

For jobs accepted under the current settlement policy, failed or incomplete generations receive a full refund of the generation's credit charge once settlement completes; usable partial outputs are retained. Reconciliation can be delayed, so a terminal status does not mean the refund is already reflected in your balance. Historical jobs retain their previous settlement policy and may have different refund outcomes.

Polling response fields
FieldTypeDetails
modelLabelOptional string | null Label recorded at admission; retained after retirement. Unknown historical labels remain null.
revisionOptional string | null
contractVersionOptional integer | null
creditsOptional object Persisted accounting facts. reserved is the original debit; charged is null while unsettled and never causes a second debit. refunded is confirmed returned credits. Missing historical amounts remain null; failed status alone does not imply a refund.
idRequired string Unique 20-character ASCII alphanumeric API Generation Job ID.
modelRequired string
promptRequired string The trimmed prompt stored for this job.
statusRequired string pendingcompletedpartial_failedfailedcancelled
recoveryStatusOptional string Optional detail on pending jobs while provider acceptance or execution is being reconciled. Keep polling this job; do not submit a replacement.outcome_unknown
createdAtRequired string | null ISO 8601 creation time, or null when unavailable.
completedAtRequired string | null ISO 8601 completion or failure time, or null. Pending jobs always return null.
outputsRequired array Committed outputs, including outputs retained by cancellation. Outputs uploaded after cancellation are fenced and cleaned up.
errorOptional object | null Present on failed and partial_failed jobs. generation_unresolved means the 24-hour recovery deadline elapsed with no usable outputs and the reserved credit was refunded once. generation_failed and partial_generation_failed do not imply a refund. partial_failed preserves usable outputs. Legacy failed jobs can retain partial assets internally without returning them here.
apiKeyIdOptional string | null
originOptional string apiplayground
lifecycleOptional string unknownqueuedrunningpublishingcompletedpartial_failedfailedcancelled
cancellationOptional object
queuedAtOptional string | null
startedAtOptional string | null
publishingAtOptional string | null
updatedAtOptional string | null
cancelRequestedAtOptional string | null

Scroll horizontally to read all fields.

200 · pending response
pending response
{
  "id": "AbCdEfGhIjKlMnOpQr01",
  "model": "snowpixel-image-v1",
  "prompt": "A snowy pixel cabin",
  "status": "pending",
  "createdAt": "2026-06-25T12:00:00+00:00",
  "completedAt": null,
  "outputs": []
}
200 · completed response
completed response
{
  "id": "AbCdEfGhIjKlMnOpQr01",
  "model": "snowpixel-image-v1",
  "prompt": "A snowy pixel cabin",
  "status": "completed",
  "createdAt": "2026-06-25T12:00:00+00:00",
  "completedAt": "2026-06-25T12:03:00+00:00",
  "outputs": [
    {
      "url": "https://cdn.example/generated.png",
      "originalUrl": "https://cdn.example/original.png",
      "metadata": {
        "width": 1024,
        "height": 1024
      }
    }
  ]
}
200 · partial_failed response
partial_failed response
{
  "id": "AbCdEfGhIjKlMnOpQr01",
  "model": "snowpixel-audio-v1",
  "prompt": "A snowy pixel cabin",
  "status": "partial_failed",
  "createdAt": "2026-06-25T12:00:00+00:00",
  "completedAt": "2026-06-25T12:03:00+00:00",
  "outputs": [
    {
      "url": "https://cdn.example/generated.wav",
      "originalUrl": null,
      "metadata": {}
    }
  ],
  "error": {
    "code": "partial_generation_failed"
  }
}
200 · failed response
failed response
{
  "id": "AbCdEfGhIjKlMnOpQr01",
  "model": "snowpixel-image-v1",
  "prompt": "A snowy pixel cabin",
  "status": "failed",
  "createdAt": "2026-06-25T12:00:00+00:00",
  "completedAt": "2026-06-25T12:00:01+00:00",
  "outputs": [],
  "error": {
    "code": "dispatch_failed"
  }
}

Output fields

Output fields
FieldTypeDetails
idOptional string Stable within the job, including historical reads.
kindOptional string imageaudiovideo3dunknown
contentTypeOptional string
widthOptional number
heightOptional number
durationOptional number
modelOptional string | null
revisionOptional string | null
previewsOptional array
urlRequired string | null Output asset URL, or null when unavailable.
originalUrlRequired string | null Original asset URL, or null when unavailable.
metadataRequired object Model-dependent output details. Fields may vary or be absent.

Scroll horizontally to read all fields.

Some downstream worker failures may leave a job pending. Use a bounded polling duration, retain the ID, and contact support if a job remains pending. The timeout in these scripts is a client limit, not an expected generation time.

Availability & unreleased features

Available now: create and revoke API keys, discover models, submit a generation and poll its returned ID. Keep the ID and reuse the same Idempotency-Key after an uncertain submission.

GET /v1/balance returns the authenticated account's available credits. Additional accounting fields may contain defaults; they do not establish current reserved-credit totals or enforced concurrency limits.

Not yet available

  • API-key operation scopes, expiry dates and monthly credit caps. These restrictions are not enforced.
  • REST job listing with GET /v1/generations and usage reports with GET /v1/usage. The signed-in app's Requests and Usage & Billing screens use separate endpoints.
  • Job cancellation with POST /v1/generations/{id}/cancel.
  • Completion webhooks, signing-secret management, delivery history and manual retries, including /v1/webhook-deliveries.

Poll individual jobs for completion. There are no webhook settings to configure in the current app.

Development contract downloads

The OpenAPI files describe the development contract and include the unreleased features listed above. They are not a list of deployed capabilities. SDK source packages also contain unreleased helpers; registry publication is a separate step.

Errors & recovery

API errors use an error object with a stable code and a message. A failed job returned by GET is still HTTP 200 and has a separate error.code field.

Example · 401 Unauthorized
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key."
  }
}
HTTP status, error code, message and recovery
HTTPCode & messageWhat to do
400invalid_requestInvalid request.Send model, prompt, declared params and optional revision/quote. Check the HTTP method and job ID.
400unsupported_modelUnsupported model.Select an enabled model from GET /v1/models.
400invalid_promptInvalid prompt.Use 2–300 characters after trimming and the supported characters listed above.
400unsupported_revisionUnsupported model revision.Use a supported direct revision or omit it for the current revision. Presets do not accept explicit revisions.
400idempotency_key_requiredProvide an Idempotency-Key header.Add an Idempotency-Key header and retain it for retries.
400invalid_idempotency_keyIdempotency-Key must contain 1 to 255 visible ASCII characters without whitespace.Use 1–255 visible ASCII characters without whitespace; a UUID is recommended.
401invalid_api_keyInvalid API key.Check that the complete key is in the Authorization Bearer header.
401revoked_api_keyAPI key has been revoked.Create a new key in your account and update your environment variable.
402insufficient_creditsInsufficient credits.Add credits to the account that owns the API key before submitting again.
404not_foundResource not found.Check the 20-character job ID and use a key belonging to the account that created it.
409quote_changedReview the current generation price.Review error.quote and deliberately resubmit with the updated price only if you accept it. Your inputs were not charged.
409model_unavailableThis model is unavailable.Refresh GET /v1/models and select an available entry. Accepted jobs retain their original configuration.
409idempotency_conflictThis Idempotency-Key was already used with a different request.Reuse the original body to recover the job, or use a new key for an intentional new generation.
500internal_errorGeneration submission could not be confirmed.Submission could not be confirmed. Retry the same Idempotency-Key and request body to recover acceptance without another charge.
503generation_busyGeneration is busy. Please retry.Concurrent activity prevented credit reservation. This request created no job and spent no credit. Retry after a short delay.
503generation_pausedGeneration is temporarily paused while we prepare for launch.
503submission_unavailableGeneration submission is temporarily unavailable. Retry with the same key and request.New submissions are temporarily unavailable. Retry later with the same key and request body.

Scroll the table horizontally to read recovery steps.

Unknown routes and unsupported methods return 400 invalid_request. Valid routes authenticate before validating the request body or looking up a job.

If a submission times out or the connection drops, retry with the same Idempotency-Key and request body. Matching retries return the original 202 acceptance even if the job has since completed or failed. If you have its ID, poll for current status. Never use a new key just because a response was lost.

Download development OpenAPI (includes unreleased features)OpenAPI 3.0.3