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

# Run Seedream 4.5 (seedream-45)

> Latest Seedream with enhanced detail, refined composition, and multi-reference image support for professional visuals.

Submits an asynchronous job and returns `202` with a job id. Fetch the result at `GET /v3/jobs/{job_id}` — each item in its `outputs[]` follows the `OutputItem` schema — or track progress via `GET /v3/jobs/{job_id}/status` / the SSE stream at `GET /v3/jobs/{job_id}/stream`.



## OpenAPI

````yaml /openapi-v3.json post /models/seedream-45
openapi: 3.1.0
info:
  title: Hedra API v3
  description: >-
    Every leading image, video, and audio model behind one endpoint, one key,
    and one bill. Browse the [model
    catalog](https://www.hedra.com/develop/models), then ship with the same key.


    **Authenticate** every request with your API key — create one in the
    [developer console](https://www.hedra.com/develop/api-keys): `Authorization:
    Key <key_id>:<secret>`.


    **Quickstart** — submit a job, poll it, then read the output URL:


    ```bash

    # 1. Submit — capture the job id from the 202 ack

    JOB_ID=$(curl -sS -X POST https://api.hedra.com/v3/models/gpt-image-2 \
      -H "Authorization: Key $HEDRA_KEY" \
      -H "Content-Type: application/json" \
      -d '{"input": {"prompt": "a space cat", "quality": "medium", "aspect_ratio": "1:1", "resolution": "1K"}}' | jq -r .job_id)

    # 2. Poll until status is COMPLETED (or FAILED)

    curl https://api.hedra.com/v3/jobs/$JOB_ID/status -H "Authorization: Key
    $HEDRA_KEY"


    # 3. Read the result — outputs[].url holds the generated file

    curl https://api.hedra.com/v3/jobs/$JOB_ID -H "Authorization: Key
    $HEDRA_KEY"

    ```


    **Find a model** — `GET /models` lists every model with its `id` and human
    `name`; each operation in the *Run a model* section is titled with both, so
    a model you know by name ("GPT Image 2") maps straight to the id you submit
    to (`gpt-image-2`). One model's typed input schema: `GET
    /models/{id}/openapi.json`.


    Estimate cost before you run with `POST /models/{model}/estimate`, and check
    your balance at `GET /balance`. For volume pricing and custom limits, [talk
    to us](https://www.hedra.com/enterprise).


    The full machine-readable spec is at
    `https://api.hedra.com/v3/openapi.json`.
  version: 3.0.0
servers:
  - url: https://api.hedra.com/v3
security: []
tags:
  - name: Models
    description: Discover models and estimate cost.
  - name: Run a model
    description: >-
      One typed submit endpoint per model — titled `Run <name> (<id>)`, so the
      model you know by name is the operation you call. Every one returns the
      same `202` job envelope; track it under Jobs.
  - name: Jobs
    description: Poll, stream, and list submitted jobs.
  - name: Access
    description: API keys, browser tokens, and file uploads.
  - name: Billing
    description: Balance and usage, in US dollars.
  - name: Webhooks
    description: >-
      Configure, test, verify, and inspect outbound webhooks.


      **Delivery and retries.** Delivery is at-least-once. A `2xx` from your
      endpoint is success; anything else — including a redirect, which is never
      followed — is retried. Hedra makes up to **12 attempts over approximately
      6 hours**, backing off 10s, 30s, 90s, 4m30s, 13m30s, 40m30s, then hourly.
      An endpoint that is unreachable for the whole window is marked `FAILED`
      and is not retried again; replay it with `POST
      /webhooks/deliveries/{job_id}/redeliver`. Two failures are permanent and
      stop the ladder immediately: a URL that resolves to a blocked address
      range, and a redirect pointing at one.


      Because the retry window is bounded, respond quickly and do your own
      processing asynchronously — acknowledge with a `2xx` first.


      **Deduplication.** Key on **`X-Hedra-Webhook-Id`**. It identifies the
      *event* — it is the job's own id, and it is byte-identical across every
      retry and every replay. Do **not** hash the request body: each attempt
      re-signs the output URL, so the body legitimately differs between attempts
      of the same event and a body hash would treat every retry as new.


      Each POST also carries `X-Hedra-Webhook-Attempt` (1-based, informational)
      and **`X-Hedra-Webhook-Redelivery`** (`true` / `false`). Redelivery is the
      exception to deduplication: when it is `true`, an operator asked for this
      event to be sent again — process it even if you have already recorded that
      id, because that request is the whole reason it was sent.


      **Inspecting deliveries.** `GET /webhooks/deliveries` reports each
      delivery's cumulative `attempts`, its latest outcome (`status`,
      `last_response_status`, `last_error`), and its replay history:
      `redelivery_count` counts operator replays, and `redeliveries` archives
      the outcome each replay superseded — a replay re-sends on the same
      delivery record and never erases the previous result. Each entry's
      `attempts` value is the cumulative total when that replay was requested,
      so the per-cycle attempt counts (and any total beyond the 12-attempt
      ladder) can be read off the record directly.


      **Why a delivery failed.** `last_error` — on the delivery and on every
      archived replay entry — is the same error envelope a failed job returns
      from `GET /jobs/{job_id}`, not a free-text diagnostic. Its `code` comes
      from the one error vocabulary this API uses, narrowed for delivery to this
      set:


      | `code` | Means |

      | --- | --- |

      | `DEADLINE_EXCEEDED` | Your endpoint did not respond before the delivery
      timeout. |

      | `UNAVAILABLE` | Your endpoint could not be reached (DNS, TLS, or
      connection failure) or answered `5xx` — or redirected, which is never
      followed. |

      | `RESOURCE_EXHAUSTED` | Your endpoint answered `429`. |

      | `UNAUTHORIZED` | Your endpoint answered `401`. |

      | `PERMISSION_DENIED` | Your endpoint answered `403`. |

      | `NOT_FOUND` | Your endpoint answered `404`. |

      | `INVALID_ARGUMENT` | Your endpoint answered some other `4xx`. |

      | `FAILED_PRECONDITION` | The URL is not a permitted webhook target — it
      resolves to a blocked address range, or redirects to one. Fix the URL;
      replaying will not help. |

      | `INTERNAL` | Hedra could not build the payload. |

      | `UNKNOWN` | No classification is available, including for deliveries
      that failed before this field became structured. |


      `retryable` describes the condition, not what Hedra did: every non-2xx
      response is retried on the ladder above, so it answers whether replaying
      the delivery is likely to help. The `message` is a fixed summary — your
      endpoint's URL, address, headers, credentials, and response body are never
      echoed back, so treat your own logs, not this field, as the record of what
      your endpoint returned.


      **Verifying a delivery.** Every request carries
      `X-Hedra-Webhook-Signature`, a hex-encoded ed25519 signature over a
      canonical string of **five newline-separated fields, in this order**:


      ```

      {X-Hedra-Webhook-Timestamp}

      {X-Hedra-Webhook-Id}

      {X-Hedra-Webhook-Event}

      {X-Hedra-Webhook-Redelivery}

      {sha256 hex digest of the raw request body}

      ```


      Rebuild that string from the request you received, verify it against the
      key from `GET /webhooks/public-key`, and reject the delivery if the
      timestamp is more than 5 minutes old. Hash the body **exactly as
      received**, before any JSON parsing or re-serialization.


      The signature deliberately covers the deduplication id and the redelivery
      flag, not just the body — both of them decide whether you process a
      duplicate, so an unsigned copy of either would let anyone who captured a
      delivery replay it past your idempotency check. Verify *before* you act on
      any header. `X-Hedra-Webhook-Attempt` is outside the signature and is
      informational only; do not branch on it.
  - name: Log drains
    description: >-
      Stream signed job lifecycle logs to HTTPS destinations.


      **Why the last batch failed.** `last_error` is the same error envelope a
      failed job returns from `GET /jobs/{job_id}`, not a free-text diagnostic.
      It is null until a batch fails and is cleared again on the next success.
      Its `code` comes from the one error vocabulary this API uses, narrowed for
      drain delivery to this set:


      | `code` | Means |

      | --- | --- |

      | `DEADLINE_EXCEEDED` | Your destination did not respond before the
      delivery timeout. |

      | `UNAVAILABLE` | Your destination could not be reached (DNS, TLS, or
      connection failure) or answered `5xx` — or redirected, which is never
      followed. |

      | `RESOURCE_EXHAUSTED` | Your destination answered `429`. |

      | `UNAUTHORIZED` | Your destination answered `401`. |

      | `PERMISSION_DENIED` | Your destination answered `403`. |

      | `NOT_FOUND` | Your destination answered `404`. |

      | `INVALID_ARGUMENT` | Your destination answered some other `4xx`. |

      | `FAILED_PRECONDITION` | The URL is not a permitted log drain target — it
      resolves to a blocked address range. Fix the URL; retrying will not help.
      |

      | `INTERNAL` | Hedra could not prepare the batch. |

      | `UNKNOWN` | No classification is available, including for drains that
      last failed before this field became structured. |


      `retryable` describes the condition, not what Hedra did: every failed
      batch is requeued until the drain auto-disables, so it answers whether
      fixing the destination and re-enabling is likely to help. The `message` is
      a fixed summary — your destination's URL, headers, credentials, and
      response body are never echoed back, so treat your own logs, not this
      field, as the record of what your destination returned.


      `disabled_reason` answers a different question and stays its own small
      vocabulary: `consecutive_failures` when five batches in a row failed,
      `disabled_by_user` when you turned the drain off.
paths:
  /models/seedream-45:
    post:
      tags:
        - Run a model
      summary: Run Seedream 4.5 (seedream-45)
      description: >-
        Latest Seedream with enhanced detail, refined composition, and
        multi-reference image support for professional visuals.


        Submits an asynchronous job and returns `202` with a job id. Fetch the
        result at `GET /v3/jobs/{job_id}` — each item in its `outputs[]` follows
        the `OutputItem` schema — or track progress via `GET
        /v3/jobs/{job_id}/status` / the SSE stream at `GET
        /v3/jobs/{job_id}/stream`.
      operationId: submit_seedream_45
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitBody_seedream_45'
      responses:
        '202':
          description: >-
            Accepted. The job runs asynchronously; poll `status_url` /
            `result_url` from the ack.
          headers:
            Location:
              description: >-
                Path of the job resource this submit created — the same value as
                the ack's `result_url`.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponse'
        '400':
          description: Invalid or failed-validation request.
          content:
            application/json:
              examples:
                field_validation:
                  summary: A submit `input` field failed schema validation
                  value:
                    error:
                      code: INVALID_ARGUMENT
                      message: 2 input fields are invalid; see details.
                      retryable: false
                      param: input.aspect_ratio
                      details:
                        - field: input.aspect_ratio
                          message: >-
                            input.aspect_ratio: Input should be '16:9', '9:16'
                            or '1:1'
                          reason: enum
                        - field: input.duration_ms
                          message: >-
                            input.duration_ms: Input should be greater than or
                            equal to 1000
                          reason: minimum
                expired_file_handle:
                  summary: >-
                    A `POST /v3/files` handle was submitted past its
                    `expires_at`
                  value:
                    error:
                      code: INVALID_ARGUMENT
                      message: >-
                        input.images[0]: the POST /v3/files url expired at
                        2026-07-28T23:31:12+00:00. Upload the file again and
                        submit with the fresh url.
                      retryable: false
                      param: input.images[0]
                      details:
                        - field: input.images[0]
                          message: >-
                            input.images[0]: the POST /v3/files url expired at
                            2026-07-28T23:31:12+00:00. Upload the file again and
                            submit with the fresh url.
                          reason: expired
                unsupported_model:
                  summary: The model exists but isn't wired to v3 yet
                  value:
                    error:
                      code: FAILED_PRECONDITION
                      message: Model 'some-model' (VIDEO) is not supported on v3 yet.
                      retryable: false
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: 'Authentication failed: missing, malformed, or invalid credentials.'
          content:
            application/json:
              examples:
                missing_credentials:
                  summary: No credential on the request
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: >-
                        Missing credentials. Send 'Authorization: Key
                        <key_id>:<secret>' or 'Authorization: Bearer <token>'.
                      retryable: false
                invalid_key:
                  summary: Credential present but not valid
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Invalid API key.
                      retryable: false
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient scope or plan.
          content:
            application/json:
              examples:
                no_api_access:
                  summary: Authenticated, but the plan excludes API access
                  value:
                    error:
                      code: PERMISSION_DENIED
                      message: This key's plan does not include API access.
                      retryable: false
                missing_scope:
                  summary: Key lacks a scope the operation requires
                  value:
                    error:
                      code: PERMISSION_DENIED
                      message: 'Key is missing required scope(s): jobs:write.'
                      retryable: false
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Resource not found.
          content:
            application/json:
              examples:
                job_not_found:
                  summary: Unknown job id, or not visible to the caller
                  value:
                    error:
                      code: NOT_FOUND
                      message: Job not found.
                      retryable: false
                model_not_found:
                  summary: No such model in the catalog
                  value:
                    error:
                      code: NOT_FOUND
                      message: Model 'some-model' not found.
                      retryable: false
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Moderation refused the request's content.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                moderation_failed:
                  summary: A reference image was refused by moderation
                  value:
                    error:
                      code: MODERATION_FAILED
                      message: Image failed moderation.
                      retryable: false
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              examples:
                rate_limited:
                  summary: Per-key request rate limit exceeded
                  value:
                    error:
                      code: RESOURCE_EXHAUSTED
                      message: Request rate limit exceeded. Retry after 30s.
                      retryable: true
                      retry_after: 30
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
            X-RateLimit-Limit:
              description: Request budget for the current window.
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            X-RateLimit-Reset:
              description: Unix epoch second the window resets at.
              schema:
                type: integer
        '500':
          description: Internal error.
          content:
            application/json:
              examples:
                internal:
                  summary: Unexpected server error; internals are never leaked
                  value:
                    error:
                      code: INTERNAL
                      message: An unexpected error occurred
                      retryable: false
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - KeyAuth: []
        - BearerToken: []
components:
  schemas:
    SubmitBody_seedream_45:
      type: object
      properties:
        input:
          $ref: '#/components/schemas/Input_seedream_45'
        webhook:
          type:
            - string
            - 'null'
          format: uri
          description: URL to receive a signed completion webhook.
          default: null
        idempotency_key:
          type:
            - string
            - 'null'
          description: >-
            Replays the original ack for a retried submit instead of enqueueing
            a duplicate job.
          default: null
      required:
        - input
    SubmitResponse:
      properties:
        job_id:
          description: This job's id — server-issued, and opaque.
          title: Job Id
          type: string
        model:
          type: string
          title: Model
        status:
          $ref: '#/components/schemas/JobStatus'
        status_url:
          description: >-
            Path of this job's status monitor: poll GET /v3/jobs/{job_id}/status
            for status, progress, and an estimate.
          title: Status Url
          type: string
        result_url:
          description: >-
            Path of the job resource itself: GET /v3/jobs/{job_id} returns the
            result envelope, including the outputs once it completes. Also the
            value of this response's `Location` header.
          title: Result Url
          type: string
        estimated_completion_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          default: null
          description: >-
            ISO-8601 instant this job is estimated to finish. Null when no
            estimate exists for the model yet; poll GET /v3/jobs/{job_id}/status
            for a refreshed one.
          title: Estimated Completion At
      required:
        - job_id
        - model
        - status
        - status_url
        - result_url
      title: SubmitResponse
      type: object
    ErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ErrorEnvelope'
        trace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Trace Id
      type: object
      required:
        - error
      title: ErrorResponse
      description: 'Top-level error body: ``{"error": {...}}`` plus a debug id.'
    Input_seedream_45:
      description: Model-specific inputs for `seedream-45`.
      type: object
      properties:
        prompt:
          description: Generation prompt.
          pattern: '[^\s\x1C-\x1F]'
          type: string
        num_outputs:
          default: 1
          type: integer
          enum:
            - 1
        enhance_prompt:
          default: false
          description: >-
            Rewrite the prompt before generation. An LLM expands it into a
            fuller description and the model receives that text instead of the
            submitted one; the result's `prompt` reports what ran.
          type: boolean
        aspect_ratio:
          description: Output aspect ratio.
          enum:
            - '1:1'
            - '1:3'
            - '2:3'
            - '3:1'
            - '3:2'
            - '3:4'
            - '4:3'
            - '16:9'
            - '9:16'
            - '10:16'
            - '16:10'
          type: string
        resolution:
          description: Output resolution.
          enum:
            - 1440p (2K QHD)
            - 2160p (4K UHD)
          type: string
        images:
          description: Images to edit or blend.
          items:
            discriminator:
              propertyName: source
            oneOf:
              - additionalProperties: false
                description: >-
                  A file uploaded via POST /v3/files, referenced by the returned
                  url.
                properties:
                  source:
                    type: string
                    enum:
                      - url
                  url:
                    description: A URL returned by POST /v3/files.
                    format: uri
                    type: string
                required:
                  - source
                  - url
                type: object
              - additionalProperties: false
                description: An existing asset you own, referenced by its id.
                properties:
                  source:
                    type: string
                    enum:
                      - asset
                  asset_id:
                    description: >-
                      The asset's id (`asset_<uuid>`), as issued by the server —
                      a completed generation publishes one per output as
                      `outputs[].asset_id`. Do not construct one or derive it
                      from any other id.
                    type: string
                required:
                  - source
                  - asset_id
                type: object
          maxItems: 14
          media_type: image
          minItems: 1
          role: reference
          type: array
          max_file_size_bytes: 30000000
      required:
        - aspect_ratio
        - prompt
        - resolution
      additionalProperties: false
      anyOf:
        - required:
            - aspect_ratio
            - images
            - prompt
            - resolution
        - required:
            - aspect_ratio
            - prompt
            - resolution
          properties:
            images: false
    JobStatus:
      type: string
      enum:
        - IN_QUEUE
        - IN_PROGRESS
        - COMPLETED
        - FAILED
      title: JobStatus
      description: The four states a job can be in.
    ErrorEnvelope:
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          title: Message
        retryable:
          type: boolean
          title: Retryable
          default: false
        retry_after:
          anyOf:
            - type: integer
            - type: 'null'
          title: Retry After
        param:
          anyOf:
            - type: string
            - type: 'null'
          title: Param
        details:
          anyOf:
            - items:
                $ref: '#/components/schemas/FieldError'
              type: array
            - type: 'null'
          title: Details
        replaced_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Replaced By
      type: object
      required:
        - code
        - message
      title: ErrorEnvelope
    ErrorCode:
      type: string
      enum:
        - UNKNOWN
        - INVALID_ARGUMENT
        - NOT_FOUND
        - GONE
        - ALREADY_EXISTS
        - ALREADY_IN_PROGRESS
        - UNAUTHORIZED
        - PERMISSION_DENIED
        - INSUFFICIENT_BALANCE
        - MODERATION_FAILED
        - FAILED_PRECONDITION
        - DEADLINE_EXCEEDED
        - RESOURCE_EXHAUSTED
        - UNAVAILABLE
        - INTERNAL
      title: ErrorCode
      description: >-
        Universal error code space. Modeled after gRPC status codes.


        Every exception in the system carries an ErrorCode. SDK clients map

        3rd-party errors to these codes at the lowest level. Retry logic,

        HTTP status mapping, and OTEL metrics all key off this enum.


        NOTE: The semantic meaning of these error codes is roughly mapped from
        the
            semantic of the gRPC status codes: see
            https://grpc.io/docs/guides/status-codes/ for details.
    FieldError:
      properties:
        field:
          type: string
          title: Field
        message:
          type: string
          title: Message
        reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Reason
        allowed:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          title: Allowed
      type: object
      required:
        - field
        - message
      title: FieldError
      description: >-
        One field-level validation problem in a request body.


        `field` is the dotted path to the offending input (e.g.
        `input.resolution`);

        `allowed` lists the accepted values when the field is an enum, so a
        caller

        can fix it without re-fetching the model schema.
  securitySchemes:
    KeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'Primary scheme: `Authorization: Key <key_id>:<secret>`.'
    BearerToken:
      type: http
      scheme: bearer
      description: >-
        `Authorization: Bearer <credential>` — accepts an API key
        (`<key_id>:<secret>`) or an ephemeral browser token minted via `POST
        /tokens` (inherits the minting key's scopes and workspace).

````