---
title: "Edit an image"
method: POST
path: "/api/v1/image-edits"
tags: ["Images"]
---

# Edit an image

`POST /api/v1/image-edits`

## Request

- Base URL: `https://your-deployment.example.com`
- URL: `https://your-deployment.example.com/api/v1/image-edits`
- Auth: one of:
  - HTTP bearer
  - API key in header `x-api-key`

Queues an edit of an existing image and returns the quest that will carry the result, before the render runs. Poll `GET /api/v1/quests/{id}` until `status` is `"done"` (see the `editImage200PollResult` schema). `image` is the URL of the source image; `fabFileIds` must name at least one file, and the first is the inpainting mask. `referenceImageFabFileIds` adds up to 15 style anchors after the source image, so the mask always applies to the source; only gpt-image models accept them, and sending any with another model is rejected with a 400 rather than ignored. Both id fields take ids from `POST /api/v1/files`: upload each file, poll `GET /api/v1/files/{id}` until `moderation_status` is `clean`, then pass its `id`. An id that is not yet `clean` fails the edit on the polled quest. `image` takes a URL rather than an id, so a source uploaded the same way is passed as its `download_url`, which must still be unexpired when the render runs. The edited image is not a file id: read it from `files[].url` on the polled quest, not from `GET /api/v1/files/{id}`. `seed` makes an edit reproducible on BFL (FLUX) models only; gpt-image and Gemini models ignore it. Credits are checked when the render runs, so insufficient credits arrive on the polled quest rather than as a 422. `organization_id` is accepted as an alias of `organizationId`, for callers used to the snake_case video and music bodies; `null` bills the personal account. `POST /api/ai/edit-image` is a legacy alias of this endpoint. Authenticate with an API key (`b4m_live_`) or a JWT.

Optional `callbackUrl`: an https URL that receives a signed POST when the quest settles (success, failure, or a stop; a failed render is `type: "error"` in the body, same as the poll). The body is the `GET /api/v1/quests/{id}` response. Headers: `X-Webhook-Event-ID` (the same on every attempt for one settlement; delivery is at-least-once, so dedupe on it, and a retried quest settles again under a new one), `X-Webhook-Delivery-ID` (unique per attempt), `X-Webhook-Timestamp` (unix seconds), `X-Webhook-Signature-256` (`sha256=` + hex HMAC-SHA256 of `<timestamp>.<raw body>` keyed with the API key's callback signing secret), and `X-Event-Type: quest.settled`. Delivery makes up to 5 attempts, retrying on a 5xx, 408, 429, timeout or network error - never on any other 4xx, and redirects are not followed; your endpoint must answer 2xx within 10 seconds. Requires API-key authentication and a signing secret on that key (returned once at key creation, or via `POST /api/user-api-keys/{id}/callback-secret`). A `callbackUrl` is rejected with a 400 when the request is not API-key authenticated, the key has no signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks; a URL that is not https fails request validation with a 422. Polling still works whether or not `callbackUrl` is set.

## Request body

- EditImageRequest
  - `prompt` string, required
  - `model` union, required
    - 'gpt-image-1' | 'gpt-image-1.5' | 'gpt-image-1-mini' | 'gpt-image-2' | 'gpt-image-2.5-sunburst' | 'gpt-image-2.5-flare' | 'flux-pro-1.1' | 'flux-pro' | 'flux-pro-1.1-ultra' | 'flux-pro-1.0-fill' | 'flux-kontext-pro' | 'flux-kontext-max' | 'grok-imagine-image-quality' | 'gemini-2.5-flash-image' | 'gemini-3-pro-image-preview' | 'gemini-3-pro-image' | 'gemini-3.1-flash-image'
    - string
  - `n` number
  - `quality` 'standard' | 'hd' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto'
  - `response_format` 'b64_json' | 'url'
  - `size` union
    - '1024x1024' | '1024x1536' | '1536x1024' | '2048x2048' | '2048x1152' | '3840x2160' | '2160x3840' | 'auto' | '1024x768'
    - string
  - `style` 'vivid' | 'natural'
  - `background` 'transparent' | 'opaque' | 'auto' | 'null', nullable
  - `width` number
  - `height` number
  - `aspect_ratio` string
  - `sessionId` string, required
  - `questId` string
  - `organizationId` string, nullable: The organization to bill: one you belong to, or null for your personal credits. Omitted bills your home organization, or your personal credits if you have none. An organization-billed API key always bills its own organization: omit this or set it to the key's organization; any other value, null included, is rejected with a 403 (`errorCode: billing_organization_mismatch`).
  - `organization_id` string, nullable: Alias of `organizationId`, for callers used to the snake_case video and music bodies. Sending both with different values is a 422.
  - `fabFileIds` string[]
  - `referenceImageFabFileIds` string[]
  - `image` string, required
  - `output_format` 'png' | 'jpeg' | 'webp' | 'null', nullable
  - `seed` number, nullable
  - `callbackUrl` string, uri: Optional https URL that receives one signed POST when the job settles (success or failure). Requires API-key authentication and a callback signing secret on that key. The body is the GET /api/v1/quests/{id} response. Verify it with the `X-Webhook-Signature-256` header: `sha256=` + hex HMAC-SHA256 of `<X-Webhook-Timestamp>.<raw body>` keyed with the signing secret. Polling keeps working either way.

## Response `200`

Edit queued - NOT a finished render. The body is the quest itself, with no images yet; its outcome arrives on `GET /api/v1/quests/{id}`.

- EditImageResponse200
  - `id` string, required
  - `status` 'pending' | 'stopped' | 'running' | 'done'
  - `type` 'message' | 'oob' | 'error' | 'system' | 'voice_transcript'
  - `errorCode` 'insufficient_credits' | 'spend_cap_exceeded'
  - `reply` string, nullable
  - `replies` string[]
  - `fallbackInfo` object
    - `primaryModel` string, required
    - `primaryModelName` string, required
    - `fallbackModel` string, required
    - `fallbackModelName` string, required
    - `reason` string
  - `sessionId` string, required
  - `images` string[]

## Other responses

- `400`: `fabFileIds` is empty, `referenceImageFabFileIds` is set for a non-gpt-image model, or the `callbackUrl` was rejected: the request is not API-key authenticated, the key has no callback signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks. An organization-billed API key bills an organization that no longer exists, or one its owner is no longer a member of; re-mint the key.
- `401`: Missing or invalid credentials.
- `403`: The API key lacks the `ai:generate` scope, or the billing organization names one you do not belong to (or is not a valid id), or an organization-billed API key names any organization but its own (`errorCode: billing_organization_mismatch`).
- `404`: The session, or the quest being retried, does not exist or is not accessible to the caller. A nonexistent billing `organizationId` is `404` only for an admin caller; for anyone else, an invalid or inaccessible `organizationId` is `403` instead.
- `405`: The path does not serve this HTTP method.
- `422`: The body is malformed, or it names the billing organization ambiguously. Both `organizationId` and `organization_id` sent with different values is a `422`; so is an empty or blank value for either (a blank is never treated as an omission, which would bill a different payer), and so is any other billing-org-shaped key such as `billing_org_id` or `payerOrgId`, which would otherwise be stripped and silently bill the caller's home organization.

## Changes

- **2026-10-20** `d437aaac22ac`: 1 info
  - added the new optional request property `organization_id`

[Operation history](https://skmtc.dev/bike4mind/apis/bike4mind-api/changes/api/v1/image-edits/post.md)

---

[API](https://skmtc.dev/bike4mind/apis/bike4mind-api.md) · [All operations](https://skmtc.dev/bike4mind/apis/bike4mind-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bike4mind/apis/bike4mind-api/revisions/d437aaac22ac?raw)
