> ## 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.

# Retrieve a run

> GET /v1/workflows/runs/{id}

The run and every step it recorded — status, output, credits, duration.

This is the polling half of `mode: "async"`. A run that is still going reports `queued` or `running`; the step list fills in as it goes, so this doubles as progress.


## OpenAPI

````yaml openapi.json GET /v1/workflows/runs/{id}
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/{id}:
    get:
      tags:
        - Workflows
      summary: Get workflow run by id
      description: >-
        The whole run, whatever its mode and whether or not it has finished —
        this is the poll target for `mode: "async"`, and the way to read back a
        `sync` or `stream` run after the fact. Free; reads nothing off the
        wallet. A run still in flight comes back with `status: "running"` (or
        `"queued"`) and the step attempts recorded so far, including ones still
        `running` themselves. COMPOSITION: `stepRuns[].childRunIds` names the
        runs a `foreach` or `sub_pipeline` step spawned — always present, `[]`
        when there are none — and each of those ids is itself readable here,
        which is how a nested run is walked. A child run is NOT nested inside
        this payload. ACCESS: a run is readable when the caller can see its
        workflow, or when the run itself was shared with them. Both end at 404
        when they do not hold, and a run whose workflow was soft-deleted is a
        404 for everyone, share or not. A run of an INLINE definition belongs to
        no workflow, so the workspace scope of the key is the whole rule for it.
      operationId: PipelineRunsController_getById[0]
      parameters:
        - name: id
          required: true
          in: path
          description: Run UUID.
          schema:
            example: 3c9a7e51-8b24-4f0d-9a17-6e2b5c4d8a03
            type: string
      responses:
        '200':
          description: The run, its steps, and what it cost.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PipelineRunResultDto'
        '401':
          description: Unauthorized — invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '404':
          description: >-
            No run with this id in this workspace, or it is not visible to this
            caller — deliberately the same answer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
      security:
        - ApiKey: []
components:
  schemas:
    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
    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_***'

````