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

# Run a workflow

> POST /v1/workflows/runs

The one endpoint that executes anything. Send a `definition` inline, or a `pipeline_id` for one you stored.

`mode` decides how you get the result: `sync` (default) waits and returns the finished run, `stream` returns SSE progress events, `async` returns a run id immediately and you poll or take a webhook.

Missing or wrongly-typed inputs are reported **all at once** with `invalid_pipeline_input`, before anything is executed or charged. Send an `Idempotency-Key` header to make a retry safe.

See [Running a workflow](/workflows/running) for the modes, resuming a failed run, and the SSE contract.


## OpenAPI

````yaml openapi.json POST /v1/workflows/runs
openapi: 3.0.0
info:
  title: Infery Gateway
  description: >-
    Infery Inference Gateway — OpenAI-compatible API for LLMs, embeddings,
    images, audio, and video
  version: '1.0'
  contact: {}
servers:
  - url: https://api.infery.ai
    description: Production
  - url: http://localhost:3001
    description: Local
security: []
tags: []
paths:
  /v1/workflows/runs:
    post:
      tags:
        - Workflows
      summary: >-
        Run a workflow (sync default; mode=async returns job id; mode=stream
        returns SSE)
      description: >-
        Terminal events for a run are delivered to the workspace notification
        channels subscribed to the event type (configure them at
        settings/notifications); a workspace with no matching subscription gets
        the default owner email instead. There is no per-run webhook override —
        the removed `webhook_url` field never routed anything (#525). ORDERING:
        a run may send more than one terminal event — a run reaped as failed
        whose worker then finishes sends a second, corrected event. Terminal
        events are delivered by a retrying, multi-worker queue and may arrive
        out of order, so order them by payload, not by arrival: for a given
        metadata.runId, a terminal event carrying metadata.correctsPriorStatus
        SUPERSEDES any terminal event that does not, regardless of arrival
        order. Once you have applied a correction for a run, discard any later
        terminal event for that run lacking the field. Full payload contract:
        docs/guides/pipeline-run-webhooks.md


        THIS SPENDS CREDITS. Every step settles against the workspace wallet as
        it dispatches; there is no pre-flight balance gate and no cap derived
        from `POST /v1/workflows/estimate`, which is a quote. A run that fails
        half way has still paid for the steps that ran — and does not have to
        pay for them twice: send its id as `resume_from_run_id` to continue it
        from the step that failed. To re-run part of a workflow that already ran
        — because a prompt, a model or a parameter changed — send
        `rerun_from_step_id` alongside it: that step and every step that reads
        its output run again, and nothing else is dispatched or billed.


        THE RESPONSE DEPENDS ON `mode`, and the three shapes have nothing in
        common:

        - `sync` (default) — the request is held open until the run reaches a
        terminal state, then returns the whole run. A run that FAILS still
        returns 201 with `status: "failed"`; only a refusal before the run
        starts is a 4xx. Note two differences from `GET /v1/workflows/runs/{id}`
        on the same run: `input` is not echoed here, and `error.stepId` is
        present here and absent there.

        - `async` — returns `{ id, status: "queued", createdAt }` immediately;
        poll `GET /v1/workflows/runs/{id}`.

        - `stream` — returns `text/event-stream`, NOT JSON. Named SSE events:
        `pipeline.started`, `step.started`, `step.delta`, `step.completed`,
        `step.failed`, `step.skipped`, `foreach.started`, `iteration.started`,
        `iteration.succeeded`, `iteration.failed`, `foreach.completed`, and one
        of `pipeline.completed` / `pipeline.failed`, followed by a final `data:
        [DONE]`. A `: ping` comment is written when nothing else has been sent
        for 15s. A `sub_pipeline` child's own events are not proxied into the
        parent stream.


        Supply EXACTLY ONE of `definition` or `pipeline_id`. Running a stored
        workflow requires only VISIBILITY of it, not edit access — a read share
        is permission to run. Sending both is a 400 (`ambiguous_definition`),
        sending neither is a 400 (`missing_definition`).
      operationId: PipelineRunsController_run[0]
      parameters:
        - name: Idempotency-Key
          required: false
          in: header
          description: >-
            Replay guard, scoped to the workspace and never expired: a second
            run with a key already seen returns the FIRST run instead of
            executing again, in every mode. In `stream` mode a completed run is
            replayed as a synthetic event sequence, and a run still in flight is
            a 409 (`idempotency_in_progress`). The value is stored in a
            120-character column and is not length-checked before that, so keep
            keys short.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunPipelineDto'
      responses:
        '201':
          description: >-
            One of two JSON shapes, by `mode`: the completed run (`sync`), or
            the queued run's identity (`async`). With `mode: "stream"` the
            response is `text/event-stream` — a sequence of the named SSE events
            listed in the operation description above, matching NEITHER JSON
            schema below. OpenAPI 3.0 has no way to express a dozen
            distinctly-shaped named event types, so the `text/event-stream`
            entry is deliberately just `{type: "string"}`: an honest admission
            that this document cannot describe that body, not a claim that it is
            JSON or that it matches either schema below.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PipelineRunResultDto'
                  - $ref: '#/components/schemas/AsyncRunAcceptedDto'
            text/event-stream:
              schema:
                type: string
        '400':
          description: >-
            Refused before anything ran, so nothing was billed and no run row
            exists: both or neither of `definition`/`pipeline_id`; a definition
            the schema or validator refuses; run input that fails the workflow's
            declared `inputs`; or an `only_step_id` that names no step, is
            missing an upstream result, has no `pipeline_id`, or was sent with
            `mode: "async"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '401':
          description: Unauthorized — invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '403':
          description: >-
            The stored workflow references a `sub_pipeline` its author can no
            longer see (`sub_pipeline_no_longer_visible`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '404':
          description: >-
            The `pipeline_id`/`pipeline_version` does not exist or is not
            visible to this caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '409':
          description: >-
            Streaming replay of an `Idempotency-Key` whose run is still in
            flight (`idempotency_in_progress`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
      security:
        - ApiKey: []
components:
  schemas:
    RunPipelineDto:
      type: object
      properties:
        pipeline_id:
          type: string
          description: Reusable workflow id (provide either this OR definition)
        pipeline_version:
          type: integer
          description: Specific version of pipeline_id (defaults to latest)
        definition:
          type: object
          additionalProperties: true
          description: Inline workflow definition (use instead of pipeline_id)
        input:
          type: object
          additionalProperties: true
          description: Top-level inputs referenced as ${input.X}
        mode:
          type: string
          enum:
            - sync
            - async
            - stream
          description: >-
            Execution mode (default sync). When "stream" the response is
            text/event-stream.
        only_step_id:
          type: string
          description: >-
            Run ONLY this step, using the outputs its upstream steps recorded on
            earlier runs. Requires pipeline_id, and every step it reads must
            already have a succeeded result. Not available with mode=async.
        resume_from_run_id:
          type: string
          description: >-
            CONTINUE THIS FAILED RUN instead of starting over: the steps it
            recorded as succeeded are reused, and only the step that failed and
            everything downstream of it runs again. A reused step is not
            dispatched and not billed. Send `definition` (or `pipeline_id`)
            alongside to fix the step that failed — swap the model, change the
            params, replace the step; send neither and the run is resumed
            against the definition it originally executed. REFUSED, before
            anything is billed, when: the run did not fail
            (`resume_run_not_failed`); the edit changes a step that already
            produced a result, which is a new run rather than a resume
            (`resume_upstream_definition_changed`); `input` differs from the
            input that run used (`resume_input_changed`); a reused step's
            artifact has since been deleted (`resume_artifact_expired`); the run
            did not record which version of the workflow it executed and the
            workflow has been saved since (`resume_source_definition_unknown`);
            or with `mode: "async"` (`resume_async_unsupported`) or with
            `only_step_id` (`resume_with_only_step`). A step with no recorded
            result — including one whose result was lost — simply runs again.
        rerun_from_step_id:
          type: string
          description: >-
            RUN THIS STEP AND EVERYTHING THAT DEPENDS ON IT, reusing the results
            of the run named by `resume_from_run_id` — which may be in ANY
            finished state (succeeded, failed or cancelled), not only failed.
            The step runs, every step that transitively reads its output runs,
            and every other step is reused from that run: not dispatched, not
            billed. DEPENDENCY-BASED, NOT POSITIONAL: a step declared after it
            that does not read it (directly or through another step) could not
            have a different result, so it is neither re-run nor re-billed. Send
            an edited `definition` (or `pipeline_id`) alongside to change what
            the step does — unlike a bare resume, an edit to a step that already
            produced a result is NOT refused here: it widens the set that runs
            again to include that step and everything below it, and the credit
            hold covers the wider set. A STEP WITH NO RECORDED RESULT ALWAYS
            RUNS AGAIN, which on a CANCELLED run means work that was in flight
            when it stopped may have been charged already and is charged again.
            REFUSED, before anything is billed, when: sent without
            `resume_from_run_id` (`rerun_from_step_without_run`); the run has
            not finished (`rerun_run_not_finished`); no step of the definition
            has this id (`rerun_from_step_unknown`); or with `mode: "async"`
            (`rerun_async_unsupported`). `resume_input_changed`,
            `resume_artifact_expired` and `resume_source_definition_unknown`
            apply here too.
      required:
        - input
    PipelineRunResultDto:
      type: object
      properties:
        id:
          type: string
          description: Run UUID.
          example: 3c9a7e51-8b24-4f0d-9a17-6e2b5c4d8a03
        status:
          type: string
          enum:
            - queued
            - running
            - succeeded
            - failed
            - cancelled
            - partial
          description: >-
            Terminal statuses are `succeeded`, `failed` and `cancelled`.
            `queued`/`running` are only ever seen by reading a run that has not
            finished. `partial` exists in the enum and no writer in this repo
            produces it — do not build on it.
          example: succeeded
        attempt:
          type: number
          description: >-
            Current run-level attempt, 1-based. Only an `async` run can exceed
            1; `sync` and `stream` runs never retry at the run level.
          example: 1
        maxAttempts:
          type: number
          description: >-
            Run-level attempts configured, from the definition's
            `retry.max_attempts` clamped to 1..5. 1 for `sync` and `stream`
            runs.
          example: 1
        input:
          type: object
          additionalProperties: true
          description: >-
            The input the run was given, AFTER declared defaults were applied.
            Returned by `GET /v1/workflows/runs/{id}` and by an idempotent
            replay; ABSENT from the response of a fresh sync run, which is built
            from the runner's result rather than the row. `{}` is a real answer
            (the workflow declares inputs and the caller sent none) — the field
            is omitted, never null, when there is nothing.
        output:
          type: object
          additionalProperties: true
          description: >-
            The run's output, resolved from the definition's `output` block
            against the steps' results. Absent for a run that failed or was
            cancelled before producing one.
        error:
          description: Present only for a failed run. A cancelled run carries no error.
          allOf:
            - $ref: '#/components/schemas/RunErrorDto'
        creditsUsed:
          type: number
          description: >-
            Credits actually settled for this run, summed from the per-step
            settles. This is real money spent — it is NOT the estimate, and it
            is not capped by one: `POST /v1/workflows/estimate` is a quote, and
            the `credits_reserved` figure the run row carries is written but
            read by nothing.
          example: 4.128
        stepRuns:
          description: >-
            One entry per step ATTEMPT. Ordered by step id then attempt on `GET
            /v1/workflows/runs/{id}` — LEXICOGRAPHIC, not execution order —
            while a fresh sync run returns them in the order the runner executed
            them. Reading a run that is still `running` returns the steps
            written so far, including ones still `running` themselves.
          type: array
          items:
            $ref: '#/components/schemas/StepRunResultDto'
        durationMs:
          type: number
          description: >-
            Wall-clock duration of the run in milliseconds. 0 when the row never
            recorded one.
          example: 8421
        createdAt:
          type: string
          description: ISO-8601 timestamp the run was created.
          example: '2026-05-04T09:15:22.113Z'
      required:
        - id
        - status
        - attempt
        - maxAttempts
        - creditsUsed
        - stepRuns
        - durationMs
        - createdAt
    AsyncRunAcceptedDto:
      type: object
      properties:
        id:
          type: string
          description: >-
            Run UUID. Poll it with `GET /v1/workflows/runs/{id}`; there is no
            other handle to the job.
          example: 3c9a7e51-8b24-4f0d-9a17-6e2b5c4d8a03
        status:
          type: string
          enum:
            - queued
          description: >-
            Always `queued`, including on an idempotent replay — this response
            reports that the run EXISTS, not what it is doing now. A replay of a
            key whose run has already finished still says `queued`; read the run
            to learn its real status.
          example: queued
        createdAt:
          type: string
          description: ISO-8601 timestamp the run row was created.
          example: '2026-05-04T09:15:22.113Z'
      required:
        - id
        - status
        - createdAt
    ErrorResponseDto:
      type: object
      properties:
        error:
          description: >-
            The error envelope. Every non-2xx response from this API has this
            shape, so a client can parse failures without branching on the
            endpoint.
          allOf:
            - $ref: '#/components/schemas/ErrorDetailDto'
      required:
        - error
    RunErrorDto:
      type: object
      properties:
        code:
          type: string
          description: >-
            Machine-readable failure code, as raised by the step executor or by
            the engine.
          example: model_call_failed
        message:
          type: string
          description: >-
            Human-readable failure message. Empty string when the row recorded a
            code but no message.
          example: upstream provider returned 502
        stepId:
          type: string
          description: >-
            The step that failed. Present on the response of a run that failed
            IN THIS REQUEST (`POST /v1/workflows/runs` in sync mode). ABSENT
            from `GET /v1/workflows/runs/{id}`: the run row stores only the code
            and message, so re-reading a failed run does not tell you which step
            failed — read `stepRuns[]` for that.
          example: summarise
      required:
        - code
        - message
    StepRunResultDto:
      type: object
      properties:
        id:
          type: string
          description: >-
            The STEP id from the definition — not a database row id. Repeats
            across entries when the step was retried.
          example: summarise
        type:
          type: string
          description: >-
            The step type from the definition (`model`, `media`, `http`,
            `foreach`, `parallel`, `sub_pipeline`, or a capability id such as
            `image.resize`).
          example: model
        status:
          type: string
          enum:
            - pending
            - running
            - succeeded
            - failed
            - skipped
            - cancelled
          description: >-
            `skipped` means the step did not execute — see `skippedReason`, and
            note its `output` is then `null` on purpose. `cancelled` is written
            by the cancel sweep over rows still `running`; a step may still
            settle `succeeded` after that, which is truthful rather than a race
            to fix.
          example: succeeded
        output:
          description: >-
            Whatever the step produced — the shape is the step type's, not a
            common one: an object for a model step, an array for `foreach` (one
            element per iteration), a Record keyed by branch id for `parallel`.
            `null` for a step skipped by a false condition. Absent when the step
            produced nothing (a failure).
          nullable: true
          oneOf:
            - type: string
            - type: number
            - type: boolean
            - type: object
              additionalProperties: true
            - type: array
              items: {}
        outputRef:
          type: string
          description: >-
            Handle for the artifact this step wrote, for binding downstream as
            `${steps.<id>.outputRef}`. Currently a `gs://<bucket>/<object>` URI
            (historically `file_id://<id>`) — an INTERNAL reference, not a
            fetchable URL. Published only by media and capability steps that
            produce a file; absent for transcription, for `sub_pipeline`, and
            for the container steps.
          example: >-
            gs://infery-prod-media/workspaces/7d1c.../pipeline-artifacts/9f2b....png
        error:
          description: Present only when this attempt failed.
          allOf:
            - $ref: '#/components/schemas/StepRunErrorDto'
        skippedReason:
          type: string
          enum:
            - condition_false
            - resumed_from_prior_attempt
            - not_selected
          description: >-
            Why the step was not executed on this run. `condition_false` — its
            `condition` evaluated false. `resumed_from_prior_attempt` — an async
            retry reused the output an earlier attempt recorded, OR (with
            `resume_from_run_id`) this run inherited the result the resumed run
            produced. `not_selected` — a one-step run (`only_step_id`) stood
            this step in with its last recorded output. READ IT ALONGSIDE
            `status`, not instead of it: a resumed run's inherited step is
            `succeeded` AND carries `resumed_from_prior_attempt`, which together
            mean "this result is part of this run, and this run did not produce
            it" — its `creditsUsed` is 0 because this run paid nothing for it.
          example: condition_false
        childRunIds:
          description: >-
            Child `pipeline_runs` this step spawned — `foreach` iterations, or
            the single invocation of a `sub_pipeline`. Always present, `[]` when
            the step spawned none. Each id is readable through `GET
            /v1/workflows/runs/{id}`.
          example: []
          type: array
          items:
            type: string
        creditsUsed:
          type: number
          description: >-
            Credits actually settled for this attempt. 0 for a skipped step, and
            for a step that failed before dispatch.
          example: 1.284
        durationMs:
          type: number
          description: >-
            Wall-clock duration of this attempt in milliseconds. 0 when the step
            never dispatched.
          example: 2143
        attempt:
          type: number
          description: 1-based attempt counter for this step within the run.
          example: 1
      required:
        - id
        - type
        - status
        - childRunIds
        - creditsUsed
        - durationMs
        - attempt
    ErrorDetailDto:
      type: object
      properties:
        message:
          type: string
          example: Model not found
          description: Human-readable error message
        type:
          type: string
          example: invalid_request_error
          description: Error category
          enum:
            - invalid_request_error
            - authentication_error
            - permission_error
            - quota_exceeded
            - rate_limit_error
            - server_error
        code:
          type: string
          example: model_not_found
          nullable: true
          description: >-
            Stable machine-readable error code. Branch on this rather than on
            `message`, which is prose and may be reworded.
        param:
          type: string
          example: model
          nullable: true
          description: >-
            Name of the request parameter that triggered the error. `null` when
            the error is not attributable to one field.
        job_id:
          type: string
          example: job_1hR9xTPZqK4mVLc2nJ7fY5wB
          description: >-
            Handle to work that is ALREADY RUNNING AND ALREADY BILLED, present
            on the few errors that carry one. When it is here, this is not a
            failure to retry — retrying pays twice. Collect the result from `GET
            /v1/images/jobs/{job_id}`, which serves every durable media job
            regardless of modality.


            Two situations produce it. A media generation that outruns the
            gateway's wait answers `504` with `code: "job_timeout"` and keeps
            working. And `POST /v1/audio/speech` answers **500** with `code:
            "artifact_unreadable"` when the speech was generated and settled but
            could not be read back from storage — the audio exists and is paid
            for; only this response failed.


            Declared here rather than per-endpoint because the rule is about the
            FIELD, not the status: if this is present, there is a paid-for
            result to collect. It was undeclared until now, so a client
            generated from this document could not see the one field that
            recovers money already spent.
      required:
        - message
        - type
        - code
        - param
    StepRunErrorDto:
      type: object
      properties:
        code:
          type: string
          description: >-
            Machine-readable failure code, as raised by the step executor or by
            the engine.
          example: model_call_failed
        message:
          type: string
          description: >-
            Human-readable failure message. Empty string when the row recorded a
            code but no message.
          example: upstream provider returned 502
      required:
        - code
        - message
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: Authorization
      description: 'API key in format: Bearer inf_***'

````