---
title: "Append an ad-hoc step to a job"
method: POST
path: "/api/v2/jobs/{job_id}/extra-steps"
tags: ["jobs"]
---

# Append an ad-hoc step to a job

`POST /api/v2/jobs/{job_id}/extra-steps`

Append an ad-hoc step to a running job.

Only allowed when the workflow's
``allow_adhoc_step`` flag is true. The new
step appends at ``max(existing sort_order) + 1`` across both
workflow steps and prior extras, so the UI's sort order is stable.

Like :func:`submit_job_step`, this is capture-only: the metadata
arrives as ``multipart/form-data`` form fields, the image as a file
upload (written straight to object storage), and the background
analysis runs at /analyse time.

## Path parameters

- `job_id` string, required

## Response `201`

Successful Response

- JobRead — Full job payload including the latest result per step. `steps` is the workflow's steps as they were at the moment the job was started -- not the current workflow. Clients should render the run UI from this list so edits to the workflow after the job started never change historical runs.
  - `id` string, required — Stable job id.
  - `workflow_id` string, required — Id of the workflow this job is running against.
  - `workflow_name` string, nullable — Display name of the workflow this job runs against. Null when the workflow row is unavailable. Additive convenience field so clients need not resolve the name from workflow_id separately.
  - `user_id` string, required — Id of the user who started the job.
  - `is_adhoc` boolean — True when this job runs against a synthetic ad-hoc workflow (no predefined steps). Clients render the workflow column as an ad-hoc placeholder rather than the synthetic workflow name.
  - `organization_id` string, nullable — Owning organization id. Null for personal jobs.
  - `status` 'in_progress' | 'analysing' | 'completed' | 'completed_with_errors' | 'abandoned', required
  - `result` 'pending' | 'passed' | 'failed' — The pass/fail outcome of a job, distinct from its lifecycle status. Lifecycle (``JobLifecycleStatus``) answers "did the run finish, and did any step technically error?". Result answers "did the run pass its checks?". The two are independent: a run can finish cleanly (``COMPLETED``) yet still ``FAILED`` because too few steps passed for the workflow's pass policy. The pass policy is driven by ``allow_step_failure``: - Off: strict. Any errored or failing step makes the result ``FAILED``. - On: threshold. Failures are tolerated until the percentage of evaluable steps that failed exceeds ``fail_threshold_percent``; errored steps count as failures. - ``PENDING``: not yet determined (job still in progress / analysing). - ``PASSED``: the workflow's pass policy is satisfied. - ``FAILED``: the workflow's pass policy is not satisfied.
  - `started_at` string, date-time, required — When the job was created.
  - `analysing_started_at` string, date-time, nullable — When the job transitioned to ANALYSING. Null while still IN_PROGRESS. Used by the stale-analysis recovery sweep to time out crashed analysis tasks.
  - `completed_at` string, date-time, nullable — When the job was completed. Null while in progress.
  - `steps` WorkflowStepRead[] — The workflow's steps as they were at job start.
    - `id` string, required — Stable id of this step. Useful as a React key on edit forms.
    - `step_key` string, nullable — Stable logical-step identity carried across snapshot versions. Reference files attach to this key, so it round-trips: the editor echoes it back on the step-replace save to keep a step's files. Null on job reads, which never edit steps.
    - `sort_order` integer, required — Zero-based position of this step within the workflow.
    - `name` string, required — Human-readable step title shown to the operator.
    - `workflow_note` string, nullable — Optional guidance shown to the operator before they capture the image.
    - `reference_file_paths` string[], nullable — Optional reference files (blob-storage object paths) shown to the operator and fed to the agent. Populated in workflow contexts; null on the job-read endpoint (GET /jobs/{id}), where only ``reference_file_urls`` is returned.
    - `reference_file_urls` string[], nullable — Short-lived signed download URLs for the step's reference files. Best-effort: paths that fail to sign are omitted, so this is not positionally aligned with the underlying paths. Populated only on terminal-status job reads (when the results page renders its report); null in workflow contexts and on in-progress/analysing job polls.
    - `step_type` 'vision_agent' | 'selection' — Discriminator for a workflow step's strategy. Every registered value has a matching :class:`StepTypeSpec` subclass in :mod:`app.workflow_steps` that owns the strategy's config, input, output, submit payload and handlers. Adding a value here is one of the three edits required to register a new step type; see :mod:`app.workflow_steps.registry` for the full checklist.
    - `step_config` object — Per-type configuration for this step. For ``vision_agent`` the blob carries ``agent_id`` and the agent's structured input parameters. For ``selection`` it carries ``options`` and ``use_selection_as_criteria``. Empty ``{}`` when the type has no configuration.
    - `agent_id` string, nullable — Deprecated. Prefer ``step_type`` + ``step_config``. Mirrored from ``step_config.agent_id`` for vision-agent steps; the selection sentinel is emitted for legacy clients on selection steps.
    - `agent_parameters` object, nullable — Deprecated. Prefer ``step_config``. Mirrored from ``step_config`` with the ``agent_id`` key stripped for vision-agent steps.
    - `pass_criteria` PassCriteria
      - `combinator` 'and' | 'or', required — How to aggregate rule outcomes. ``and`` requires every rule to pass; ``or`` requires at least one.
      - `rules` PassCriteriaRule[], required — One or more rules to evaluate against the agent response.
        - `path` string, required — Dotted path into agent response.
        - `op` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists' | 'length_gte' | 'length_lt', required
        - `value` unknown
    - `allow_multiple_images` boolean — Whether a capture step accepts more than one image per submission (up to the server-enforced cap). Meaningful only for ``vision_agent`` steps; ignored for ``selection`` steps, which never capture images.
  - `allow_adhoc_step` boolean — Whether ad-hoc steps may be appended to this job via `POST /jobs/{id}/extra-steps`. Mirrors the backing workflow's `allow_adhoc_step`. Clients should gate the add-step UI on this flag rather than fetching the workflow, which is unavailable for hidden ad-hoc workflows.
  - `allow_out_of_order_steps` boolean — When True, steps may be captured in any order. When False, they must be captured in ascending sort_order. Mirrors the backing workflow's `allow_out_of_order_steps` so clients can gate step selection without fetching the workflow.
  - `allow_step_skip` boolean — When True, the job may be finalised (analysed) with steps left uncaptured. When False, every step must be captured first. Mirrors the backing workflow's `allow_step_skip` so clients can relax the Analyse gate without fetching the workflow.
  - `extra_steps` JobExtraStepRead[] — Ad-hoc steps appended to this job via `POST /jobs/{id}/extra-steps`. Empty for jobs started against a workflow with `allow_adhoc_step=false`.
    - `id` string, required — Stable id of this extra step.
    - `sort_order` integer, required — Zero-based position after all workflow steps.
    - `name` string, required — Human-readable name shown to the operator.
    - `workflow_note` string, nullable — Optional guidance copy for the operator.
    - `reference_file_paths` string[], nullable — Optional reference files (blob-storage object paths). Null on the job-read endpoint (GET /jobs/{id}), where only ``reference_file_urls`` is returned.
    - `reference_file_urls` string[], nullable — Short-lived signed download URLs for the step's reference files. Best-effort: paths that fail to sign are omitted, so this is not positionally aligned with the underlying paths. Populated only once the job reaches a terminal status (when the results page renders its report); null on the in-progress/analysing polls.
    - `step_type` 'vision_agent' | 'selection' — Discriminator for a workflow step's strategy. Every registered value has a matching :class:`StepTypeSpec` subclass in :mod:`app.workflow_steps` that owns the strategy's config, input, output, submit payload and handlers. Adding a value here is one of the three edits required to register a new step type; see :mod:`app.workflow_steps.registry` for the full checklist.
    - `step_config` object — Per-type configuration for this step. Empty ``{}`` when the type has none.
    - `agent_id` string, nullable — Deprecated. Mirrored from ``step_config.agent_id`` for vision-agent steps; carries the selection sentinel for legacy selection clients.
    - `agent_parameters` object, nullable — Deprecated. Mirrored from ``step_config`` (with ``agent_id`` stripped for vision-agent steps).
    - `pass_criteria` PassCriteria
      - `combinator` 'and' | 'or', required — How to aggregate rule outcomes. ``and`` requires every rule to pass; ``or`` requires at least one.
      - `rules` PassCriteriaRule[], required — One or more rules to evaluate against the agent response.
        - `path` string, required — Dotted path into agent response.
        - `op` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists' | 'length_gte' | 'length_lt', required
        - `value` unknown
    - `allow_multiple_images` boolean — Whether this ad-hoc capture step accepts more than one image per submission (up to the server-enforced cap). Meaningful only for ``vision_agent`` steps; ignored for ``selection`` steps, which never capture images.
    - `is_extra` boolean — Always true; lets the UI render ad-hoc chips with a single field check.
  - `step_results` JobStepResultRead[] — Latest result per step (one entry per submitted step).
    - `sort_order` integer, required — Zero-based position of the step in the workflow.
    - `workflow_step_id` string, nullable — Id of the workflow step that this result is for. Null when the result belongs to an ad-hoc extra step added during the job -- `extra_step_id` will be set instead.
    - `extra_step_id` string, nullable — Id of the ad-hoc extra step, if this result is for one.
    - `agent_id` string, nullable — Id of the agent that produced this result. Null for non-agent step types (e.g. an operator-picked selection).
    - `step_input` unknown
    - `step_output` unknown
    - `agent_response` unknown
    - `submitted_at` string, date-time, required — When this (most-recent) result was submitted.
    - `analysis_status` 'pending' | 'running' | 'completed' | 'errored' — Per-step analysis lifecycle for the deferred-analysis flow (VA-415). A capture-only POST creates a step row in `pending`. The background completion task transitions it to `running`, then to `completed` or `errored`. The agent verdict (pass/fail) is a separate field on the same row and is only meaningful when `analysis_status == completed`.
    - `passed` boolean, nullable — Verdict from the step's pass-criteria evaluation. Null when the step is informational (no criteria configured) or has not yet been analysed.
    - `pass_explanation` string, nullable — Human-readable summary of which criteria passed / failed.
    - `error_message` string, nullable — Truncated error message when analysis_status == ERRORED.
    - `image_available` boolean — True when an image was captured for this result and can be fetched via `GET /jobs/{id}/steps/{sort_order}/image-url` (which returns a short-lived signed URL).
    - `capture_batch_index` integer — Zero-based position of this image within its capture batch, defines display order.
    - `capture_batch_id` string, nullable — Id grouping the rows submitted together for one step. Null on legacy rows written before batching existed.
    - `original_filename` string, nullable — Original name of the uploaded file, sanitised to its basename. Null when the client never sent one (e.g. a mobile camera capture).

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/tiliter/apis/tiliter-vision-ai-public-api.md) · [All operations](https://skmtc.net/tiliter/apis/tiliter-vision-ai-public-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/tiliter/tiliter-vision-ai-public-api/revisions/30b6f7f148f8/schema)
