Skip to content

Qwen-ImageAPI reference

Generate and edit images with the Qwen-Image models from code: a three-step quick start, every field, per-image credits, webhooks and idempotency.

BetaEndpointPOST /v1/images/generationsOpenAPIllms.txt
Request API access

Developer API access is by application.

The image API is in beta, and its fields may change. The studio is not affected.

Quick startin three calls

Set a key, send the job with a price cap, then read the result.

  1. 01

    Set your key

    Developer API access is by application: request it in the studio under API keys. A key is shown once, so keep it in an environment variable.

    Shell
    export API_KEY="sk_test_…"
  2. 02

    Send the job

    POST the image with an Idempotency-Key and a max_credits cap. Edit models also take image_url. You get 202 and a queued job, and the credits are held.

    POST /images/generations
    curl https://api.pershot.app/v1/images/generations \
      -H "Authorization: Bearer $API_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "qwen-image-2512",
        "prompt": "A ceramic mug on a walnut table, soft window light, product photo",
        "size": "1K",
        "aspect_ratio": "1:1",
        "max_credits": 2
      }'
    You should see
    {
      "id": "job_01J9ZK4T8W",
      "object": "job",
      "kind": "image",
      "status": "queued",
      "money_status": "reserved",
      "queue_position": 2,
      "requested_model": "qwen-image-2512",
      "credits_quoted": 2,
      "credits_charged": null
    }
  3. 03

    Read the result

    GET the job until its status is succeeded. outputs[].url is signed and expires after 24 hours, so download what you keep.

    GET /jobs/{id}
    curl https://api.pershot.app/v1/jobs/job_01J9ZK4T8W \
      -H "Authorization: Bearer $API_KEY"

Parametersand limits

Read from the OpenAPI document, which is generated from the same schemas the API validates against. Unknown fields are refused.

Qwen-Image request fields
FieldTypeAllowedNotes
modelRequiredstringqwen-image-2512-lightning · qwen-image-2512 · qwen-image-edit-2511-lightning · qwen-image-edit-2511The model id.
promptRequiredstring2–2,000 charactersWhat to render: the subject, the camera and the light.
sizeDefault "1K"string1K · 2K1K for drafts and first frames, 2K for detail.
aspect_ratioDefault "1:1"string16:9 · 9:16 · 1:1Frame shape.
image_urlOptionalstringhttps URLFirst frame for video; the source image for the edit models. A public https URL or an upload URL.
seedOptionalinteger0–2,147,483,647Repeat a result with the same inputs.
quote_idOptionalstringqt_…Run at a quote from POST /v1/quotes, valid for 10 minutes.
max_creditsOptionalnumber> 0The most this job may cost. Above it the job is refused, never charged.
webhook_urlOptionalstringhttps URLAn https URL that receives the job's events.
end_user_idOptionalstring1–128 charactersFor approved developer accounts (access is by application): your own user id for this request, shown in usage.
project_idOptionalstringprj_…
shot_idOptionalstringsht_…
routing_preferenceOptionalstringstability · speed

Send exactly one of quote_id or max_credits. This endpoint refuses model "auto": for video, call POST /v1/auto/plan first, then send the model it picks with max_credits.

Models
qwen-image-2512-lightningGenerate · Qwen-Image-2512 · Lightning 4-stepqwen-image-edit-2511-lightningEdit · Qwen-Image-Edit-2511 · Lightningqwen-image-2512Generate · Qwen-Image-2512qwen-image-edit-2511Edit · Qwen-Image-Edit-2511
Rate and concurrency
60 requests a minute per key, and 3 jobs at a time per account by default. More keys do not add concurrency.
For AI agents
Plain text for AI agents: inputs, legal values, every price and a request exampleqwen-image-2512-lightningqwen-image-2512qwen-image-edit-2511-lightningqwen-image-edit-2511

Limits

Read from the same table the API checks against. Each row says who checks the limit and what happens past it.

  • EnforcedWe check it and answer 400 before anything is quoted or held.
  • SupplierThe supplier checks it. A job it refuses fails, and nothing is charged.
  • RecommendedNobody checks it. Staying inside it gives better results.
Limits for qwen-image-2512-lightning, qwen-image-2512
LimitValueUnitWho checksPast the limit
Request
Prompt lengthprompt2 to 2,000charactersEnforced400 invalid_request on prompt, before any quote or hold.
Image sizesize1K, 2KDefault 1K–Enforced400 invalid_request on size.
Aspect ratioaspect_ratio16:9, 9:16, 1:1Default 1:1–Enforced400 invalid_request on aspect_ratio.
Images you send
Source imageimage_urlNone–EnforcedEdit models need image_url and generate models refuse it: 400 invalid_request on image_url.
Output
Output pixels1K16:91344×7689:16768×13441:11024×10242K16:92048×11529:161152×20481:12048×2048pxEnforcedWe ask for exactly these sizes.
Output side256 to 2,048px per sideSupplierEvery size above fits. The supplier refuses larger ones.
Output formatJPEG–EnforcedEvery image is a JPEG.
Links
Output link24hoursEnforcedAfter that the link answers 403. GET the job again for a fresh link. The file itself is kept for 12 months (the job's outputs_expire_at gives the date), longer if the result is saved to Assets or used in a project.
Limits for qwen-image-edit-2511-lightning, qwen-image-edit-2511
LimitValueUnitWho checksPast the limit
Request
Prompt lengthprompt2 to 2,000charactersEnforced400 invalid_request on prompt, before any quote or hold.
Image sizesize1K, 2KDefault 1K–Enforced400 invalid_request on size.
Aspect ratioaspect_ratio16:9, 9:16, 1:1Default 1:1–Enforced400 invalid_request on aspect_ratio.
Images you send
Source imageimage_urlExactly 1–EnforcedEdit models need image_url and generate models refuse it: 400 invalid_request on image_url.
Upload sizefileup to 10MBEnforcedPOST /v1/uploads answers 400 invalid_request on file.
Upload formatsfileJPEG, PNG, WebP–EnforcedRead from the file itself, not its name. Anything else: 400 invalid_request on file.
Output
Output pixels1K16:91344×7689:16768×13441:11024×10242K16:92048×11529:161152×20481:12048×2048pxEnforcedWe ask for exactly these sizes.
Output side512 to 2,048px per sideSupplierEvery size above fits. The supplier refuses larger ones.
Output formatJPEG–EnforcedEvery image is a JPEG.
Links
Upload link7daysEnforcedAfter that the link answers 403. Upload the file again.
Output link24hoursEnforcedAfter that the link answers 403. GET the job again for a fresh link. The file itself is kept for 12 months (the job's outputs_expire_at gives the date), longer if the result is saved to Assets or used in a project.

We never fetch images from other hosts, so for those links the supplier checks the size, and a job it refuses is not charged. The same limits come back as JSON: GET /v1/models returns a limits object for every model.

Pricingworked out in the open

One price per image, by model and size, in credits (US$1 = 100 credits). Generating and editing are priced separately.

Qwen-Image prices
Model1K2K
qwen-image-2512-lightningGenerate1 cr≈ $0.01≈ 1,000 images per $103 cr≈ $0.03≈ 333 images per $10
qwen-image-edit-2511-lightningEdit2 cr≈ $0.02≈ 500 images per $104 cr≈ $0.04≈ 250 images per $10
qwen-image-2512Generate2 cr≈ $0.02≈ 500 images per $106 cr≈ $0.06≈ 166 images per $10
qwen-image-edit-2511Edit3 cr≈ $0.03≈ 333 images per $1010 cr≈ $0.10≈ 100 images per $10

Webhooksand idempotency

Retries never charge twice, and you do not have to poll.

Idempotency
Every generation POST needs an Idempotency-Key (1 to 255 visible ASCII characters). The same key within 24 hours returns the same job with HTTP 200, so a retry after a timeout never creates or charges a second job.
Refused prompts
A prompt refused by the pre-check (422 content_rejected) creates nothing, so the same key can be used again with a new prompt.
Events
job.queuedjob.processingjob.succeededjob.failedjob.cancelled
Signature
X-Signature carries t, the unix time, and v1, an HMAC-SHA256 of t, a dot and the raw body, keyed with your whsec_ secret. Refuse timestamps older than five minutes and dedupe on X-Event-Id.
Delivery
A 2xx within 10 seconds counts as delivered. Failed deliveries retry after 1 min, 5 min, 30 min, 2 h and 6 h. Redirects are not followed.
Webhook body
{
  "id": "evt_01J9ZKB1QX",
  "type": "job.succeeded",
  "created_at": "2026-09-29T14:02:31Z",
  "data": {
    "job": {
      "id": "job_01J9ZK4T8W",
      "object": "job",
      "kind": "image",
      "status": "succeeded",
      "money_status": "settled",
      "requested_model": "qwen-image-2512",
      "credits_charged": 2,
      "outputs": [
        {
          "kind": "image",
          "url": "https://…/job_01J9ZK4T8W.png?sig=…"
        }
      ]
    }
  }
}
Verify X-Signature
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received, before any JSON parsing.
export function verifyWebhook(header, rawBody, secret, toleranceSec = 300) {
  let t = null;
  const signatures = [];
  for (const item of (header ?? "").split(",")) {
    const [key, value] = item.split("=", 2);
    if (key === "t") t = Number(value);
    if (key === "v1") signatures.push(Buffer.from(value, "hex"));
  }
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((s) => s.length === expected.length && timingSafeEqual(s, expected));
}

// verifyWebhook(req.headers["x-signature"], rawBody, process.env.WEBHOOK_SECRET)
// Answer 2xx quickly, and skip an X-Event-Id you have already handled.

Errorsand what to do

Every error has the same shape: an error object with code, message, param and request_id. The ones you are most likely to meet:

CodeHTTPWhat to do
invalid_request400Fix the field named in param.
unauthorized401Check the key and the Bearer header.
insufficient_credits402Top up, then retry with the same Idempotency-Key.
forbidden_model403Add the model or tool to the key's allowlist in the studio, or use another key. Tools such as Enhance need their own entry.
quote_expired409Get a new quote; quotes last 10 minutes.
price_changed409The price rose above your cap. Quote again or raise max_credits.
content_rejected422Nothing was charged. Change the prompt or the images.
rate_limited429Wait for Retry-After, then retry.
upstream_unavailable503The model is unavailable. Retry later; a job that never starts is not charged.