Skip to content

Developer API

Version 1 uses the same models, assets, task recovery and credit accounting as Studio. Keep your key on your server.

Manage API keys

Send Authorization: Bearer YOUR_API_KEY. Website cookies are not accepted as API credentials. An active paid membership and the required key permission are checked on every request.

Read models, upload a reference if needed, request a quote, explicitly acknowledge that quote, then submit with a stable Idempotency-Key. Poll the returned task every two seconds and download through the protected output endpoint.

Only generation submission consumes account credits. Quotes and key creation do not consume credits. Provider failures use the existing refund and reconciliation process. Do not submit a new idempotency key while the original outcome is unknown.

Each key allows 60 authenticated requests per minute. A 429 response includes Retry-After. Keys expire within 1–365 days and can be revoked immediately.

HTTPURLPermissions
GET/api/v1/modelsmodels:read
GET/api/v1/assetsassets:read
GET/api/v1/assets/{id}/downloadassets:read
POST/api/v1/uploadsassets:write
PUT/api/v1/uploads/{id}assets:write
POST/api/v1/uploads/{id}/completeassets:write
POST/api/v1/quotesgenerations:write
POST/api/v1/generationsgenerations:write
GET/api/v1/generations/{kind}/{id}generations:read
GET/api/v1/generations/{kind}/{id}/outputs/{index}generations:read

References use a three-step upload: declare MIME and byte size, PUT the bytes, then complete. JPEG, PNG and WebP up to 10 MB are verified server-side. Use the returned asset ID; external reference URLs and other users’ assets are not accepted.

// Node.js 22+; keep API_KEY on your server.
const base = process.env.API_BASE_URL;
const headers = {
  Authorization: `Bearer ${process.env.API_KEY}`,
  'Content-Type': 'application/json',
};
const modelsResponse = await fetch(base + '/api/v1/models', { headers });
if (!modelsResponse.ok) throw new Error('Read models failed');
const models = await modelsResponse.json();

const draft = {
  "kind": "image",
  "modelPreset": "gpt-image-2",
  "mode": "text-to-image",
  "prompt": "A yellow ceramic mug on a pale blue background",
  "aspectRatio": "1:1",
  "resolution": "1k",
  "outputCount": 1
};
const quoteResponse = await fetch(base + '/api/v1/quotes', {
  method: 'POST', headers, body: JSON.stringify({ draft }),
});
if (!quoteResponse.ok) throw new Error('Quote failed');
const quote = await quoteResponse.json();

// Review quote.quote before submitting. Persist this operation ID first.
const operationId = process.env.OPERATION_ID;
if (!operationId) throw new Error('A stable operation ID is required');
const submitted = await fetch(base + '/api/v1/generations', {
  method: 'POST',
  headers: { ...headers, 'Idempotency-Key': operationId },
  body: JSON.stringify({
    draft: quote.normalizedDraft,
    quoteAcknowledgement: quote.quote,
  }),
});
if (!submitted.ok) throw new Error('Check response before retrying');
const { task } = await submitted.json();
const taskResponse = await fetch(
  base + '/api/v1/generations/' + task.kind + '/' + task.id, { headers },
);
if (!taskResponse.ok) throw new Error('Read task failed');
const detail = await taskResponse.json();
// Poll at two-second intervals until terminal. Download detail.outputs[].downloadUrl
// with the same Authorization header. Never change the ID to retry an unknown outcome.

401: invalid, expired or revoked key. 403: missing permission or paid membership. 409: idempotency conflict or changed quote; request and confirm a new quote. 422: invalid input. 429: rate limit. 503: temporarily unavailable. Read the response error code; never retry a paid submission with a new key automatically.