{"openapi":"3.1.0","info":{"title":"Tiliter Vision AI - Public API","description":"Public REST API for AI-powered vision agents and job workflows on the Tiliter Platform. Authenticate with an `X-API-Key` header.","version":"0.0.1"},"paths":{"/api/v2/workflows/tags":{"get":{"tags":["workflows"],"summary":"List the org's tag vocabulary","description":"Return the selectable tags for the active org.\n\nCombines the global prebaked vocabulary (``is_system=True``) with the\norg's own custom tags (``is_system=False``). Used to populate the tag\npicker on the workflow edit form.","operationId":"list_workflow_tags_api_v2_workflows_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowTagOption"},"title":"Response List Workflow Tags Api V2 Workflows Tags Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows":{"get":{"tags":["workflows"],"summary":"List workflows","description":"Return workflows visible to the caller.\n\nSystem workflows are visible to everyone. For org workflows, visibility\nis role-scoped: ADMIN and OWNER see every workflow in the active org.\nVIEWER and MEMBER see the workflows they own plus any in a group they\nbelong to; a member in the virtual \"All workflows\" group sees every org\nworkflow (VA-499).","operationId":"list_workflows_api_v2_workflows_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"description":"Page size (max 200).","title":"Limit"},"description":"Page size (max 200)."},{"name":"is_system","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"If true, only system workflows. If false, only user workflows.","title":"Is System"},"description":"If true, only system workflows. If false, only user workflows."},{"name":"mine","in":"query","required":false,"schema":{"type":"boolean","description":"If true, restrict to workflows owned by the active org (organization_id == active org). Combine with is_system=true to list the system workflows this org promoted (its manage view).","default":false,"title":"Mine"},"description":"If true, restrict to workflows owned by the active org (organization_id == active org). Combine with is_system=true to list the system workflows this org promoted (its manage view)."},{"name":"owned_by_me","in":"query","required":false,"schema":{"type":"boolean","description":"If true, restrict to workflows whose ``owner_user_id`` is the caller. Used by the post-login redirect on the web app to decide between the empty-state and the workflow list.","default":false,"title":"Owned By Me"},"description":"If true, restrict to workflows whose ``owner_user_id`` is the caller. Used by the post-login redirect on the web app to decide between the empty-state and the workflow list."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/WorkflowStatus"},{"type":"null"}],"description":"Filter by lifecycle status. Omitted (default) lists only 'active' workflows and hides drafts. Pass 'draft' to list the caller's own drafts (drafts are private to their author, never shown to others or counted against the plan).","title":"Status"},"description":"Filter by lifecycle status. Omitted (default) lists only 'active' workflows and hides drafts. Pass 'draft' to list the caller's own drafts (drafts are private to their author, never shown to others or counted against the plan)."},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter to workflows carrying any of the given tags. Repeat the param for multiple tags.","title":"Tags"},"description":"Filter to workflows carrying any of the given tags. Repeat the param for multiple tags."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match against workflow name/description (case-insensitive).","title":"Q"},"description":"Substring match against workflow name/description (case-insensitive)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Paginated_WorkflowSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["workflows"],"summary":"Create a workflow","description":"Create a new workflow owned by the caller's active org (begins at v1).","operationId":"create_workflow_api_v2_workflows_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/drafts":{"post":{"tags":["workflows"],"summary":"Save a new workflow draft","description":"Persist an in-progress workflow as a resumable draft.\n\nA draft is a real workflow row in ``draft`` status: it is private to its\nauthor, never appears in the normal workflow list, never counts against\nthe plan's workflow cap, and cannot be run, cloned, promoted, or shared.\nValidation is deliberately loose so a half-finished workflow (missing\nsteps, an unconfigured agent, no name) still saves. There is no capacity\ngate here on purpose. The cap is enforced only when the draft is\npublished. No ``WORKFLOW_CREATED`` audit is written for a draft; that is\nemitted at publish time when the workflow becomes real.","operationId":"create_workflow_draft_api_v2_workflows_drafts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowDraftCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/drafts/{workflow_id}":{"put":{"tags":["workflows"],"summary":"Overwrite an existing workflow draft","description":"Overwrite the caller's draft in place with the latest form state.\n\nA draft keeps a single snapshot (version 1) that is rewritten in place on\nevery save, so drafts never accumulate versions. Metadata and the full\nstep list are replaced from the payload. Still lenient: an incomplete\ndraft saves fine. Only the draft's author can call this.","operationId":"update_workflow_draft_api_v2_workflows_drafts__workflow_id__put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowDraftUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/publish":{"post":{"tags":["workflows"],"summary":"Publish a draft (new workflow or edits to a live one)","description":"Publish the caller's draft.\n\nFor a brand-new-workflow draft this is the \"Save workflow\" action: full\nvalidation (at least one step, at least one Vision step, every agent\nand its parameters valid, pass criteria within range), org name uniqueness,\nand the plan's workflow cap (the only place a new draft is counted). The row\nflips from ``draft`` to ``active`` and normal versioning begins.\n\nFor an edit draft (staging changes to an existing live workflow) the draft's\nsteps are applied to the parent as a new snapshot version, its metadata and\nreference images overwrite the parent's, and the draft is discarded. No cap\nis consumed (the parent already counts). If the parent has been published\nagain since the draft branched, this returns 409 ``draft_base_outdated`` so\nthe author can overwrite (retry with ``force=true``) or discard, rather than\nsilently dropping the newer version. Either way a validation failure leaves\nthe draft intact to fix and retry.","operationId":"publish_workflow_draft_api_v2_workflows__workflow_id__publish_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Publish an edit draft even though its workflow has been published by someone else since the draft branched, overwriting their version.","default":false,"title":"Force"},"description":"Publish an edit draft even though its workflow has been published by someone else since the draft branched, overwriting their version."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/drafts":{"post":{"tags":["workflows"],"summary":"Start (or resume) a draft of edits to a live workflow","description":"Open an editable draft copy of a live workflow's current definition.\n\nReturns a private ``draft`` row (steps and reference images copied from the\nlive workflow) that the caller edits and autosaves without affecting the\nrunning workflow, then publishes to apply the changes. Resumes the caller's\nexisting edit draft for this workflow if one is already open, so reopening\nthe editor continues where they left off. The live workflow keeps running\nuntouched until publish.","operationId":"create_workflow_edit_draft_api_v2_workflows__workflow_id__drafts_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/clone":{"post":{"tags":["workflows"],"summary":"Clone a workflow into an editable copy","description":"Clone a visible workflow into a fresh copy owned by the active org.\n\nWorks on system workflows and the org's own workflows alike. The\ncopy carries over the four workflow flags and\nevery step (including pass_criteria), and is always editable\n(``is_system=False``). The copy is auto-named ``<source name>\n(cloned)`` with a numeric suffix when needed to stay unique within\nthe org. Use this as the \"start from a system workflow\" entry point\non create.","operationId":"clone_workflow_endpoint_api_v2_workflows__workflow_id__clone_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/promote":{"post":{"tags":["workflows"],"summary":"Promote a workflow into a global system workflow","description":"Promote one of the org's workflows into a global, public system workflow.\n\nTakes a one-time frozen copy of the workflow's current version (steps,\nflags, pass_criteria, and reference files) into a new system workflow\nvisible to and runnable by every org. The original private workflow is left\nuntouched. Any admin/owner may promote their org's own workflow. Its\nreference images become public to all orgs, which the client confirms before\ncalling this.\n\n``is_tiliter_created`` (sorts the entry ahead in the picker) is superuser\nonly: a non-superuser requesting it is refused (403). A system workflow\ncannot itself be promoted again.","operationId":"promote_workflow_api_v2_workflows__workflow_id__promote_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/WorkflowPromote"},{"type":"null"}],"title":"Payload"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}":{"get":{"tags":["workflows"],"summary":"Get a workflow","description":"Return a single workflow by id, including its ordered steps.\n\nReturns 404 if the workflow does not exist, is soft-deleted, or is not\nvisible to the caller. Non-system workflows are scoped to the active org,\nand non-admin members are further restricted to their group-visible set\n(own + system + workflows in their groups). VA-499.","operationId":"get_workflow_api_v2_workflows__workflow_id__get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["workflows"],"summary":"Update workflow metadata","description":"Patch display metadata (name/description/tags) on the family row.","operationId":"patch_workflow_api_v2_workflows__workflow_id__patch","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["workflows"],"summary":"Delete a workflow","description":"Delete a workflow owned by the caller's active org.\n\nIn-progress drafts are abandoned automatically. A workflow with an\nanalysis actively running is still refused so the worker is not orphaned.","operationId":"delete_workflow_api_v2_workflows__workflow_id__delete","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/steps":{"put":{"tags":["workflows"],"summary":"Replace the steps of the workflow with the new set of steps.","description":"Replace the workflow's steps with the supplied step list.","operationId":"replace_workflow_steps_api_v2_workflows__workflow_id__steps_put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStepsReplace"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/steps/{step_key}/reference-files":{"post":{"tags":["workflows"],"summary":"Attach a reference file to a workflow step","description":"Store a reference file and attach it to ``step_key``.\n\nThe file arrives as ``multipart/form-data``; the bytes are written\nstraight to object storage and the row keeps only the server-owned\nobject path. ``step_key`` need not correspond to a saved step yet -- the\neditor mints it so files can be attached before the first save -- so no\nstep-existence check is made beyond workflow visibility.","operationId":"add_step_reference_file_api_v2_workflows__workflow_id__steps__step_key__reference_files_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}},{"name":"step_key","in":"path","required":true,"schema":{"type":"string","title":"Step Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_step_reference_file_api_v2_workflows__workflow_id__steps__step_key__reference_files_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStepReferenceFileRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["workflows"],"summary":"List a workflow step's reference files","description":"Return a step's reference files as signed download URLs.\n\nBest-effort signing: a file whose path fails to sign is omitted rather\nthan failing the whole list.","operationId":"list_step_reference_files_api_v2_workflows__workflow_id__steps__step_key__reference_files_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}},{"name":"step_key","in":"path","required":true,"schema":{"type":"string","title":"Step Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowStepReferenceFileRead"},"title":"Response List Step Reference Files Api V2 Workflows  Workflow Id  Steps  Step Key  Reference Files Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/workflows/{workflow_id}/steps/{step_key}/reference-files/{file_id}":{"delete":{"tags":["workflows"],"summary":"Delete a workflow step's reference file","description":"Detach a reference file from a step.\n\nDeletes the row only; the underlying blob is left in storage (an orphan\nis harmless and avoids a storage round-trip in the request path).","operationId":"delete_step_reference_file_api_v2_workflows__workflow_id__steps__step_key__reference_files__file_id__delete","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}},{"name":"step_key","in":"path","required":true,"schema":{"type":"string","title":"Step Key"}},{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs":{"get":{"tags":["jobs"],"summary":"List jobs","description":"List jobs visible to the caller, newest first.\n\nCursor-paginated. Optionally filter by lifecycle ``status``, by\n``workflow_id`` (matches all jobs for that workflow), and/or by a\n``search`` term.\n\n``search`` looks inside the job rather than at its name alone: an operator\nwho recorded a tenant on a Selection step can find that tenant's jobs\nafterwards (VA-694). See ``job_search_predicate`` for what it matches.\n\nVisibility is role-scoped within the active organization: ADMIN and\nOWNER see every job in the org; VIEWER and MEMBER see only their own.\nSearch narrows that scope; it never widens it.","operationId":"list_jobs_api_v2_jobs_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobLifecycleStatus"},{"type":"null"}],"description":"Filter to jobs in this status.","title":"Status"},"description":"Filter to jobs in this status."},{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to jobs of this workflow family.","title":"Workflow Id"},"description":"Filter to jobs of this workflow family."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Filter to jobs matching this text, case-insensitively. Matches Selection answers recorded against the job, its step names, and the workflow name. Blank or whitespace-only is ignored.","title":"Search"},"description":"Filter to jobs matching this text, case-insensitively. Matches Selection answers recorded against the job, its step names, and the workflow name. Blank or whitespace-only is ignored."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Paginated_JobSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["jobs"],"summary":"Start a job","description":"Start a new job against a workflow.\n\nRequires a writer role (MEMBER, ADMIN, or OWNER) in the active\norganization; VIEWERs are refused. The job is created in\n``IN_PROGRESS`` status. The workflow must exist, not be soft-deleted,\nand be visible to the active organization (its own workflows plus system\nworkflows). Members are further restricted to workflows they own, system\nworkflows, or workflows assigned to a group they belong to. Members in\nthe virtual \"All workflows\" group have unrestricted access.\nOmit ``workflow_id`` to start an ad-hoc job (requires the org\nto have ad-hoc jobs enabled). Submit step results with\n``POST /{id}/steps/{sort_order}`` and finalize with ``POST /{id}/analyse``.","operationId":"create_job_api_v2_jobs_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Get a job","description":"Return a single job by id, including all step results so far.\n\nVisible to the job's owner, to an ADMIN/OWNER of the organisation\nthat ran the job, or to the assignee of a corrective action linked\nto this job (either its reinspection job or the original inspection\nof their assigned finding). Returns 404 for jobs the caller may not\nsee (so existence is not leaked). Steps carrying reference files get\nshort-lived signed URLs minted so the results page can render them\nwithout a second round-trip. Signing only runs once the job has\nreached a terminal status (when the report is shown); the per-second\nanalysing polls skip it. The raw object paths are then dropped so\nthis read carries one reference field, ``reference_file_urls``, and\nsurfaces no blob-storage layout.","operationId":"get_job_api_v2_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["jobs"],"summary":"Delete a job","description":"Delete an in-progress job.\n\nRequires a writer role (MEMBER, ADMIN, or OWNER); VIEWERs are refused.\nScoped to the caller's own jobs: returns 404 if the job does not exist\nor is owned by another user. Only jobs that are still ``IN_PROGRESS``\nmay be deleted; once a job has been finalised it returns 409.","operationId":"delete_job_api_v2_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/location":{"patch":{"tags":["jobs"],"summary":"Set location before resuming an inspection","description":"Attach a location to an unlabelled in-progress job before capture.","operationId":"update_job_location_api_v2_jobs__job_id__location_patch","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobLocationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/download_report":{"get":{"tags":["jobs"],"summary":"Download a job report as a PDF","description":"Generate and stream a PDF report for a completed job.\n\nThe report is rendered fresh on every request from stored run data and\nis never cached. Visible to the job's owner, or to an ADMIN/OWNER of the\norganisation that ran the job. Returns 404 for jobs the caller may not\nsee (so existence is not leaked) and 409 for jobs that have not finished\nrunning.","operationId":"get_job_report_api_v2_jobs__job_id__download_report_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"tz","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional IANA timezone name (e.g. `Australia/Sydney`, `America/Los_Angeles`) used to render every timestamp in the PDF. When omitted or set to an unknown value, the report renders in UTC. The zone is labelled once, under the Job Details table.","title":"Tz"},"description":"Optional IANA timezone name (e.g. `Australia/Sydney`, `America/Los_Angeles`) used to render every timestamp in the PDF. When omitted or set to an unknown value, the report renders in UTC. The zone is labelled once, under the Job Details table."}],"responses":{"200":{"description":"The rendered job report.","content":{"application/pdf":{}}},"404":{"description":"Job does not exist or the caller may not see it."},"409":{"description":"Job has not finished running, so no report is available yet."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/steps/{sort_order}":{"post":{"tags":["jobs"],"summary":"Submit a vision-agent step (legacy multipart shim)","description":"Legacy single-shot capture endpoint for the deployed mobile build.\n\nKept permanently as a wire-compat shim: the currently deployed\nproduction mobile app is a frozen artifact that POSTs the captured\nimage bytes here as ``multipart/form-data`` and expects the row to\nland in ``PENDING`` for the deferred worker. Web and the next mobile\nbuild call ``POST /steps/{sort_order}/image`` + ``POST\n/steps/{sort_order}/submit`` instead.\n\nOnly accepts vision-agent steps; other step types have their own\nsubmit payload shape and must go through ``/submit``.\n\nResubmission inserts a new row (with its own image object) and the\nwindow query in :func:`_latest_step_results` makes the most recent\nrow win, so \"retake photo\" simply supersedes the previous capture\nwithout needing an explicit delete.","operationId":"submit_job_step_api_v2_jobs__job_id__steps__sort_order__post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort_order","in":"path","required":true,"schema":{"type":"integer","title":"Sort Order"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_job_step_api_v2_jobs__job_id__steps__sort_order__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/steps/{sort_order}/image":{"post":{"tags":["jobs"],"summary":"Upload a captured image for a step (any type with has_image)","description":"Persist a captured image and return its object storage path.\n\nFirst half of the split submit lifecycle: the client uploads the\nimage bytes here, receives the persisted object path, then references\nthat path in the JSON payload sent to ``POST /submit``. Each upload\nlands as its own immutable object under ``jobs/{job_id}/`` so\nre-captures never overwrite prior takes.\n\nGated by the step type: only types whose ``StepTypeSpec.has_image``\nis true accept an image upload. A caller trying to upload against a\nnon-image step (e.g. selection) gets 409 rather than a silently\nstored but unusable object.","operationId":"upload_step_image_api_v2_jobs__job_id__steps__sort_order__image_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort_order","in":"path","required":true,"schema":{"type":"integer","title":"Sort Order"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_step_image_api_v2_jobs__job_id__steps__sort_order__image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStepImageUploadRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/steps/{sort_order}/submit":{"post":{"tags":["jobs"],"summary":"Submit a step (unified per-type payload)","description":"Uniform submit endpoint for every step type.\n\nBody is a discriminated union on ``step_type``:\n\n* ``vision_agent``: ``{\"step_type\": \"vision_agent\",\n  \"image_object_path\": \"jobs/<job_id>/...\"}``. The path must have\n  already been returned by ``POST /steps/{sort_order}/image``. The\n  row is inserted ``PENDING`` and the deferred worker picks it up\n  at ``/analyse`` time.\n* ``selection``: ``{\"step_type\": \"selection\", \"header\": \"...\"}``. The\n  server matches the header against the configured options and the\n  row is inserted ``COMPLETED`` immediately with the resolved\n  ``{selected}`` payload.\n\nServer-side checks:\n\n* The body's ``step_type`` must match the snapshot step's type (409\n  otherwise), so an old client cannot pretend a selection row is a\n  vision capture (or vice-versa).\n* For vision submits, ``image_object_path`` must live under this\n  job's namespace so a caller cannot reference another job's image.\n* The registry re-validates the payload against the persisted\n  ``step_config``: invalid selection headers, malformed configs, and\n  unknown agents surface as 400s here rather than as errored steps\n  minutes later.","operationId":"submit_step_api_v2_jobs__job_id__steps__sort_order__submit_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort_order","in":"path","required":true,"schema":{"type":"integer","title":"Sort Order"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/VisionAgentStepSubmit"},{"$ref":"#/components/schemas/SelectionStepSubmit"}],"discriminator":{"propertyName":"step_type","mapping":{"vision_agent":"#/components/schemas/VisionAgentStepSubmit","selection":"#/components/schemas/SelectionStepSubmit"}},"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/steps/{sort_order}/image-url":{"get":{"tags":["jobs"],"summary":"Get a signed URL for a step's captured image","description":"Mint a short-lived signed URL for the first image at ``sort_order``.\n\nThe bytes live in object storage; the backend never proxies them.\nInstead it returns a time-limited signed URL the frontend fetches\ndirectly. The latest capture batch for the step wins (mirroring\n:func:`_latest_step_results`), so a \"retake\" is reflected here.\nDeterministically returns the batch's ``capture_batch_index == 0`` image, so\nthis endpoint keeps returning exactly one URL even for a multi-image\nstep. Retained for shipped mobile builds that predate the multi-image\nlist endpoint; new clients should use ``GET .../images``.","operationId":"get_job_step_image_url_api_v2_jobs__job_id__steps__sort_order__image_url_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort_order","in":"path","required":true,"schema":{"type":"integer","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStepImageUrlRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/steps/{sort_order}/images":{"get":{"tags":["jobs"],"summary":"List signed URLs for every image in a step's winning capture batch","description":"Mint short-lived signed URLs for every image captured at ``sort_order``.\n\nReturns one entry per image in the step's latest capture batch,\nordered by ``capture_batch_index``, mirroring :func:`_latest_step_results`\nso a retake (a whole new batch) supersedes the prior set. 404 when\nthe step has no captured image; 502 when a URL cannot be minted.","operationId":"get_job_step_images_api_v2_jobs__job_id__steps__sort_order__images_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort_order","in":"path","required":true,"schema":{"type":"integer","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStepImagesRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/extra-steps":{"post":{"tags":["jobs"],"summary":"Append an ad-hoc step to a job","description":"Append an ad-hoc step to a running job.\n\nOnly allowed when the workflow's\n``allow_adhoc_step`` flag is true. The new\nstep appends at ``max(existing sort_order) + 1`` across both\nworkflow steps and prior extras, so the UI's sort order is stable.\n\nLike :func:`submit_job_step`, this is capture-only: the metadata\narrives as ``multipart/form-data`` form fields, the image as a file\nupload (written straight to object storage), and the background\nanalysis runs at /analyse time.","operationId":"add_extra_step_api_v2_jobs__job_id__extra_steps_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_extra_step_api_v2_jobs__job_id__extra_steps_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/jobs/{job_id}/analyse":{"post":{"tags":["jobs"],"summary":"Finalise a job (kicks off deferred analysis)","description":"Transition IN_PROGRESS -> ANALYSING and fire the analysis worker.\n\nPre-flight: every workflow step must have at least one captured\nresult (PENDING / RUNNING / COMPLETED / ERRORED - slice 5 adds the\n\"skipped is allowed\" relaxation). The credit budget is checked but\nnot yet debited - the worker charges per-step so a failing step\nrefunds cleanly.\n\nReturns immediately with ``status=\"analysing\"``; clients poll\n``GET /{id}`` to see per-step transitions and the terminal\n``completed`` / ``completed_with_errors`` state.\n\nRe-run path: when the job is already in\n``COMPLETED_WITH_ERRORS``, the request reopens analysis for only\nthe ERRORED steps (each is refunded and reset to PENDING) and\nre-enters the same worker. ``COMPLETED`` / ``ABANDONED`` /\n``ANALYSING`` all return 409.","operationId":"analyse_job_api_v2_jobs__job_id__analyse_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_add_extra_step_api_v2_jobs__job_id__extra_steps_post":{"properties":{"name":{"type":"string","title":"Name","description":"Display name for the ad-hoc step."},"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image","description":"Captured image for this step (standard file upload)."},"step_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Step Type","description":"Strategy for the ad-hoc step (e.g. ``vision_agent``). Omit and send legacy ``agent_id`` for pre-refactor clients."},"step_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Step Config","description":"Per-type configuration as a JSON object string. Legacy clients may omit this."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Deprecated. Legacy top-level agent id; folded into ``step_config`` when supplied."},"workflow_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Note","description":"Optional note shown to the operator for this step."},"reference_file_paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Paths","description":"Optional reference file object paths for this step (repeatable form field)."},"agent_parameters":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Parameters","description":"Deprecated. Legacy agent parameters as a JSON object string; folded into ``step_config``."},"pass_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pass Criteria","description":"Pass criteria as a JSON object string."},"allow_multiple_images":{"type":"boolean","title":"Allow Multiple Images","description":"Whether this ad-hoc step accepts more than one image per submission.","default":true}},"type":"object","required":["name","image"],"title":"Body_add_extra_step_api_v2_jobs__job_id__extra_steps_post"},"Body_add_step_reference_file_api_v2_workflows__workflow_id__steps__step_key__reference_files_post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image","description":"Reference file for this step (standard file upload)."}},"type":"object","required":["image"],"title":"Body_add_step_reference_file_api_v2_workflows__workflow_id__steps__step_key__reference_files_post"},"Body_submit_job_step_api_v2_jobs__job_id__steps__sort_order__post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image","description":"Captured image for this step (standard file upload)."}},"type":"object","required":["image"],"title":"Body_submit_job_step_api_v2_jobs__job_id__steps__sort_order__post"},"Body_upload_step_image_api_v2_jobs__job_id__steps__sort_order__image_post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image","description":"Captured image for this step (standard file upload)."}},"type":"object","required":["image"],"title":"Body_upload_step_image_api_v2_jobs__job_id__steps__sort_order__image_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobCreate":{"properties":{"workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Id","description":"Id of the workflow to run. The job captures the workflow's current steps automatically and will always resolve to those exact steps, even if the workflow is edited later. Omit to start an ad-hoc job with no workflow (requires the org to have ad-hoc jobs enabled); steps are then added via extra-steps.","examples":["3f9b1c2d-4f7e-4b9a-9a3c-1f7e0d2b5a11"]},"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name","description":"Display name for an ad-hoc job. Ignored when workflow_id is set.","examples":["Ad-hoc inspection"]},"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"Optional site where this inspection is being performed."},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"Optional asset being inspected. Its site is used automatically."},"reinspection_action_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reinspection Action Id","description":"Corrective action this job reinspects. When present, the backend validates the action and links the follow-up job atomically."}},"type":"object","title":"JobCreate","description":"Body for `POST /api/v2/jobs`."},"JobExtraStepRead":{"properties":{"id":{"type":"string","title":"Id","description":"Stable id of this extra step."},"sort_order":{"type":"integer","title":"Sort Order","description":"Zero-based position after all workflow steps."},"name":{"type":"string","title":"Name","description":"Human-readable name shown to the operator."},"workflow_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Note","description":"Optional guidance copy for the operator."},"reference_file_paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Paths","description":"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":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Urls","description":"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":{"$ref":"#/components/schemas/StepType","description":"Strategy used by this step (see :class:`StepType`).","default":"vision_agent"},"step_config":{"additionalProperties":true,"type":"object","title":"Step Config","description":"Per-type configuration for this step. Empty ``{}`` when the type has none."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Deprecated. Mirrored from ``step_config.agent_id`` for vision-agent steps; carries the selection sentinel for legacy selection clients.","examples":["object-detection-v1"]},"agent_parameters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Parameters","description":"Deprecated. Mirrored from ``step_config`` (with ``agent_id`` stripped for vision-agent steps)."},"pass_criteria":{"anyOf":[{"$ref":"#/components/schemas/PassCriteria"},{"type":"null"}],"description":"Pass-criteria captured when the extra step was created. Mirrors :class:`JobExtraStepCreate.pass_criteria` so the read and write contracts have identical shapes."},"allow_multiple_images":{"type":"boolean","title":"Allow Multiple Images","description":"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.","default":true},"is_extra":{"type":"boolean","title":"Is Extra","description":"Always true; lets the UI render ad-hoc chips with a single field check.","default":true}},"type":"object","required":["id","sort_order","name"],"title":"JobExtraStepRead","description":"Ad-hoc step appended to a running job.\n\nMirrors :class:`WorkflowStepRead` so the frontend can render extra\nsteps and workflow steps with the same component, just with an\n`is_extra=True` chip."},"JobLifecycleStatus":{"type":"string","enum":["in_progress","analysing","completed","completed_with_errors","abandoned"],"title":"JobLifecycleStatus"},"JobLocationUpdate":{"properties":{"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"Site where the in-progress inspection is being performed."},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"Optional nested location or asset being inspected within the selected site."}},"type":"object","title":"JobLocationUpdate","description":"Location selected before an unlabelled in-progress job is resumed."},"JobNotificationRule":{"properties":{"recipient_user_ids":{"items":{"type":"string"},"type":"array","title":"Recipient User Ids","description":"Ids of users to notify, in addition to the job owner when ``notify_job_owner`` is set. Ids outside the job's organisation are dropped at dispatch time rather than failing the run."},"notify_job_owner":{"type":"boolean","title":"Notify Job Owner","description":"Whether the user who ran the job is notified.","default":true},"delivery_channels":{"items":{"$ref":"#/components/schemas/NotificationChannel"},"type":"array","minItems":1,"title":"Delivery Channels","description":"Channels used for this rule. Email and WhatsApp are only sent when explicitly selected."},"email_recipients":{"items":{"type":"string"},"type":"array","title":"Email Recipients","description":"Additional email addresses that receive this notification's result summary."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the rule is live. False sends nothing.","default":true},"on":{"$ref":"#/components/schemas/JobNotificationTrigger","description":"Job outcome that fires the notification. ``complete`` fires whatever the verdict; ``passed`` and ``failed`` fire only on the matching job result.","default":"complete"}},"additionalProperties":false,"type":"object","title":"JobNotificationRule","description":"Who to tell about a workflow's job outcome, and when.\n\nAttached to the workflow family, so an edit applies to the next job that\nfinalises without bumping the snapshot version. Every workflow carries one:\nswitching notifications off is ``enabled: false``, not an absent rule."},"JobNotificationTrigger":{"type":"string","enum":["complete","passed","failed"],"title":"JobNotificationTrigger","description":"Job outcome that fires a job-level notification."},"JobRead":{"properties":{"id":{"type":"string","title":"Id","description":"Stable job id."},"workflow_id":{"type":"string","title":"Workflow Id","description":"Id of the workflow this job is running against."},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name","description":"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":{"type":"string","title":"User Id","description":"Id of the user who started the job."},"is_adhoc":{"type":"boolean","title":"Is Adhoc","description":"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.","default":false},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id","description":"Owning organization id. Null for personal jobs."},"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"Site selected at the start of this inspection."},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"Asset selected at the start of this inspection."},"location_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Path","description":"Human-readable site and hierarchy path captured when this inspection started."},"status":{"$ref":"#/components/schemas/JobLifecycleStatus","description":"Current lifecycle status of the job."},"result":{"$ref":"#/components/schemas/JobResult","description":"Pass/fail outcome of the job, independent of lifecycle status. PENDING until the job reaches a terminal state, then PASSED or FAILED. A COMPLETED job can still be FAILED when a step's verdict was False on a workflow that does not allow step failure.","default":"pending"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"When the job was created."},"analysing_started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Analysing Started At","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the job was completed. Null while in progress."},"steps":{"items":{"$ref":"#/components/schemas/WorkflowStepRead"},"type":"array","title":"Steps","description":"The workflow's steps as they were at job start."},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"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.","default":false},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"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.","default":true},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"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.","default":true},"extra_steps":{"items":{"$ref":"#/components/schemas/JobExtraStepRead"},"type":"array","title":"Extra Steps","description":"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`."},"step_results":{"items":{"$ref":"#/components/schemas/JobStepResultRead"},"type":"array","title":"Step Results","description":"Latest result per step (one entry per submitted step)."}},"type":"object","required":["id","workflow_id","user_id","status","started_at"],"title":"JobRead","description":"Full job payload including the latest result per step.\n\n`steps` is the workflow's steps as they were at the moment the job\nwas started -- not the current workflow. Clients should render the\nrun UI from this list so edits to the workflow after the job started\nnever change historical runs."},"JobResult":{"type":"string","enum":["pending","passed","failed"],"title":"JobResult","description":"The pass/fail outcome of a job, distinct from its lifecycle status.\n\nLifecycle (``JobLifecycleStatus``) answers \"did the run finish, and did\nany step technically error?\". Result answers \"did the run pass its\nchecks?\". The two are independent: a run can finish cleanly\n(``COMPLETED``) yet still ``FAILED`` because too few steps passed for the\nworkflow's pass policy.\n\nThe pass policy is driven by ``allow_step_failure``:\n\n- Off: strict. Any errored or failing step makes the result ``FAILED``.\n- On: threshold. Failures are tolerated until the percentage of\n  evaluable steps that failed exceeds ``fail_threshold_percent``;\n  errored steps count as failures.\n\n- ``PENDING``: not yet determined (job still in progress / analysing).\n- ``PASSED``: the workflow's pass policy is satisfied.\n- ``FAILED``: the workflow's pass policy is not satisfied."},"JobStepImageRead":{"properties":{"capture_batch_index":{"type":"integer","title":"Capture Batch Index","description":"Zero-based position of this image within its capture batch."},"url":{"type":"string","title":"Url","description":"Time-limited signed download URL."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the signed URL expires."}},"type":"object","required":["capture_batch_index","url","expires_in"],"title":"JobStepImageRead","description":"One image within a step's winning capture batch, with a signed URL."},"JobStepImageUploadRead":{"properties":{"image_object_path":{"type":"string","title":"Image Object Path","description":"Object storage path where the image was persisted. Echo this back in the ``/submit`` payload for a vision-agent step."},"original_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Filename","description":"Original name of the uploaded file, sanitised to its basename. Null when the upload carried no filename (e.g. a mobile camera capture)."}},"type":"object","required":["image_object_path"],"title":"JobStepImageUploadRead","description":"Path returned by ``POST /jobs/{id}/steps/{sort_order}/image``.\n\nNew submit lifecycle: the client uploads the captured image\nseparately, receives an object-storage path, then references that\npath in the JSON ``/submit`` payload. The path is scoped to the\nowning job (``jobs/{job_id}/...``) so ``/submit`` can reject a path\nreferencing another job."},"JobStepImageUrlRead":{"properties":{"url":{"type":"string","title":"Url","description":"Time-limited signed download URL."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the signed URL expires."}},"type":"object","required":["url","expires_in"],"title":"JobStepImageUrlRead","description":"Short-lived signed URL for a captured step image.\n\nReturned by `GET /jobs/{id}/steps/{sort_order}/image-url`. The client\nfetches the bytes directly from object storage so images never\ntransit the app servers."},"JobStepImagesRead":{"properties":{"images":{"items":{"$ref":"#/components/schemas/JobStepImageRead"},"type":"array","title":"Images","description":"Images in display order."}},"type":"object","required":["images"],"title":"JobStepImagesRead","description":"Every image of a step's winning capture batch, ordered by capture_batch_index.\n\nReturned by ``GET /jobs/{id}/steps/{sort_order}/images``."},"JobStepResultRead":{"properties":{"sort_order":{"type":"integer","title":"Sort Order","description":"Zero-based position of the step in the workflow."},"workflow_step_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Step Id","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extra Step Id","description":"Id of the ad-hoc extra step, if this result is for one."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Id of the agent that produced this result. Null for non-agent step types (e.g. an operator-picked selection)."},"step_input":{"anyOf":[{},{"type":"null"}],"title":"Step Input","description":"Generic per-type submission input. Vision-agent rows carry `{image_object_path}`; selection rows carry `{header}`. Null on legacy rows written before the generic input column existed. Typed as ``object`` to tolerate a JSON scalar or list defensively."},"step_output":{"anyOf":[{},{"type":"null"}],"title":"Step Output","description":"Generic per-type output. Vision-agent rows hold the agent's raw response once analysed; selection rows hold `{selected}` at submit time. Null while a vision step is still pending or errored. Typed as ``object`` to match the underlying JSON column and to survive historical rows that stored non-dict values."},"agent_response":{"anyOf":[{},{"type":"null"}],"title":"Agent Response","description":"DEPRECATED legacy mirror of `step_output` for the frozen production mobile build and rolled-back backends. New clients should read `step_output` instead."},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At","description":"When this (most-recent) result was submitted."},"analysis_status":{"$ref":"#/components/schemas/StepAnalysisStatus","description":"Deferred-analysis status for this captured step: PENDING (image captured, awaiting /analyse), RUNNING (analysis in flight), COMPLETED (agent ran successfully), ERRORED (agent failed; the credit was refunded).","default":"pending"},"passed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Passed","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pass Explanation","description":"Human-readable summary of which criteria passed / failed."},"step_narrative":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Step Narrative","description":"Resolved, display-ready sentence describing this step's outcome. Built server-side from the agent's declared narrative fields (first non-empty), falling back to the analysis error, then the pass-criteria explanation, then a generic verdict line. This is the same resolution the PDF report uses, so every surface reads the same text. Null while the step has nothing to say yet (e.g. captured but not analysed). Clients should render this instead of deriving text from `step_output`."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Truncated error message when analysis_status == ERRORED."},"image_available":{"type":"boolean","title":"Image Available","description":"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).","default":false},"capture_batch_index":{"type":"integer","title":"Capture Batch Index","description":"Zero-based position of this image within its capture batch, defines display order.","default":0},"capture_batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Capture Batch Id","description":"Id grouping the rows submitted together for one step. Null on legacy rows written before batching existed."},"original_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Filename","description":"Original name of the uploaded file, sanitised to its basename. Null when the client never sent one (e.g. a mobile camera capture)."}},"type":"object","required":["sort_order","submitted_at"],"title":"JobStepResultRead","description":"A single (latest) step result attached to a job."},"JobSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Stable job id."},"workflow_id":{"type":"string","title":"Workflow Id","description":"Id of the workflow this job is running against."},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name","description":"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":{"type":"string","title":"User Id","description":"Id of the user who started the job."},"is_adhoc":{"type":"boolean","title":"Is Adhoc","description":"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.","default":false},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id","description":"Owning organization id. Null for personal jobs."},"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"Site selected at the start of this inspection."},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"Asset selected at the start of this inspection."},"location_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Path","description":"Human-readable site and hierarchy path captured when this inspection started."},"status":{"$ref":"#/components/schemas/JobLifecycleStatus","description":"Current lifecycle status of the job."},"result":{"$ref":"#/components/schemas/JobResult","description":"Pass/fail outcome of the job, independent of lifecycle status. PENDING until the job reaches a terminal state, then PASSED or FAILED. A COMPLETED job can still be FAILED when a step's verdict was False on a workflow that does not allow step failure.","default":"pending"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"When the job was created."},"analysing_started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Analysing Started At","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the job was completed. Null while in progress."}},"type":"object","required":["id","workflow_id","user_id","status","started_at"],"title":"JobSummary","description":"List-view payload (no step results)."},"NotificationChannel":{"type":"string","enum":["in_app","push","sms","email","whatsapp"],"title":"NotificationChannel","description":"A delivery channel a notification can be dispatched through.\n\n- ``IN_APP``: recorded in the in-app feed only (always produced).\n- ``PUSH``: Expo push notification to a registered device.\n- ``SMS``: text message (added in a later phase).\n- ``EMAIL``: transactional email (added in a later phase).\n- ``WHATSAPP``: WhatsApp message delivered through Twilio."},"Paginated_JobSummary_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/JobSummary"},"type":"array","title":"Items","description":"Page of items in the order the endpoint defines; see the endpoint's order_by for the sort."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor to fetch the next page. Null when this is the last page.","examples":[null,"eyJjIjoiMjAyNi0wNS0yMVQwMzowMDowMCswMDowMCIsImkiOiJhYmMifQ"]},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied.","examples":[50]}},"type":"object","required":["items","limit"],"title":"Paginated[JobSummary]"},"Paginated_WorkflowSummary_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkflowSummary"},"type":"array","title":"Items","description":"Page of items in the order the endpoint defines; see the endpoint's order_by for the sort."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor to fetch the next page. Null when this is the last page.","examples":[null,"eyJjIjoiMjAyNi0wNS0yMVQwMzowMDowMCswMDowMCIsImkiOiJhYmMifQ"]},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied.","examples":[50]}},"type":"object","required":["items","limit"],"title":"Paginated[WorkflowSummary]"},"PassCriteria":{"properties":{"combinator":{"type":"string","enum":["and","or"],"title":"Combinator","description":"How to aggregate rule outcomes. ``and`` requires every rule to pass; ``or`` requires at least one."},"rules":{"items":{"$ref":"#/components/schemas/PassCriteriaRule"},"type":"array","minItems":1,"title":"Rules","description":"One or more rules to evaluate against the agent response."}},"additionalProperties":false,"type":"object","required":["combinator","rules"],"title":"PassCriteria"},"PassCriteriaOperator":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","contains","not_contains","in","not_in","exists","not_exists","length_gte","length_lt"],"title":"PassCriteriaOperator"},"PassCriteriaRule":{"properties":{"path":{"type":"string","minLength":1,"title":"Path","description":"Dotted path into agent response."},"op":{"$ref":"#/components/schemas/PassCriteriaOperator"},"value":{"title":"Value","description":"Right-hand operand for the comparison. Ignored for existence operators (cleared to null). For membership operators (``in`` / ``not_in``) this must be a list."}},"additionalProperties":false,"type":"object","required":["path","op"],"title":"PassCriteriaRule"},"ReportConfig":{"properties":{"image_size":{"$ref":"#/components/schemas/ReportImageSize","description":"Size captured images render at in the job report PDF.","default":"small"}},"type":"object","title":"ReportConfig","description":"Report-presentation settings an author controls per workflow.\n\nDefaults reproduce today's report exactly (image size Small), so a workflow\nthat never sets a report config renders as it always has."},"ReportImageSize":{"type":"string","enum":["small","medium","large"],"title":"ReportImageSize","description":"How large captured images render in the job report PDF.\n\nBounds scale each dimension relative to Small (the pre-VA-680 fixed size):\nMedium is 1.5x, Large is 2x. Per workflow; there is no org default or\noverride layer."},"SelectionStepSubmit":{"properties":{"step_type":{"type":"string","const":"selection","title":"Step Type","description":"Discriminator for the discriminated-union submit body.","default":"selection"},"header":{"type":"string","maxLength":255,"minLength":1,"title":"Header","description":"Header of the option the operator selected. Whitespace is trimmed before matching."}},"additionalProperties":false,"type":"object","required":["header"],"title":"SelectionStepSubmit","description":"Discriminated-union variant for the ``/submit`` endpoint."},"StepAnalysisStatus":{"type":"string","enum":["pending","running","completed","errored"],"title":"StepAnalysisStatus","description":"Per-step analysis lifecycle for the deferred-analysis flow (VA-415).\n\nA capture-only POST creates a step row in `pending`. The background\ncompletion task transitions it to `running`, then to `completed` or\n`errored`. The agent verdict (pass/fail) is a separate field on the\nsame row and is only meaningful when `analysis_status == completed`."},"StepNotificationRule":{"properties":{"recipient_user_ids":{"items":{"type":"string"},"type":"array","title":"Recipient User Ids","description":"Ids of users to notify, in addition to the job owner when ``notify_job_owner`` is set. Ids outside the job's organisation are dropped at dispatch time rather than failing the run."},"notify_job_owner":{"type":"boolean","title":"Notify Job Owner","description":"Whether the user who ran the job is notified.","default":true},"delivery_channels":{"items":{"$ref":"#/components/schemas/NotificationChannel"},"type":"array","minItems":1,"title":"Delivery Channels","description":"Channels used for this rule. Email and WhatsApp are only sent when explicitly selected."},"email_recipients":{"items":{"type":"string"},"type":"array","title":"Email Recipients","description":"Additional email addresses that receive this notification's result summary."},"on":{"$ref":"#/components/schemas/StepNotificationTrigger","description":"Step outcome that fires the notification. ``passed`` and ``failed`` read the step's own pass/fail verdict. ``criteria`` evaluates ``criteria`` against the step's output, which lets a step alert on a threshold independently of whether it passed."},"criteria":{"anyOf":[{"$ref":"#/components/schemas/PassCriteria"},{"type":"null"}],"description":"Rule set evaluated against the step's output, using the same engine as ``pass_criteria``. Required when ``on`` is ``criteria`` and rejected otherwise."}},"additionalProperties":false,"type":"object","required":["on"],"title":"StepNotificationRule","description":"Who to tell about one step's outcome, and when.\n\nAttached to the step, so it is pinned to the workflow version the job ran\nagainst. Null means the step notifies nobody."},"StepNotificationTrigger":{"type":"string","enum":["passed","failed","criteria"],"title":"StepNotificationTrigger","description":"Step outcome that fires a step-level notification."},"StepType":{"type":"string","enum":["vision_agent","selection"],"title":"StepType","description":"Discriminator for a workflow step's strategy.\n\nEvery registered value has a matching :class:`StepTypeSpec` subclass in\n:mod:`app.workflow_steps` that owns the strategy's config, input,\noutput, submit payload and handlers. Adding a value here is one of the\nthree edits required to register a new step type; see\n:mod:`app.workflow_steps.registry` for the full checklist."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VisionAgentStepSubmit":{"properties":{"step_type":{"type":"string","const":"vision_agent","title":"Step Type","description":"Discriminator for the discriminated-union submit body.","default":"vision_agent"},"image_object_paths":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Image Object Paths","description":"Object storage paths returned by /steps/{sort_order}/image, in display order."},"original_filenames":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Original Filenames","description":"Original filenames for each captured image, in the same order as ``image_object_paths``. Optional so old clients keep working; when present its length must equal ``image_object_paths``."}},"additionalProperties":false,"type":"object","required":["image_object_paths"],"title":"VisionAgentStepSubmit","description":"Discriminated-union variant for the ``/submit`` endpoint.\n\nCarries an ordered batch of captures for one step (1..``MAX_STEP_CAPTURE_IMAGES``\nimages, display order is list order). Each image's bytes are uploaded\nfirst via ``POST /steps/{n}/image``, which returns an\n``image_object_path``; those paths are echoed here so the submit row\nreferences the persisted images without re-uploading.\n\nThe legacy singular ``image_object_path`` key is still accepted on the\nwire (shipped mobile builds and the legacy multipart path send it) and\nis folded into a 1-item ``image_object_paths`` list before validation."},"WorkflowCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable workflow name.","examples":["Site walk-through"]},"description":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Description","description":"Optional long-form description of the workflow's purpose."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag values (system or org-custom) for filtering/grouping."},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"Allow completing the job with uncaptured steps.","default":true},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"Allow capturing steps in any order.","default":true},"allow_step_failure":{"type":"boolean","title":"Allow Step Failure","description":"Strict (False) fails the job on any failing step; threshold (True) uses fail_threshold_percent.","default":false},"fail_threshold_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Fail Threshold Percent","description":"Required. Maximum percentage (0-100) of evaluable steps that may fail before the job is FAILED when ``allow_step_failure`` is True. Has no effect when ``allow_step_failure`` is False, but is still required -- send 0 for strict workflows. Required (rather than defaulted) so a client enabling ``allow_step_failure`` cannot silently inherit a strict 0 threshold by omitting it."},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"Allow the operator to append ad-hoc steps during a run.","default":true},"notification_rule":{"$ref":"#/components/schemas/JobNotificationRule","description":"Rule naming who to tell about the job's own outcome. Defaults to notifying the job owner whenever a job finishes."},"report_config":{"$ref":"#/components/schemas/ReportConfig","description":"Report-presentation settings for this workflow, stored live on the family row and read when a report is generated. Defaults to the current report (image size Small)."},"steps":{"items":{"$ref":"#/components/schemas/WorkflowStepCreateOrUpdate"},"type":"array","maxItems":200,"minItems":1,"title":"Steps","description":"Ordered list of steps. Must contain at least one step and at most 200."}},"type":"object","required":["name","fail_threshold_percent","steps"],"title":"WorkflowCreate","description":"Create a new workflow."},"WorkflowDraftCreate":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name","description":"Draft name (may be blank until published).","default":""},"description":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Description","description":"Optional long-form description of the workflow's purpose."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag values (system or org-custom) for filtering/grouping."},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"Allow completing the job with uncaptured steps.","default":true},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"Allow capturing steps in any order.","default":true},"allow_step_failure":{"type":"boolean","title":"Allow Step Failure","description":"Strict (False) fails the job on any failing step; threshold (True) uses fail_threshold_percent.","default":false},"fail_threshold_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Fail Threshold Percent","description":"Percentage of steps that can fail before workflow fails (0-100).","default":0},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"Allow the operator to append ad-hoc steps during a run.","default":true},"notification_rule":{"$ref":"#/components/schemas/JobNotificationRule","description":"Rule naming who to tell about the job's own outcome. Defaults to notifying the job owner whenever a job finishes."},"report_config":{"$ref":"#/components/schemas/ReportConfig","description":"Report-presentation settings for this workflow, stored live on the family row and read when a report is generated. Defaults to the current report (image size Small)."},"steps":{"items":{"$ref":"#/components/schemas/WorkflowDraftStepCreateOrUpdate"},"type":"array","maxItems":200,"title":"Steps","description":"Ordered steps (may be empty for a draft; at most 200)."}},"type":"object","title":"WorkflowDraftCreate","description":"Create or first-save a draft workflow. Every field is optional.\n\nDrops the completeness gates: no required name, no required\n``fail_threshold_percent``, no \"at least one Vision step\" rule,\nand steps may be empty. Completeness is only enforced when the draft\nis published."},"WorkflowDraftStepCreateOrUpdate":{"properties":{"step_key":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Step Key","description":"Stable identity for this logical step. The editor mints this (a uuid) so reference files attached to a not-yet-saved step survive the save. Omit for steps that carry no reference files; the server then assigns one. Reference files themselves are managed through the dedicated reference-file endpoints, never through this payload."},"sort_order":{"type":"integer","minimum":0.0,"title":"Sort Order","description":"Display order of step in the workflow.","default":0},"name":{"type":"string","maxLength":255,"title":"Name","description":"Step name (may be blank in draft).","default":""},"workflow_note":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Workflow Note","description":"Optional guidance shown to the operator before they capture the image."},"reference_file_paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Paths","description":"Optional reference files (blob-storage object paths) shown to the operator and fed to the agent."},"step_type":{"anyOf":[{"$ref":"#/components/schemas/StepType"},{"type":"null"}],"description":"Strategy for this step. Omit to let the server infer it from a legacy ``agent_id`` (matching a step-type sentinel picks that type; anything else picks ``vision_agent``)."},"step_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Step Config","description":"Per-type configuration. For ``vision_agent``: ``{'agent_id': '...', ...agent-params}``. For ``selection``: ``{'options': [...], 'use_selection_as_criteria': bool, 'allow_adhoc_option': bool, 'use_as_report_heading': bool, 'report_heading_level': 1|2|3}``. A selection flagged as a report heading prints its captured value as a section heading in the job report PDF and starts a new table beneath it, instead of appearing as a row. Legacy clients may omit this and instead send ``agent_id`` + ``agent_parameters`` at the top level; the server will fold them into ``step_config`` for you. Maximum 32,768 bytes when JSON-serialized, nested at most 32 levels deep."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Deprecated. Legacy top-level field; new clients put the agent id inside ``step_config``. Folded into ``step_config`` on save when supplied.","examples":["object-detection-v1"]},"agent_parameters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Parameters","description":"Deprecated. Legacy top-level field; new clients put agent parameters inside ``step_config``. Folded into ``step_config`` on save when supplied.","examples":[null,{"object_type":"items"}]},"pass_criteria":{"anyOf":[{"$ref":"#/components/schemas/PassCriteria"},{"type":"null"}],"description":"Optional pass/fail rule set evaluated against this step's agent response by the deferred analysis. Null (default) makes the step informational."},"notification_rule":{"anyOf":[{"$ref":"#/components/schemas/StepNotificationRule"},{"type":"null"}],"description":"Optional rule naming who to tell about this step's outcome when the job finalises. Null (default) means the step notifies nobody."},"allow_multiple_images":{"type":"boolean","title":"Allow Multiple Images","description":"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.","default":true}},"type":"object","title":"WorkflowDraftStepCreateOrUpdate","description":"A step in a draft save. Deliberately lenient.\n\nA draft captures work-in-progress, so every field is optional and no\nagent-registry or \"config matches type\" validation runs here. The\nstrict shape (:class:`WorkflowStepCreateOrUpdate`) is only applied at\npublish time, so an unfinished step (no agent chosen yet, empty name)\nround-trips through a draft without a 422. Legacy top-level\n``agent_id`` / ``agent_parameters`` are still folded into\n``step_config`` so a draft and a normal save serialise identically.\nThe byte/depth guard on ``step_config`` is kept because it protects\nthe server regardless of lifecycle status."},"WorkflowDraftUpdate":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name","description":"Draft name (may be blank until published).","default":""},"description":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Description","description":"Optional long-form description of the workflow's purpose."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag values (system or org-custom) for filtering/grouping."},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"Allow completing the job with uncaptured steps.","default":true},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"Allow capturing steps in any order.","default":true},"allow_step_failure":{"type":"boolean","title":"Allow Step Failure","description":"Strict (False) fails the job on any failing step; threshold (True) uses fail_threshold_percent.","default":false},"fail_threshold_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Fail Threshold Percent","description":"Percentage of steps that can fail before workflow fails (0-100).","default":0},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"Allow the operator to append ad-hoc steps during a run.","default":true},"notification_rule":{"$ref":"#/components/schemas/JobNotificationRule","description":"Rule naming who to tell about the job's own outcome. Defaults to notifying the job owner whenever a job finishes."},"report_config":{"$ref":"#/components/schemas/ReportConfig","description":"Report-presentation settings for this workflow, stored live on the family row and read when a report is generated. Defaults to the current report (image size Small)."},"steps":{"items":{"$ref":"#/components/schemas/WorkflowDraftStepCreateOrUpdate"},"type":"array","maxItems":200,"title":"Steps","description":"Ordered steps (may be empty for a draft; at most 200)."}},"type":"object","title":"WorkflowDraftUpdate","description":"Overwrite an existing draft in place. Same lenient shape as create."},"WorkflowPromote":{"properties":{"is_tiliter_created":{"type":"boolean","title":"Is Tiliter Created","description":"Superuser-only. Flag the promoted workflow as Tiliter-authored so it sorts ahead in the picker. Rejected (403) for non-superusers.","default":false}},"type":"object","title":"WorkflowPromote","description":"Promote an org's workflow into a global, public system workflow."},"WorkflowRead":{"properties":{"id":{"type":"string","title":"Id","description":"Stable family id, unchanged across edits."},"name":{"type":"string","title":"Name","description":"Current human-readable workflow name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional long-form description of the workflow's purpose."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag values (system or org-custom) for filtering/grouping."},"is_system":{"type":"boolean","title":"Is System","description":"True when this is a system workflow visible to every org."},"system_source":{"anyOf":[{"type":"string","enum":["registry","user"]},{"type":"null"}],"title":"System Source","description":"Provenance of a system workflow: 'registry' for a built-in seeded into the environment, 'user' for one promoted from an org's workflow. Null for non-system workflows."},"is_tiliter_created":{"type":"boolean","title":"Is Tiliter Created","description":"True when this system workflow is Tiliter-authored. Tiliter-created entries sort ahead of community-promoted ones in the picker. Always false for non-system workflows.","default":false},"is_deleted":{"type":"boolean","title":"Is Deleted","description":"True when the workflow has been soft-deleted.","default":false},"status":{"$ref":"#/components/schemas/WorkflowStatus","description":"Lifecycle status. 'active' is a normal, runnable workflow. 'draft' is a work-in-progress that is not runnable and not counted against the plan cap: it can only be edited, published, or discarded.","default":"active"},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"When True, the job can be completed with steps left uncaptured. When False, every step must be captured first.","default":true},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"When True, steps may be captured in any order. When False, they must be captured in ascending sort_order.","default":true},"allow_step_failure":{"type":"boolean","title":"Allow Step Failure","description":"Selects the job pass policy. When False (strict), any step that fails its pass_criteria or errors makes the job's result FAILED. When True (threshold), failures are tolerated until the percentage of evaluable steps that failed exceeds ``fail_threshold_percent``. Either way the job's lifecycle status is unaffected -- only a technical error makes a job COMPLETED_WITH_ERRORS.","default":false},"fail_threshold_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Fail Threshold Percent","description":"Maximum percentage (0-100) of evaluable steps that may fail before the job is FAILED when ``allow_step_failure`` is True. Ignored when ``allow_step_failure`` is False.","default":0},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"When True, the operator may append ad-hoc steps during a run.","default":true},"notification_rule":{"$ref":"#/components/schemas/JobNotificationRule","description":"Rule naming who to tell about the job's own outcome when it finalises."},"owner_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner User Id","description":"Owning user id. Null for system workflows."},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id","description":"Owning organization id. Null for personal workflows."},"parent_workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Workflow Id","description":"Set on an edit draft (a 'draft' row that stages changes to a live workflow): the id of the live workflow these edits will publish to. Null on active workflows and on brand-new-workflow drafts."},"unpublished_draft_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unpublished Draft Id","description":"On an active workflow, the id of the caller's own in-progress edit draft for it, if one exists (so the client can offer 'resume editing' and show an 'unpublished changes' indicator). Null when the caller has no edit draft for this workflow."},"step_count":{"type":"integer","title":"Step Count","description":"Number of steps in the workflow.","default":0},"active_job_count":{"type":"integer","title":"Active Job Count","description":"Number of non-completed jobs currently running against this workflow.","default":0},"access_user_count":{"type":"integer","title":"Access User Count","description":"Number of distinct users who have access to this workflow via group membership or direct ownership. Zero for system workflows.","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the workflow was first created."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the workflow was last modified."},"steps":{"items":{"$ref":"#/components/schemas/WorkflowStepRead"},"type":"array","title":"Steps","description":"Ordered list of steps in the workflow."},"report_config":{"$ref":"#/components/schemas/ReportConfig","description":"Report-presentation settings for this workflow, read live when a report is generated. Defaults to the current report (image size Small)."}},"type":"object","required":["id","name","is_system","notification_rule","created_at"],"title":"WorkflowRead","description":"Full workflow payload including its steps."},"WorkflowStatus":{"type":"string","enum":["active","draft","deleted"],"title":"WorkflowStatus","description":"Lifecycle state of a workflow row.\n\nTransitions: ``draft`` -> ``active`` -> ``deleted``.\nNon-draft rows skip to ``active`` on creation."},"WorkflowStepCreateOrUpdate":{"properties":{"step_key":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Step Key","description":"Stable identity for this logical step. The editor mints this (a uuid) so reference files attached to a not-yet-saved step survive the save. Omit for steps that carry no reference files; the server then assigns one. Reference files themselves are managed through the dedicated reference-file endpoints, never through this payload."},"sort_order":{"type":"integer","minimum":0.0,"title":"Sort Order","description":"Zero-based position of this step within the workflow. Must be unique within the request.","examples":[0]},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable step title shown to the operator.","examples":["Overview shot"]},"workflow_note":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Workflow Note","description":"Optional guidance shown to the operator before they capture the image."},"reference_file_paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Paths","description":"Optional reference files (blob-storage object paths) shown to the operator and fed to the agent."},"step_type":{"anyOf":[{"$ref":"#/components/schemas/StepType"},{"type":"null"}],"description":"Strategy for this step. Omit to let the server infer it from a legacy ``agent_id`` (matching a step-type sentinel picks that type; anything else picks ``vision_agent``)."},"step_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Step Config","description":"Per-type configuration. For ``vision_agent``: ``{'agent_id': '...', ...agent-params}``. For ``selection``: ``{'options': [...], 'use_selection_as_criteria': bool, 'allow_adhoc_option': bool, 'use_as_report_heading': bool, 'report_heading_level': 1|2|3}``. A selection flagged as a report heading prints its captured value as a section heading in the job report PDF and starts a new table beneath it, instead of appearing as a row. Legacy clients may omit this and instead send ``agent_id`` + ``agent_parameters`` at the top level; the server will fold them into ``step_config`` for you. Maximum 32,768 bytes when JSON-serialized, nested at most 32 levels deep."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Deprecated. Legacy top-level field; new clients put the agent id inside ``step_config``. Folded into ``step_config`` on save when supplied.","examples":["object-detection-v1"]},"agent_parameters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Parameters","description":"Deprecated. Legacy top-level field; new clients put agent parameters inside ``step_config``. Folded into ``step_config`` on save when supplied.","examples":[null,{"object_type":"items"}]},"pass_criteria":{"anyOf":[{"$ref":"#/components/schemas/PassCriteria"},{"type":"null"}],"description":"Optional pass/fail rule set evaluated against this step's agent response by the deferred analysis. Null (default) makes the step informational."},"notification_rule":{"anyOf":[{"$ref":"#/components/schemas/StepNotificationRule"},{"type":"null"}],"description":"Optional rule naming who to tell about this step's outcome when the job finalises. Null (default) means the step notifies nobody."},"allow_multiple_images":{"type":"boolean","title":"Allow Multiple Images","description":"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.","default":true}},"type":"object","required":["sort_order","name"],"title":"WorkflowStepCreateOrUpdate","description":"A step in a create/update request.\n\nNew clients send ``step_type`` and ``step_config``. Legacy clients\nthat still send top-level ``agent_id`` / ``agent_parameters`` are\naccepted transparently: a ``model_validator`` folds those fields\ninto ``step_config`` and infers ``step_type`` from the value of\n``agent_id`` (matching the selection sentinel picks ``selection``;\neverything else picks ``vision_agent``)."},"WorkflowStepRead":{"properties":{"id":{"type":"string","title":"Id","description":"Stable id of this step. Useful as a React key on edit forms."},"step_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Step Key","description":"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":{"type":"integer","title":"Sort Order","description":"Zero-based position of this step within the workflow.","examples":[0]},"name":{"type":"string","title":"Name","description":"Human-readable step title shown to the operator.","examples":["Overview shot"]},"workflow_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Note","description":"Optional guidance shown to the operator before they capture the image.","examples":["Stand back and capture the whole subject in frame."]},"reference_file_paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Paths","description":"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.","examples":[null,["user-1/abc123.jpg"]]},"reference_file_urls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reference File Urls","description":"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.","examples":[null,["https://storage.googleapis.com/...signed..."]]},"step_type":{"$ref":"#/components/schemas/StepType","description":"Strategy that produces this step's output: ``vision_agent`` captures an image and runs an inference agent; ``selection`` records an operator-picked option. New strategies extend this enum plus a registry entry.","default":"vision_agent"},"step_config":{"additionalProperties":true,"type":"object","title":"Step Config","description":"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``, ``use_selection_as_criteria``, ``allow_adhoc_option``, and the report-heading pair ``use_as_report_heading`` + ``report_heading_level``. Empty ``{}`` when the type has no configuration."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"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.","examples":["object-detection-v1"]},"agent_parameters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Parameters","description":"Deprecated. Prefer ``step_config``. Mirrored from ``step_config`` with the ``agent_id`` key stripped for vision-agent steps.","examples":[null,{"object_type":"items"}]},"pass_criteria":{"anyOf":[{"$ref":"#/components/schemas/PassCriteria"},{"type":"null"}],"description":"Optional pass/fail rule set the deferred analysis evaluates against this step's agent response. Null means the step is informational (no verdict)."},"notification_rule":{"anyOf":[{"$ref":"#/components/schemas/StepNotificationRule"},{"type":"null"}],"description":"Optional rule naming who to tell about this step's outcome when the job finalises. Null means the step notifies nobody."},"allow_multiple_images":{"type":"boolean","title":"Allow Multiple Images","description":"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.","default":true}},"type":"object","required":["id","sort_order","name"],"title":"WorkflowStepRead","description":"A single step in a workflow."},"WorkflowStepReferenceFileRead":{"properties":{"id":{"type":"string","title":"Id","description":"Id of this reference file, used to delete it."},"url":{"type":"string","title":"Url","description":"Short-lived signed download URL for the file."},"sort_order":{"type":"integer","title":"Sort Order","description":"Display order of the file within its step.","examples":[0]}},"type":"object","required":["id","url","sort_order"],"title":"WorkflowStepReferenceFileRead","description":"A reference file attached to a workflow step.\n\nThe server owns the underlying blob path and never exposes it; the\nclient renders ``url`` (a short-lived signed download URL) and uses\n``id`` to delete the file."},"WorkflowStepsReplace":{"properties":{"steps":{"items":{"$ref":"#/components/schemas/WorkflowStepCreateOrUpdate"},"type":"array","maxItems":200,"minItems":1,"title":"Steps","description":"New ordered list of steps. Replaces the workflow's existing steps. At most 200 entries."}},"type":"object","required":["steps"],"title":"WorkflowStepsReplace","description":"Replace the workflow's step list."},"WorkflowSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Stable family id, unchanged across edits."},"name":{"type":"string","title":"Name","description":"Current human-readable workflow name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional long-form description of the workflow's purpose."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag values (system or org-custom) for filtering/grouping."},"is_system":{"type":"boolean","title":"Is System","description":"True when this is a system workflow visible to every org."},"system_source":{"anyOf":[{"type":"string","enum":["registry","user"]},{"type":"null"}],"title":"System Source","description":"Provenance of a system workflow: 'registry' for a built-in seeded into the environment, 'user' for one promoted from an org's workflow. Null for non-system workflows."},"is_tiliter_created":{"type":"boolean","title":"Is Tiliter Created","description":"True when this system workflow is Tiliter-authored. Tiliter-created entries sort ahead of community-promoted ones in the picker. Always false for non-system workflows.","default":false},"is_deleted":{"type":"boolean","title":"Is Deleted","description":"True when the workflow has been soft-deleted.","default":false},"status":{"$ref":"#/components/schemas/WorkflowStatus","description":"Lifecycle status. 'active' is a normal, runnable workflow. 'draft' is a work-in-progress that is not runnable and not counted against the plan cap: it can only be edited, published, or discarded.","default":"active"},"allow_step_skip":{"type":"boolean","title":"Allow Step Skip","description":"When True, the job can be completed with steps left uncaptured. When False, every step must be captured first.","default":true},"allow_out_of_order_steps":{"type":"boolean","title":"Allow Out Of Order Steps","description":"When True, steps may be captured in any order. When False, they must be captured in ascending sort_order.","default":true},"allow_step_failure":{"type":"boolean","title":"Allow Step Failure","description":"Selects the job pass policy. When False (strict), any step that fails its pass_criteria or errors makes the job's result FAILED. When True (threshold), failures are tolerated until the percentage of evaluable steps that failed exceeds ``fail_threshold_percent``. Either way the job's lifecycle status is unaffected -- only a technical error makes a job COMPLETED_WITH_ERRORS.","default":false},"fail_threshold_percent":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Fail Threshold Percent","description":"Maximum percentage (0-100) of evaluable steps that may fail before the job is FAILED when ``allow_step_failure`` is True. Ignored when ``allow_step_failure`` is False.","default":0},"allow_adhoc_step":{"type":"boolean","title":"Allow Adhoc Step","description":"When True, the operator may append ad-hoc steps during a run.","default":true},"notification_rule":{"$ref":"#/components/schemas/JobNotificationRule","description":"Rule naming who to tell about the job's own outcome when it finalises."},"owner_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner User Id","description":"Owning user id. Null for system workflows."},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id","description":"Owning organization id. Null for personal workflows."},"parent_workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Workflow Id","description":"Set on an edit draft (a 'draft' row that stages changes to a live workflow): the id of the live workflow these edits will publish to. Null on active workflows and on brand-new-workflow drafts."},"unpublished_draft_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unpublished Draft Id","description":"On an active workflow, the id of the caller's own in-progress edit draft for it, if one exists (so the client can offer 'resume editing' and show an 'unpublished changes' indicator). Null when the caller has no edit draft for this workflow."},"step_count":{"type":"integer","title":"Step Count","description":"Number of steps in the workflow.","default":0},"active_job_count":{"type":"integer","title":"Active Job Count","description":"Number of non-completed jobs currently running against this workflow.","default":0},"access_user_count":{"type":"integer","title":"Access User Count","description":"Number of distinct users who have access to this workflow via group membership or direct ownership. Zero for system workflows.","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the workflow was first created."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the workflow was last modified."}},"type":"object","required":["id","name","is_system","notification_rule","created_at"],"title":"WorkflowSummary","description":"Lightweight list-view payload (no steps)."},"WorkflowTagOption":{"properties":{"value":{"type":"string","title":"Value","description":"Normalised tag value (lower-cased)."},"is_system":{"type":"boolean","title":"Is System","description":"True for the global system vocabulary, False for org-custom tags."}},"type":"object","required":["value","is_system"],"title":"WorkflowTagOption","description":"A selectable tag in the org's vocabulary (system or custom)."},"WorkflowUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name","description":"New human-readable name. Omit to leave unchanged."},"description":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Description","description":"New long-form description. Omit to leave unchanged."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"New full tag list (replaces the existing tags). Omit to leave unchanged."},"allow_step_skip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Step Skip","description":"New value for the skip-steps flag. Omit to leave unchanged."},"allow_out_of_order_steps":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Out Of Order Steps","description":"New value for the capture-order flag. Omit to leave unchanged."},"allow_step_failure":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Step Failure","description":"New value for the allow-step-failure flag. Omit to leave unchanged."},"fail_threshold_percent":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Fail Threshold Percent","description":"New fail threshold percentage (0-100). Omit to leave unchanged."},"allow_adhoc_step":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Adhoc Step","description":"New value for the add-steps-during-run flag. Omit to leave unchanged."},"notification_rule":{"anyOf":[{"$ref":"#/components/schemas/JobNotificationRule"},{"type":"null"}],"description":"New job-outcome notification rule. Omit to leave unchanged. To stop notifying, send a rule with ``enabled`` false."},"is_tiliter_created":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Tiliter Created","description":"Superuser-only. Set the Tiliter-authored ordering flag on a system workflow. Omit to leave unchanged. Rejected (403) for non-superusers."},"report_config":{"anyOf":[{"$ref":"#/components/schemas/ReportConfig"},{"type":"null"}],"description":"New report-presentation settings for this workflow. Omit to leave unchanged. Applies live to every report for this workflow (past, in-progress, and future jobs) the next time each is generated."}},"type":"object","title":"WorkflowUpdate","description":"In-place patch for workflow display metadata."}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Pass the key in the `X-API-Key` header on every request. Contact support to obtain an API key."}}},"security":[{"ApiKeyAuth":[]}],"servers":[{"url":"https://api.ai.vision.tiliter.com","description":"Vision AI"}]}