> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Image Edit

> Edit an image guided by a text prompt. Accepts either a single image via "image" or multiple images via "images[]" (mutually exclusive).

Image Edit transforms an existing image based on a text prompt (image-to-image). Returns a `request_id` for status polling.

<Note>
  **Prerequisite:** Consult the [Model Selection](/api/v2/utilities/models) endpoint to identify a valid model `slug` and check specific **limits**.
</Note>

<Tip>
  **OpenAI SDK users:** This endpoint is also available via the OpenAI-compatible surface as `POST /v1/images/edits` at `https://oai.deapi.ai/v1`. Inpainting (`mask` parameter) is not supported. See [OpenAI Compatibility](/openai-compatibility).
</Tip>


## OpenAPI

````yaml openapi-v2.json POST /api/v2/images/edits
openapi: 3.1.0
info:
  title: deAPI REST API
  description: >-
    Decentralized AI inference API for image generation, video processing, audio
    transcription, and more.


    ## Rate limits


    Two windows can apply to a request: a per-minute one (RPM) and a per-day one
    (RPD). Every

    throttled response — success or 429 — carries the `X-RateLimit-*` headers,
    so you can pace

    traffic without waiting to be rejected. Each header documents itself: see
    `components/headers`,

    or the headers listed on any response. What follows is only what a header
    cannot say about

    itself.


    Windows open on your first request into a bucket and run their full length
    from that second —

    never to a calendar boundary, in any timezone. That is deliberate: a
    boundary every account

    shares is a boundary every account can double up on.


    **The headers describe the pool *this* request landed in, and
    `X-RateLimit-Source` is what

    identifies it — not `X-RateLimit-Limit` alone.** A per-model limit is keyed
    to the model you

    named, so a request that carried no recognised `model` — rejected at
    validation, an unknown

    slug, an empty body — was counted against your tier pool, and it is the tier
    pool the headers

    then describe. The two pools are fully independent: separate minute window,
    separate day,

    separate `X-RateLimit-Daily-Reset`. This is a consequence of rate limiting
    running *before*

    validation, which is what keeps malformed traffic from reaching the rest of
    the API

    uncounted. Compare headers only across responses carrying the same
    `X-RateLimit-Source`.


    **Buckets are shared in ways worth knowing.** API **v1 and v2 count against
    the same bucket**,

    so migrating grants no fresh allowance and mixed traffic behaves as one
    stream. All `*/price`

    and `*/price-calculation` endpoints share a single quote bucket, separate
    from generation.

    Prompt enhancement (`/v2/prompts/enhancements`) has its own bucket as well,
    so heavy prompt

    work no longer eats into your image throughput.


    **Counting is exact in sequence, best-effort in parallel.** Sent one after
    another, your

    requests are counted precisely: the one that crosses the limit is the one
    that gets 429. Sent

    in parallel, a few beyond the limit can slip through, because the counter is
    read and then

    incremented as two steps and requests already in flight are not yet visible
    to one another.

    The excess is bounded by how many of your own requests are in flight at
    once, and it only

    ever runs in your favour — you are never rejected while below your limit. So
    do not treat

    `X-RateLimit-Remaining` as a reservation: it is a snapshot, and a request
    you sent a

    millisecond ago may already have spent what it shows.


    Requests rejected before authentication (HTTP 401) never reach the limiter
    and carry no

    rate-limit headers at all.
  contact:
    name: deAPI Support
    url: https://deapi.ai
    email: support@deapi.ai
  version: 0.0.1
servers:
  - url: https://api.deapi.ai
    description: Production API Server base URL
security:
  - bearerAuth: []
tags:
  - name: Client API v2
    description: Current client endpoints (OpenAI-aligned noun-based paths)
paths:
  /api/v2/images/edits:
    post:
      tags:
        - Client API v2
      summary: Edit an image guided by a text prompt
      description: >-
        Edit an image guided by a text prompt. Accepts either a single image via
        "image" or multiple images via "images[]" (mutually exclusive).
      operationId: editImage
      parameters:
        - $ref: '#/components/parameters/AcceptHeader'
      requestBody:
        description: >-
          Image generation parameters. Either "image" or "images[]" must be
          provided (not both).
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Img2ImgRequestBody'
      responses:
        '200':
          description: ID of the inference request.
          headers:
            X-RateLimit-Policy:
              $ref: '#/components/headers/X-RateLimit-Policy'
            X-RateLimit-Source:
              $ref: '#/components/headers/X-RateLimit-Source'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-RateLimit-Daily-Limit:
              $ref: '#/components/headers/X-RateLimit-Daily-Limit'
            X-RateLimit-Daily-Remaining:
              $ref: '#/components/headers/X-RateLimit-Daily-Remaining'
            X-RateLimit-Daily-Reset:
              $ref: '#/components/headers/X-RateLimit-Daily-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobRequestResponseResource'
        '401':
          description: Unauthorized user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/response_error_default'
        '403':
          $ref: '#/components/responses/AccountSuspended'
        '404':
          description: Not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/response_error_default'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
      security:
        - bearerAuth: []
components:
  parameters:
    AcceptHeader:
      name: Accept
      in: header
      required: true
      schema:
        type: string
        default: application/json
        enum:
          - application/json
  schemas:
    Img2ImgRequestBody:
      description: >-
        Every example in this schema is a working set for the `model` example
        shown — they are not defaults and not portable. Sizes, step counts,
        frame counts and frame rates are all per-model: read `info.limits` and
        `info.defaults` for the model you actually intend to use from `GET
        /api/v2/models`. Substituting a different `model` while keeping these
        values is the most common source of a 422.


        An input image is MANDATORY and comes in exactly one of two shapes —
        `image` for a single file, or `images[]` for several. Sending neither is
        a 422 ("At least one image is required"), and sending both is a 422 as
        well; the `oneOf` below is that choice, not a pair of optional fields.
        Everything else applies to both shapes.
      required:
        - prompt
        - model
        - seed
      properties:
        prompt:
          description: The main prompt for image generation
          type: string
          example: A beautiful sunset over mountains
        negative_prompt:
          description: >-
            Elements to avoid in the generated image. Dropped (not forwarded to
            the worker) when the model declares
            `info.features.supports_negative_prompt: false`.
          type:
            - string
            - 'null'
          example: blur, darkness, noise
        image:
          description: >-
            Single source image to edit. Required if "images[]" is not provided.
            Mutually exclusive with "images[]". Supported formats: JPG, JPEG,
            PNG, GIF, BMP, WebP. Maximum file size: 10 MB.
          type: string
          format: binary
        images[]:
          description: >-
            Multiple source images for editing. Maximum count is model-dependent
            (see model specs max_input_images, defaults to 1). Required if
            "image" is not provided. Mutually exclusive with "image". Supported
            formats: JPG, JPEG, PNG, GIF, BMP, WebP. Maximum file size per
            image: 10 MB.
          type: array
          items:
            type: string
            format: binary
        width:
          description: >-
            Output image width in pixels. Optional - defaults to first input
            image width if not specified. Subject to model-specific min/max
            limits. The accepted range is model-specific — read
            `info.limits.min_width`/`max_width` and `min_height`/`max_height`
            for your `model` from `GET /api/v2/models`. Some models also publish
            `dimension_multiple` or `resolution_step`: values are snapped DOWN
            to that grid before validation, so an off-grid size is silently
            reduced rather than rejected. `max_pixels` and `max_ratio`, where
            present, cap total area and aspect ratio on top of the per-side
            bounds.
          type:
            - integer
            - 'null'
          example: 512
        height:
          description: >-
            Output image height in pixels. Optional - defaults to first input
            image height if not specified. Subject to model-specific min/max
            limits. The accepted range is model-specific — read
            `info.limits.min_width`/`max_width` and `min_height`/`max_height`
            for your `model` from `GET /api/v2/models`. Some models also publish
            `dimension_multiple` or `resolution_step`: values are snapped DOWN
            to that grid before validation, so an off-grid size is silently
            reduced rather than rejected. `max_pixels` and `max_ratio`, where
            present, cap total area and aspect ratio on top of the per-side
            bounds.
          type:
            - integer
            - 'null'
          example: 512
        model:
          description: >-
            The model to use for image editing. Available models can be
            retrieved via the GET /api/v2/models endpoint.
          type: string
          example: QwenImageEdit_Plus_NF4
        loras:
          description: >-
            Array of LoRA models to apply. Only models that publish a `loras`
            array in `GET /api/v2/models` have any to apply — the `model`
            example shown here publishes none, so leave the field out unless you
            switched to a model that does. Example is deliberately empty: a LoRA
            name is only valid for the one model that lists it.
          type: array
          items:
            required:
              - name
              - weight
            properties:
              name:
                description: >-
                  LoRA identifier, copied verbatim from `loras[].name` for your
                  `model` in `GET /api/v2/models`. That is the value the API
                  validates against — not the human-readable
                  `loras[].display_name`.
                type: string
              weight:
                description: Weight of the LoRA model (must be non-negative)
                type: number
                minimum: 0
            type: object
          example: []
        guidance:
          description: >-
            Guidance scale. Bounded by `info.limits.min_guidance` /
            `max_guidance` from `GET /api/v2/models` (0–30 when the model
            declares none). Required when the model declares
            `info.features.supports_guidance: true`; optional when the flag is
            absent. When the model declares `supports_guidance: false` the value
            is accepted and dropped — it is neither forwarded to the worker nor
            priced.
          type: number
          example: 7.5
        steps:
          description: >-
            Number of inference steps. Bounded by `info.limits.min_steps` /
            `max_steps` from `GET /api/v2/models` (1–100 when the model declares
            none). Required when the model declares
            `info.features.supports_steps: true`; optional when the flag is
            absent. When the model declares `supports_steps: false` the value is
            accepted and dropped — it is neither forwarded to the worker nor
            priced.
          type: integer
          example: 20
        quality:
          description: >-
            Quality tier, for models that support one (e.g. gpt-image:
            low/medium/high/auto). Only the values listed in that model's
            available options are accepted; ignored for models without a quality
            tier. Accepted values are model-specific — read
            `info.limits.quality_options` for your `model` from `GET
            /api/v2/models`. A model with an empty or absent list does not
            support a quality tier and ignores the field.
          type:
            - string
            - 'null'
          example: medium
        seed:
          description: Random seed for generation
          type: integer
          example: 42
        enhance_prompt:
          description: >-
            When true, the prompt is boosted by the inline prompt booster before
            generation (async pre-step). Requires a prompt-booster guide
            configured for the model; the boost fee is billed on the job.
            Rejected with 422 if no guide exists for the model or the account
            balance cannot cover the boost fee.
          type: boolean
          example: false
          default: false
        webhook_url:
          description: >-
            Optional HTTPS URL to receive webhook notifications for job status
            changes (processing, completed, failed). Must be HTTPS. Max 2048
            characters. A per-request URL is self-contained: it is delivered
            even when the account has no webhook configuration, or the account
            webhook is disabled. See the `webhooks` block at the top level of
            this document for the event payloads and the headers each delivery
            carries.
          type:
            - string
            - 'null'
          format: uri
          example: https://your-server.com/webhooks/deapi
          maxLength: 2048
        webhook_secret:
          description: >-
            Optional per-request HMAC secret (min. 32 chars) used to sign the
            webhook callback. Overrides the account-default secret for this job
            only. If omitted, the account-default secret signs the callback; if
            no secret exists at all, the callback is still delivered but
            UNSIGNED (empty X-DeAPI-Signature header). Requires `webhook_url` to
            also be set.
          type:
            - string
            - 'null'
          example: a1b2c3d4e5f60708091a2b3c4d5e6f7081920a1b2c3d4e5f60708091a2b3c4d5
          maxLength: 255
          minLength: 32
      type: object
      oneOf:
        - title: One image
          required:
            - image
          type: object
        - title: Several images
          required:
            - images[]
          type: object
    JobRequestResponseResource:
      required:
        - data
      properties:
        data:
          description: Information from success endpoint
          required:
            - request_id
          properties:
            request_id:
              description: >-
                UUID identifying the created inference request. Pass it to `GET
                /api/v2/jobs/{job_request}` to poll status and collect the
                result.
              type: string
              format: uuid
              example: c08a339c-73e5-4d67-a4d5-231302fbff9a
          type: object
      type: object
    response_error_default:
      properties:
        data:
          description: Information from success endpoint
          type: object
        message:
          description: Error general message
          type: string
        errors:
          description: Information about errors
          type: array
          items: {}
        statusCode:
          description: Status code
          type: integer
        code:
          description: >-
            A stable, machine-readable reason, on the responses that publish
            one. Refused credentials (401): `missing_key` — no Bearer
            credentials arrived at all; `invalid_key` — a key was presented but
            is unknown, carries the wrong secret, or is malformed; `key_expired`
            — past its expiry date; `key_revoked` — deactivated. Refused
            requests: `insufficient_permissions` (403), `ip_not_allowed` (403),
            `active_key_limit_reached` (422), `insufficient_balance` (422),
            `key_limit_exceeded` (402), `rate_limited` (429). Absent wherever a
            response publishes no code, so treat an unknown value as the status
            code alone — the list grows.
          type: string
          enum:
            - missing_key
            - invalid_key
            - key_expired
            - key_revoked
            - insufficient_permissions
            - ip_not_allowed
            - active_key_limit_reached
            - insufficient_balance
            - key_limit_exceeded
            - rate_limited
      type: object
    response_error_rate_limit:
      description: Rate limit exceeded response
      properties:
        message:
          description: Error message
          type: string
          example: Too Many Attempts.
      type: object
  headers:
    X-RateLimit-Policy:
      description: >-
        Every window that applies, as `<limit>;w=<seconds>` elements. Always
        present on a throttled response, so a single element means there is no
        daily limit — do not infer that from the absence of the Daily-* headers.
        `300;w=60` is minute-only; `3;w=60, 100;w=86400` is both.
      schema:
        type: string
        example: 3;w=60, 100;w=86400
    X-RateLimit-Source:
      description: >-
        Where the limit came from, so you know what would change it:

        `tier` — your account tier's rate for this endpoint;

        `fallback` — no rate configured for this tier and endpoint, degraded
        default in force;

        `model` — a per-model override for the model in the request, unaffected
        by your tier

        (only when the request carried a recognised `model`, otherwise the tier
        pool applies);

        `static` — a flat limit outside the tier system, identical for every
        account.


        If you ever see `fallback`, tell us: it means no rate is configured for
        that tier and endpoint.
      schema:
        type: string
        example: tier
        enum:
          - tier
          - fallback
          - model
          - static
    X-RateLimit-Limit:
      description: Maximum requests allowed per minute (RPM)
      schema:
        type: integer
        example: 3
    X-RateLimit-Remaining:
      description: Remaining requests in current minute window
      schema:
        type: integer
        example: 2
    X-RateLimit-Reset:
      description: >-
        Epoch second at which the minute (RPM) window refills. Fixed window —
        the value does not drift as you spend the quota. Describes the same
        window as X-RateLimit-Limit, including on a daily rejection.
      schema:
        type: integer
        example: 1755800460
    X-RateLimit-Daily-Limit:
      description: Maximum requests allowed per day (RPD)
      schema:
        type: integer
        example: 100
    X-RateLimit-Daily-Remaining:
      description: Remaining requests in current day window
      schema:
        type: integer
        example: 95
    X-RateLimit-Daily-Reset:
      description: >-
        Epoch second at which the daily (RPD) window refills — 86400 s after the
        first request that opened this bucket's window, not a calendar midnight.
        Present only when a daily limit applies.
      schema:
        type: integer
        example: 1755846000
    X-RateLimit-Type:
      description: >-
        Which limit rejected the request. Sent only on 429. Read it before
        anything else on the response: it decides what the other `X-RateLimit-*`
        headers are counting.


        `minute` — the per-request RPM window. `daily` — the per-request RPD
        window; the Daily-* pair is the one at 0.


        `key-creation` — the hourly cap on keys created over the API (`POST
        /api/v2/keys`), per account and shared by its ADMIN keys. The whole
        `X-RateLimit-*` set describes that bucket and there are no Daily-*
        headers.


        `ip-not-allowed` — too many calls from an address your key's IP
        whitelist refuses. Counted against that address, never against your
        account.
      schema:
        type: string
        example: minute
        enum:
          - minute
          - daily
          - key-creation
          - ip-not-allowed
    Retry-After:
      description: >-
        Seconds until the window that rejected you frees up (60 for minute, up
        to 86400 for daily). Counted from when that window opened, not to a
        calendar boundary. Sent only on 429.
      schema:
        type: integer
        example: 60
  responses:
    AccountSuspended:
      description: >-
        The account is suspended. Applies to every endpoint that spends credits
        — generation, transcription and prompt enhancement — and is returned
        before any validation runs, so a suspended account sees this rather than
        a 422 about the body. Price-estimate endpoints are not gated and keep
        working.
      content:
        application/json:
          schema:
            properties:
              message:
                description: >-
                  Human-readable explanation. No `errors` object accompanies
                  this response.
                type: string
                example: >-
                  Client account is suspended. If you believe this is a mistake,
                  contact us.
            type: object
    ValidationError:
      description: >-
        Validation failed. Common errors include: model does not exist, model
        does not support the inference type for this endpoint, the model is not
        available on your plan (it requires a higher tier than your account has
        — upgrade to use it), or invalid request parameters.
      content:
        application/json:
          schema:
            properties:
              message:
                description: General error message
                type: string
                example: The selected model does not support Text To Image.
              errors:
                description: >-
                  Detailed validation errors by field. A model your plan does
                  not cover is reported on the "model" field with the message
                  "This model is not available on your plan. Please upgrade to
                  access it." Such a model is also omitted from GET /models.
                type: object
                example:
                  model:
                    - >-
                      This model is not available on your plan. Please upgrade
                      to access it.
            type: object
    RateLimitExceeded:
      description: >-
        Rate limit exceeded. Check X-RateLimit-Type header to determine if
        minute (RPM) or daily (RPD) limit was hit.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Daily-Limit:
          $ref: '#/components/headers/X-RateLimit-Daily-Limit'
        X-RateLimit-Daily-Remaining:
          $ref: '#/components/headers/X-RateLimit-Daily-Remaining'
        X-RateLimit-Type:
          $ref: '#/components/headers/X-RateLimit-Type'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-RateLimit-Daily-Reset:
          $ref: '#/components/headers/X-RateLimit-Daily-Reset'
        X-RateLimit-Source:
          $ref: '#/components/headers/X-RateLimit-Source'
        X-RateLimit-Policy:
          $ref: '#/components/headers/X-RateLimit-Policy'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/response_error_rate_limit'
  securitySchemes:
    bearerAuth:
      type: http
      description: >-
        Sanctum personal access token, sent as `Authorization: Bearer <token>`.
        The token is opaque — it carries no claims and no embedded expiry, so do
        not attempt to decode it. Issue and revoke tokens from your account
        dashboard.
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.