Skip to main content
subjects[] is how you tell POST /v1/images which people or reference images to compose into the output — one ordered array, each entry either a character (a saved identity) or a reference (ad-hoc images you supply inline). It supersedes the older character_id and reference_image_urls fields, which only ever let you attach a single identity input. This guide covers what the field does, how to migrate off the older fields, and what’s different about a generation that uses it. (For loose, ungrouped references — raw material your prompt draws on, with no identity guarantee — see Context images instead.)

What subjects[] is

Each entry has a type and, depending on the type, either a character_id or an image_urls array:
  • character subjects reference a saved character (from POST /v1/characters) by its char_<ulid> — a legacy UUID is also accepted, same as the top-level character_id field. The character must be status: ready; otherwise you get 400 character_not_ready.
  • reference subjects carry 1-10 image_urls — each entry either an opaque file_<ulid> from POST /v1/files or an https:// URL. This is the same input shape reference_image_urls already accepts.
The array is ordered and positional: the platform composes every input image into numbered slots (Image 1, Image 2, …), assigned in the order the subjects appear. A character subject always occupies exactly one slot; a reference subject occupies one slot per URL. So a character subject followed by a 3-image reference subject places the character at Image 1 and the reference subject across Images 2-4. Reordering subjects[] changes which images map to which slot — put your intended focal subject first. Omit subjects entirely, or send [], for a plain text-to-image or style (lora_id) generation — both are treated as “no subjects.”

Migrating from the legacy fields

Both legacy identity fields fold onto subjects[] as a single-entry array.

character_id → one character subject

reference_image_urls → one reference subject

Both migrations are purely mechanical — wrap the same value in a one-entry subjects[] array with the matching type. The generation behaves identically for a single identity; subjects[] only starts to matter once you want more than one.

Existing requests keep working

character_id and reference_image_urls are not being removed and there is no scheduled removal date. If you keep sending them, your integration keeps working exactly as it does today — nothing to fix urgently. What changes: a successful (2xx) response to a request that used either legacy field now carries two extra headers, nudging you toward subjects[]:
  • Deprecation: true — advisory only. There’s no Sunset header and no countdown; it simply signals “this request used a superseded field shape.”
  • Link — points back to this page.
The check is on the field name, not its value — a request body that includes the key at all (even "character_id": null) is still counted as legacy-shaped. If you’ve fully migrated and still see the header, check for a stray character_id/reference_image_urls key your client is serializing. The header is only ever set on success; a request that 400s/404s/402s never carries it, since there’s no successful call to advertise a migration path for.

Quickstart: two characters in one image

The clearest reason to move to subjects[] — compositing more than one identity into a single output — needs nothing beyond two saved characters and an array:
Poll GET /v1/images/{id} (or register a webhook endpoint) until status reaches a terminal value, same as any other generation. Need a char_<ulid> to try this with? See Create a character. Mixing a character subject with a reference subject in the same array works the same way — swap the second entry for { "type": "reference", "image_urls": [...] }.

Rules

The 10-image budget

Every subject contributes at least one input image to the composition, and the total across the whole array is capped at 10:
  • A character subject always contributes exactly 1 image (its saved cover reference).
  • A reference subject contributes one image per URL in image_urls.
Go over the cap — for example nine character subjects plus a reference subject with two URLs (11 images) — and the request returns 400 too_many_reference_images. This endpoint does not trim on your behalf: fix the request client-side (drop a subject, or drop a URL from a reference subject) and resubmit. See too_many_reference_images in the error reference.

The response

Every GET/POST response for a generation — and every webhook delivery, which carries the same shape — now includes a subjects field:
  • Entries are positional — entry N echoes entry N of the subjects[] you sent.
  • character entries echo the opaque character_id you sent (never anything else about the character).
  • reference entries echo character_id: null and image_count — the number of URLs you sent, never the URLs themselves.
  • subjects is null on every generation that didn’t compose subjects — plain prompt-only generations, style/LoRA generations, and all video generations. It’s a new, always-present, nullable key: existing integrations that don’t read it are unaffected.

What’s different about a subject generation

A generation that uses the explicit subjects[] field always runs on a fixed, optimized composition pipeline rather than the general-purpose generation path. (Requests using the legacy single-identity fields may or may not be served by this pipeline depending on the rest of the request — the reliable signal is the response itself: subjects is non-null exactly when the composition pipeline handled the generation.) For subjects[] requests, a few things follow:
  • Exactly one output image. count is accepted but ignored — you always get exactly one image back, and pricing reflects that: it’s the standard single-image rate, with no per-subject or per-reference-image surcharge, regardless of how many subjects you attach (up to the 10-image budget).
  • Tuning knobs are inert. steps, guidance_scale, cfg_rescale, denoise_strength, negative_prompt, and seed are all still accepted on the request — none of them will cause an error — but subject generations use optimized settings and don’t apply them. On the response, cfg_rescale and denoise_strength are omitted, and seed and negative_prompt come back null, regardless of what you sent.
  • output_format: "png" for transparency. The default is jpeg. Set output_format: "png" if you want a transparent background where the composition supports it.

Estimates

POST /v1/images/estimate mirrors all of the above for a subjects[] request: send the same body and get back a quote for exactly one image, at the standard single-image rate. One difference from the real create call: the estimate never fetches your https:// reference URLs — it validates their shape and, for character subjects, that the character exists and is ready, but any problem specific to an inline URL (unreachable host, non-image content, and so on) only surfaces when you actually call POST /v1/images.

Where to next?