# Firefly Generate Image V3 Async

> Generate images with the Adobe Firefly V3 asynchronous API — job submission, status polling, webhook callbacks, prompt structure, content class, style and structure references, seed control, multi-variation results, and the migration from V2 sync to V3 async. Use whenever the user wants to "generate an image with Firefly", "text-to-image", "Firefly V3", "async generate", "polling", "jobId", "statusUrl", or upgrades from V2 sync. Returns the production pattern for the highest-volume Firefly workload — including a polling cadence that avoided rate limiting in live testing and a webhook design pattern for scaling to thousands of concurrent jobs.

- Skill: `focus-gts/firefly-generate-image-v3-async` (Agent Skill)
- Install (CLI): `npx skillmds@latest add focus-gts/firefly-generate-image-v3-async`
- Raw SKILL.md: https://api.skillmd.com/api/skills/focus-gts/firefly-generate-image-v3-async/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: Apache-2.0
- Author: Focus-GTS (https://skillmd.com/u/focus-gts)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/focus-gts/firefly-generate-image-v3-async

---


# Firefly Generate Image — V3 Async

The production pattern for text-to-image generation with Adobe Firefly's V3 asynchronous API (`/v3/images/generate-async`). V3 async is the right shape for every workload above one-off interactive use. The synchronous V3 endpoint (`/v3/images/generate`) is still available but its per-request blocking makes it unsuitable for production volume.

## When to Use This Skill

Use this skill when:
- Generating images from text prompts at any production volume
- Migrating from the synchronous endpoint (`/v3/images/generate`) to V3 async (`/v3/images/generate-async`)
- Building a campaign pipeline, banner-at-scale system, or batch generator
- Adding style or structure references to a generate call
- Designing a webhook-based generation pipeline

Do **NOT** use this skill when:
- The user wants a *variation* of an existing image — use `firefly-generate-similar`
- The user wants to *extend* an image canvas — use `firefly-expand-fill`
- The user is generating video — use `firefly-video-model`

## Sync vs Async — When to Use Which

| Property | V3 sync (`/v3/images/generate`) | V3 async (`/v3/images/generate-async`) |
|---|---|---|
| Latency to first byte | 10-30s (blocking) | ~200ms (returns jobId) |
| Time to result | Same | Same |
| Connection lifetime | Whole job | Just submission |
| Resilient to caller restarts | No — results lost on disconnect | Yes — pick up by jobId |
| Webhook callbacks | No | Yes (preferred) |
| Recommended for production | No | Yes |
| Recommended for one-shot CLI | Acceptable | Acceptable |

**Default to V3 async for everything.** The only acceptable reason to use sync is a one-shot script where the user is watching the terminal. The sync endpoint returns `200` with the finished `{size, outputs[]}` body directly — no jobId. The async variant (`/v3/images/generate-async`) is documented in Adobe's [async API guide](https://developer.adobe.com/firefly-services/docs/firefly-api/guides/how-tos/using-async-apis/); as of this writing the bundled `@adobe/firefly-apis` SDK spec covers only the sync path, so use raw HTTP for async submission.

## The Async Workflow

```
1. POST /v3/images/generate-async  →  { jobId, statusUrl, cancelUrl }
2. Either:
   a. Poll statusUrl every 1-2s until status === "succeeded" | "failed"
   b. OR provide a webhook callback URL — Firefly calls it on completion
3. On success: response includes outputs[].image.url (pre-signed)
4. Download from URL within ~1 hour or it expires
```

## Step 1 — Submit the Generation Job

Minimum required request:

```bash
curl --silent -X POST 'https://firefly-api.adobe.io/v3/images/generate-async' \
  -H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
  -H "X-Api-Key: $FIREFLY_SERVICES_CLIENT_ID" \
  -H 'Content-Type: application/json' \
  -d '{
    "prompt": "a single red apple on a white background",
    "contentClass": "photo",
    "numVariations": 1,
    "size": {"width": 1024, "height": 1024}
  }'
```

Response:

```json
{
  "jobId": "urn:ff:jobs:example:00000000-0000-4000-8000-000000000000",
  "statusUrl": "https://firefly-api.adobe.io/v3/status/urn:ff:jobs:...",
  "cancelUrl": "https://firefly-api.adobe.io/v3/cancel/urn:ff:jobs:..."
}
```

The submission returns in ~200ms regardless of job complexity. Persist the `jobId` immediately — that is the only thing that lets you recover the result if the worker crashes mid-poll.

## Step 2 — Request Shape

Full request shape with all common fields:

```json
{
  "prompt": "string (required, 1-1024 chars)",
  "negativePrompt": "string (optional, things to avoid)",
  "contentClass": "photo | art",
  "numVariations": 1,
  "size": {"width": 1024, "height": 1024},
  "seeds": [12345],
  "visualIntensity": 6,
  "style": {
    "presets": ["bold_colors"],
    "imageReference": {"source": {"uploadId": "abc-123"}},
    "strength": 75
  },
  "structure": {
    "imageReference": {"source": {"url": "https://..."}},
    "strength": 50
  },
  "customModelId": "optional-uuid-for-custom-model"
}
```

### Supported sizes (V3)

| Dimensions | Aspect |
|---|---|
| 1024×1024 | Square (1:1) |
| 2048×2048 | Square (1:1) |
| 2304×1792 | Landscape (4:3) |
| 1344×768 | Landscape (7:4) |
| 1152×896 | Landscape (9:7) |
| 2688×1536 | Widescreen (16:9) |
| 1792×2304 | Portrait (3:4) |
| 896×1152 | Portrait (7:9) |

Other dimensions are rejected with a 400 `bad_request` whose message enumerates the currently valid sizes — live-verified 2026-08-10: `Size must be one of {(2688, 1536), (1344, 756), (896, 1152), (1344, 768), (2688, 1512), (2304, 1792), (1152, 896), (2048, 2048), (1792, 2304), (1024, 1024)}`. Note the live error enumerates 10 tuples — a superset of the documented sizes in the table above (it additionally accepts 1344×756 and 2688×1512); the table shows Adobe's documented set. Prefer the documented sizes, or generate at the nearest match and crop in post.

### Content class

| Value | Use for |
|---|---|
| `photo` | Photorealistic output — products, scenes, people |
| `art` | Stylized output — illustrations, paintings, designs |

If omitted, Firefly auto-detects the content class from the prompt; setting it explicitly produces sharper results in the chosen direction.

### Variations and seeds

- `numVariations`: 1-4. Production typically uses 2-4 to give downstream selection logic options.
- `seeds`: array of integers. The same seed with the same prompt and model biases generation toward a consistent composition, but does not guarantee byte-identical output (live-verified: identical seeded requests produced different bytes). Use seeds for near-reproducibility and A/B comparisons, and archive the actual output artifact when you need an audit trail. If provided alongside `numVariations`, the seed count must equal `numVariations`.

## Step 3 — Poll for Completion

```bash
JOB_ID=$(echo "$SUBMIT_RESPONSE" | jq -r .jobId)
STATUS_URL=$(echo "$SUBMIT_RESPONSE" | jq -r .statusUrl)

while true; do
  RESPONSE=$(curl --silent "$STATUS_URL" \
    -H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
    -H "X-Api-Key: $FIREFLY_SERVICES_CLIENT_ID")
  STATUS=$(echo "$RESPONSE" | jq -r .status)
  case "$STATUS" in
    succeeded|failed) echo "$RESPONSE"; break ;;
    *) sleep 1 ;;
  esac
done
```

Node implementation:

```js
async function pollJob(statusUrl, accessToken, clientId, { intervalMs = 1000, maxMs = 300_000 } = {}) {
  const start = Date.now();
  while (Date.now() - start < maxMs) {
    const res = await fetch(statusUrl, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'X-Api-Key': clientId,
      },
    });
    if (!res.ok) throw new Error(`Status check failed: ${res.status}`);
    const data = await res.json();
    if (data.status === 'succeeded' || data.status === 'failed') return data;
    await new Promise(r => setTimeout(r, intervalMs));
  }
  throw new Error('Job polling timed out');
}
```

### Polling cadence

| Cadence | When to use |
|---|---|
| 1s | Interactive workloads, user is waiting |
| 2s | Background batch jobs, no user attention |
| 5s | Very large batches where polling rate matters more than latency |

Polling every 250ms or faster is wasteful — typical Firefly V3 jobs complete in 3-10 seconds. Sub-second polling will not make them complete faster.

### Polling and generation limits

In live testing, status polls did not trigger generate-endpoint 429s. Adobe does not publicly document how status calls are billed or rate-limited relative to generation (treatment may be org/contract-dependent — verify for your org), so keep polling to the 1-2s cadence above rather than assuming polls are free. The production concern with fast polling is wasted compute either way.

## Step 4 — Webhook Callbacks (Preferred at Scale)

> **Illustrative — verify against current Adobe docs.** The `notify`/`webhookUrl`/`X-Adobe-Signature` HMAC fields below are not part of the published Firefly generate-image request schema or the official SDK at time of writing. Treat this section as a design pattern, not a documented contract: confirm field names, headers, and signature scheme against the current Adobe Firefly Services documentation before relying on it. If your account does not expose webhook callbacks, use the polling pattern in Step 3.

For production batch workloads, webhooks beat polling. The pattern is: pass a callback URL on submission and Firefly calls it when the job completes.

```json
{
  "prompt": "...",
  "notify": {
    "webhookUrl": "https://api.example.com/firefly/callback",
    "secretKey": "shared-secret-for-hmac-validation"
  }
}
```

In this design shape, the service POSTs the job result body to the callback URL. Validate an HMAC signature over the body before trusting the payload — the header name and signature scheme must be confirmed against current Adobe documentation.

A webhook receiver in this pattern should provide:

| Component | Detail |
|---|---|
| Public URL | Reachable from the calling service |
| HMAC validation | e.g. SHA-256 over the body with the shared secret (confirm scheme with Adobe docs) |
| Idempotency | Callbacks may be delivered more than once; key jobs by `jobId` |
| Prompt 2xx acknowledgment | Respond quickly; treat delivery timing and retry semantics as unspecified until confirmed |

Regardless of webhook behavior, the result remains retrievable via the original `statusUrl` — always implement polling fallback for robustness.

## Step 5 — Read the Result

A succeeded job's response:

```json
{
  "status": "succeeded",
  "jobId": "urn:ff:jobs:...",
  "result": {
    "size": {"width": 1024, "height": 1024},
    "outputs": [
      {
        "seed": 12345,
        "image": {
          "url": "https://pre-signed-cdn-url..."
        }
      }
    ]
  }
}
```

**Download immediately.** The `image.url` is a pre-signed CDN URL that typically expires within 1 hour. For production:

```js
const result = await pollJob(statusUrl, token, clientId);
for (const output of result.result.outputs) {
  const imgRes = await fetch(output.image.url);
  const buffer = await imgRes.arrayBuffer();
  // Persist to your own bucket
  await s3.putObject({
    Bucket: 'my-outputs',
    Key: `${jobId}/${output.seed}.png`,
    Body: Buffer.from(buffer),
    ContentType: 'image/png',
  });
}
```

Never store the raw Firefly URL long-term. Always re-host in your own storage.

## Style and Structure References

V3 image generation supports two reference mechanisms — style (via presets and/or an image reference) and structure (via an image reference):

| Reference | Effect |
|---|---|
| `style.imageReference` | Output matches the *visual style* of the reference |
| `style.presets` | Output matches a named style preset |
| `structure.imageReference` | Output matches the *composition* of the reference |

Combine for fine control:

```json
{
  "prompt": "a futuristic city at sunset",
  "contentClass": "art",
  "style": {
    "presets": ["bold_colors"],
    "imageReference": {"source": {"uploadId": "style-ref-id"}},
    "strength": 75
  },
  "structure": {
    "imageReference": {"source": {"uploadId": "structure-ref-id"}},
    "strength": 50
  }
}
```

`strength`: structure accepts 0-100; style accepts 1-100 (0 is excluded). Higher = stronger influence. Start at 50 and tune.

The reference image must be a valid storage reference — see `firefly-services-storage-refs`.

## Custom Models

To generate with a custom-trained model, pass `customModelId` in the body **and** send the `x-model-version: image3_custom` header:

```bash
curl --silent -X POST 'https://firefly-api.adobe.io/v3/images/generate-async' \
  -H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
  -H "X-Api-Key: $FIREFLY_SERVICES_CLIENT_ID" \
  -H 'x-model-version: image3_custom' \
  -H 'Content-Type: application/json' \
  -d '{
    "prompt": "an icon of a key in our brand style",
    "customModelId": "00000000-0000-0000-0000-000000000000",
    "contentClass": "art",
    "size": {"width": 1024, "height": 1024}
  }'
```

The `x-model-version: image3_custom` header is required — without it, the request runs against the base Firefly model and `customModelId` is ignored, so you get base-model output with no error. Custom model IDs come from the custom-model training workflow — see `firefly-custom-models`.

## Production Patterns

### Pattern: Single-job CLI

For interactive one-shot use, submit + poll in a single function. Acceptable for <50 calls.

### Pattern: Queue-fronted batch

For >50 calls, use the SQS / Lambda / Token-Bucket pattern from `firefly-services-rate-limits`. Each queue message is one generate call. Worker submits, polls (or relies on webhook), persists result.

### Pattern: Multi-variation A/B funnel

For campaigns where you want choice:

1. Submit with `numVariations: 4` and exactly 4 seeds (seed count must equal `numVariations`)
2. Persist all 4 outputs to your bucket
3. Downstream selection logic (human or automated) picks 1
4. Audit which combinations win for future prompt tuning

This is the standard pattern for high-volume template-driven campaign asset production — variations give downstream creative teams options without re-running the pipeline.

## Validate

A correctly wired V3 async pipeline:

1. Submits jobs and persists `jobId` before any subsequent work
2. Polls with 1-2s cadence (webhook callbacks, where available and confirmed against current docs, are an alternative — see Step 4)
3. Honors `statusUrl` from the submission response — does not hardcode URLs
4. Downloads result URLs within 1 hour and re-hosts in your own bucket
5. Has retry-with-backoff on submission (covered by `firefly-services-rate-limits`)
6. Logs `jobId` for every submission for downstream audit

## Troubleshooting & Edge Cases

- **`numVariations` > 4 rejected:** Max is 4. Submit multiple jobs if you need more.
- **Size rejected as invalid:** Use only the published sizes (see Supported sizes table above).
- **`prompt` rejected as too long:** Max 1024 chars. Strip or rephrase.
- **Webhook never fires:** Adobe was unable to reach the URL. Test with a `curl -X POST` from outside your VPC. Fall back to polling.
- **Job stuck in `running` for >5 minutes:** Cancel via `cancelUrl` and resubmit. Adobe-side jobs almost always complete in under 30s; 5+ minutes is a sign something is wrong.
- **`outputs` array is empty on success:** Content safety filtered all variations. Rephrase the prompt — see `firefly-services-troubleshoot` §6.
- **Different output between identical requests:** Expected — generation is not byte-deterministic even with a fixed seed. Set `seeds: [<int>]` to bias toward a consistent composition, and archive outputs you need to reproduce exactly.

## Chaining with Other Skills

- `firefly-services-auth` — Token freshness before submission
- `firefly-services-storage-refs` — Required for any reference-image-based generation
- `firefly-services-rate-limits` — Production batch pipeline
- `firefly-services-troubleshoot` — When generation fails

## References

- [Firefly Async API Guide](https://developer.adobe.com/firefly-services/docs/firefly-api/guides/how-tos/using-async-apis/)
- [Generate Image V3 API Reference](https://developer.adobe.com/firefly-services/docs/firefly-api/api/)
- [Style Reference Concepts](https://developer.adobe.com/firefly-services/docs/firefly-api/guides/concepts/style-image-reference/)
- [Structure Reference Concepts](https://developer.adobe.com/firefly-services/docs/firefly-api/guides/concepts/structure-image-reference/)
- [Seeds Concept](https://developer.adobe.com/firefly-services/docs/firefly-api/guides/concepts/seeds/)

