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

# Send a follow-up run

> Admits a new run on an existing agent. Same agent, same
workspace, same conversation context. Responds `202 Accepted`
with the initial run object; agent work runs in the
background. Use this to answer a `needs_input` question — the
agent picks up from the prior conversation.




## OpenAPI

````yaml /openapi-v2.yaml post /agents/{id}/runs
openapi: 3.1.0
info:
  title: Origami Agent API
  version: '2.0'
  description: >
    The Origami **v2 API** is the single canonical public surface,

    organized into five segments — **Projects** (tenancy), **Agents**,

    **Workspace**, **Outreach** (campaigns, sequences, steps), and

    **Account**. Drive AI workers from a prompt, read and write table

    data (rows, cells, upsert, enrichment runs), manage documents and

    workspaces, control the sequencer and campaigns, run recurring

    scheduled agents, and read account/credit state.


    **Objects are self-describing** (Stripe-style): every object in a

    response carries an `object` field naming its type (`"agent"`,

    `"run"`, `"table"`, `"campaign"`, ...), and every list endpoint

    returns the one list envelope

    `{ "object": "list", "items": [...], "nextCursor": string|null, "url":
    string }`.


    **v1 is deprecated** (still fully functional — no removal date in

    this release). New integrations should target v2 only. See the

    [v1 → v2 migration guide](./api-v1-to-v2-migration) for the route

    map; in short:


    | v1 | v2 |

    | --- | --- |

    | `GET /tables` | `GET /api/v2/tables` |

    | `GET /tables/:id/rows` | `GET /api/v2/tables/:tableId/rows` |

    | `POST /tables/:id/rows` (insert) | `POST
    /api/v2/tables/:tableId/rows/upsert` (upsert; no bare insert) |

    | `GET /batches/:id` | `GET /api/v2/enrichment-runs/:runId` (`GET
    /api/v2/batches/:batchId` remains a deprecated alias) |

    | `GET /credits` | `GET /api/v2/account/credits` |


    Errors use the canonical envelope `{ error, code, details?, handoff? }`;

    any 4xx the user can fix in-app carries a forwardable `handoff` link.


    ## Tenancy — parent-wide keys & the `x-origami-project` header


    Every API key is **parent-wide**: bound to the parent (agency)

    organization and able to act on the parent and any of its

    **projects** (child orgs). Send the

    **`x-origami-project: <projectId>`** header on any v2 request to

    scope it to that project — all org-scoped resources (workspaces,

    tables, agents, outreach) then operate inside the project. Omit the

    header to act on the parent. Two exceptions: `/projects/*` manages

    the projects themselves from the parent (the header is ignored

    there, never rejected), and `/account` is always parent-scoped.


    The header fails closed: a malformed id →

    `400 VALIDATION_ERROR`; an unknown, cross-parent, or deleted

    project → `404 PROJECT_NOT_FOUND`. The plan gate

    (`canUseApi`) and request-rate limits stay keyed to the parent, and

    the concurrent-agent pool is shared across the whole parent.

    Credits spent inside a project draw on the parent's shared wallet,

    subject to the project's optional `monthlyCredits` budget cap.


    ## Transport — async by default


    `POST /agents` and `POST /agents/:id/runs` respond `202 Accepted`

    with the initial run object (`status: "running"`) as soon as the

    run is admitted. The actual agent work runs in the background.

    Callers poll `GET /agents/:id/runs/:runId` until `status !==

    "running"` to read the terminal state.


    Every endpoint returns plain `application/json`. There is no

    NDJSON, no heartbeats, no `tail -1`, no pre-stream vs in-stream

    error branches. Closing the HTTP socket has no effect on the run

    — there is no `abortOnDisconnect` knob.


    SDKs hide the polling — `await origami.agents.create({ prompt })`

    returns the final terminal `Run` object. Direct REST consumers

    poll `GET /agents/:id/runs/:runId` and honor the `Retry-After`

    header it returns while the run is still `running` (currently

    `15` seconds). The header is omitted on terminal responses —

    that absence is the signal to stop polling. Runs typically take

    1–5 minutes; polling faster than the hint does not make a run

    finish sooner.


    ## Pagination — cursor only


    Every list endpoint takes the same parameter pair — `limit`

    (default 50, max 100; row queries allow up to 200) and `cursor`

    (opaque; pass back the previous page's `nextCursor`) — and returns

    the one envelope `{ object: "list", items, nextCursor, url }`.

    `nextCursor: null` marks the last page. There is no

    `page`/`pageSize` and no `offset`. Per-endpoint filters (e.g.

    `?search=`, `?status=`, `?enabled=`) compose with `cursor`/`limit`.


    ## Idempotency


    Any `POST` may send an **`Idempotency-Key`** header (1–255

    printable ASCII chars). The first response for a key is recorded

    and replayed verbatim for retries with the same key + request for

    24 hours; replayed responses carry the `Idempotency-Replay: true`

    header. Reusing a key with a *different* method/path/body — or

    racing a still-in-flight original — is `409 IDEMPOTENCY_ERROR`.

    Row upserts additionally dedup on the body `batchId`

    (domain-level idempotency, independent of transport retries).


    ## Rate limits


    Same layers as v1: **per IP** (300 req/min) and **per organization**

    (100 req/min). The real scarce resource for agent runs is the

    per-org concurrent-agent slot — see `CONCURRENT_LIMIT_EXCEEDED`.


    | Scope | Limit |

    |-------|-------|

    | Per client IP | **300 requests / minute** |

    | Per organization | **100 requests / minute** |

    | Concurrent runs (per organization) | **plan-tunable** (1 on starter, 3 on
    pro, 10 on scale, 20 on ultra, unlimited on enterprise) |


    Rate-limit headers (`X-RateLimit-Limit-{IP|Global}`,

    `X-RateLimit-Remaining-*`, `X-RateLimit-Reset-*`) and `Retry-After`

    are inherited from v1.


    ## Plan-tier policy


    Agent-loop knobs (`maxSteps`, `timeoutSeconds`) are **not** request

    parameters — they're internal config gated by the plan tier. The

    chat agent's intrinsic 20-minute `AGENT_TIMEOUT_MS` is the wall-

    clock ceiling.


    | Plan | Default model | Available models | internal maxSteps |

    | --- | --- | --- | --- |

    | `starter` | `origami-lite` | `origami-lite` | 30 |

    | `pro` | `origami-max` | `origami-lite`, `origami-max` | 50 |

    | `scale` | `origami-max` | `origami-lite`, `origami-max` | 75 |

    | `ultra` | `origami-max` | `origami-lite`, `origami-max` | 100 |

    | `enterprise` | `origami-max` | `origami-lite`, `origami-max` | 200 |


    ## Errors


    All error codes are `UPPERCASE_SNAKE_CASE` and live on `code`. The

    response body always has `error` (human-readable) and `code`;

    error-specific fields (e.g. `creditsRequired`, `topUpUrl`,

    `currentRun`, `activeSessions`) live alongside at the top level

    when they're stable parts of the contract.


    Non-`completed` terminal *run* states (`timed_out`, `errored`,

    `cancelled`, `needs_input`, `step_cap_hit`) are surfaced on the

    `Run` object's single `status` field — not as HTTP error codes.

    Callers branch on the run object.


    Notable codes beyond the per-endpoint ones: `IDEMPOTENCY_ERROR`

    (409 — `Idempotency-Key` reused with a different request, or the

    original is still in flight), `TOO_MANY_ROWS` (413 — a CSV upsert

    exceeds the 100-row per-request cap), `CAMPAIGN_NOT_FOUND` (404 —

    missing or cross-org campaign id), `SEQUENCE_NOT_FOUND` (404 —

    missing or cross-org sequence id), and `PROJECT_NOT_FOUND` (404 —

    the `x-origami-project` header names an unknown, cross-parent, or

    deleted project).


    ## Webhooks


    Sequencer events fan out via HTTPS POSTs you configure in

    **Settings → Developers → Webhooks**. See the

    [webhooks reference](/webhooks/overview) and the machine-readable

    [openapi-webhooks.yaml](/openapi-webhooks.yaml) spec for working

    receiver scaffolding.
servers:
  - url: https://origami.chat/api/v2
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Projects
    description: >-
      Projects (child orgs) under the parent org — list, get, create, update,
      delete. Managed from the parent; the `x-origami-project` header is ignored
      on these paths.
  - name: Agents
    description: Create, list, and archive AI workers (a.k.a. agents).
  - name: Runs
    description: Send prompts to an agent, poll status, and cancel.
  - name: Tables
    description: Read table state and lifetime credit cost.
  - name: Workspace
    description: Workspaces, tables, columns, rows, cells, enrichment runs, and documents.
  - name: Campaigns
    description: >-
      First-class outreach campaigns — list, get, read people + stats, create +
      edit (agentic), launch/pause/resume, delete.
  - name: Sequences
    description: >-
      Read per-recipient sequences (with steps inline), stop and delete them.
      Content edits go through the agentic campaign edit.
  - name: Scheduled agents
    description: Recurring agents (cron) — CRUD, enable/disable, trigger, run history.
  - name: Account
    description: Org account state — plan, capabilities, credit balance.
paths:
  /agents/{id}/runs:
    parameters:
      - $ref: '#/components/parameters/AgentId'
    post:
      tags:
        - Runs
      summary: Send a follow-up run
      description: |
        Admits a new run on an existing agent. Same agent, same
        workspace, same conversation context. Responds `202 Accepted`
        with the initial run object; agent work runs in the
        background. Use this to answer a `needs_input` question — the
        agent picks up from the prior conversation.
      operationId: sendRun
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendRunRequest'
      responses:
        '202':
          description: Run admitted; agent work is in the background.
          content:
            application/json:
              schema:
                type: object
                required:
                  - run
                properties:
                  run:
                    $ref: '#/components/schemas/Run'
        '400':
          $ref: '#/components/responses/ValidationError'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '404':
          $ref: '#/components/responses/AgentNotFound'
        '409':
          $ref: '#/components/responses/AgentBusy'
        '429':
          $ref: '#/components/responses/ConcurrentLimit'
components:
  parameters:
    AgentId:
      in: path
      name: id
      required: true
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      description: |
        Transport-level replay protection — any v2 `POST` may send it
        (1–255 printable ASCII characters; UUIDs recommended). The first
        response for a key is recorded and replayed verbatim for retries
        with the same key + method/path/body for 24 hours; replayed
        responses carry the `Idempotency-Replay: true` response header.
        Reusing a key with a *different* request — or retrying while the
        original is still in flight — returns `409 IDEMPOTENCY_ERROR`.
        5xx responses are never recorded (the retry re-executes).
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: ^[\x21-\x7e]{1,255}$
  schemas:
    SendRunRequest:
      type: object
      required:
        - prompt
      properties:
        prompt:
          type: string
          minLength: 1
          maxLength: 10000
        focusTableIds:
          type: array
          items:
            type: string
            format: uuid
          maxItems: 50
          default: []
        attachments:
          type: array
          maxItems: 20
          default: []
          items:
            $ref: '#/components/schemas/UploadedRunAttachment'
          description: See CreateAgentRequest.attachments.
        model:
          type: string
          enum:
            - origami-lite
            - origami-max
    Run:
      allOf:
        - $ref: '#/components/schemas/RunSummary'
        - type: object
          required:
            - workspaceId
            - request
            - response
            - todo
          properties:
            workspaceId:
              type: string
              format: uuid
            request:
              $ref: '#/components/schemas/RunRequest'
            response:
              nullable: true
              allOf:
                - $ref: '#/components/schemas/RunResponse'
            todo:
              $ref: '#/components/schemas/RunTodo'
    UploadedRunAttachment:
      oneOf:
        - type: object
          required:
            - kind
            - documentId
          properties:
            kind:
              type: string
              enum:
                - document
            documentId:
              type: string
              format: uuid
        - type: object
          required:
            - kind
            - tableId
          properties:
            kind:
              type: string
              enum:
                - table
            tableId:
              type: string
              format: uuid
      description: >-
        A document (injected as an attached file) or a table (merged into
        focusTableIds).
    RunSummary:
      type: object
      required:
        - object
        - id
        - agentId
        - status
        - prompt
        - model
        - steps
        - startedAt
      properties:
        object:
          type: string
          const: run
        id:
          type: string
        agentId:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/RunStatus'
        prompt:
          type: string
          description: |
            The user prompt that drove this run, truncated to 200
            characters with an ellipsis when longer. Suitable for a
            run-history list item; for the full prompt use Get Run.
        model:
          type: string
          enum:
            - origami-lite
            - origami-max
          description: |
            Public model id the run actually executed on. When the run
            has no assistant message yet (still admitting), falls back
            to the plan default.
        steps:
          $ref: '#/components/schemas/RunSteps'
        startedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true
    RunRequest:
      type: object
      required:
        - prompt
        - model
        - focusTableIds
      properties:
        prompt:
          type: string
        model:
          type: string
          enum:
            - origami-lite
            - origami-max
          description: |
            Public model id — `origami-lite` or `origami-max`. Echoes
            the caller's request (after legacy-alias normalization)
            or the plan default when the request omitted `model`.
            Internal chat-agent ids (e.g. `origami-lobotomized`) are
            never surfaced here.
        focusTableIds:
          type: array
          items:
            type: string
            format: uuid
          description: Echoed verbatim from the request.
    RunResponse:
      type: object
      required:
        - text
        - actions
        - tables
        - transcript
        - transcriptTruncated
      description: |
        `null` while `status === "running"`. Once terminal, an object
        with the agent's cleaned prose, the structured workspace
        mutations it performed (`actions[]`), the full TableObjects
        for every touched table (`tables[]`), and (optionally) the
        full message transcript. `tables[]` is the resolved version
        of `actions[].tableId` — same shape as
        `GET /api/v2/tables/{id}`, fanned out server-side. Pass
        `?include=stats` on the Get Run call to attach economics on
        each embedded TableObject.
      properties:
        text:
          type: string
          nullable: true
          description: |
            Cleaned user-facing assistant text — internal markup
            (`<internal>`, `<plan>`, `<suggestions>`, history-tool
            markers) is stripped server-side, identical to the
            stripping in `transcript` content. `null` when `status`
            is `errored` or `timed_out` (we suppress partial prose on
            failed runs).
        actions:
          type: array
          items:
            $ref: '#/components/schemas/RunAction'
          description: |
            Structured workspace-mutation actions the agent performed,
            in the order they fired. Projected from the persisted
            `data-*` parts the chat-agent's execute-code event
            listener writes onto each assistant message — the same
            audit trail the desktop UI renders. Empty when the run
            did no workspace mutations.
        tables:
          type: array
          items:
            $ref: '#/components/schemas/Table'
          description: |
            Full TableObjects for every table this run touched
            (derived from `actions[].tableId` plus any
            `focusTableIds` the caller passed). Same shape as a
            single `GET /api/v2/tables/{id}` response. Empty when
            the run touched no tables.
        transcriptTruncated:
          type: boolean
          description: |
            True when `transcript` was truncated to its hard cap.
            Always present (defaults to `false`) so consumers don't
            have to do a presence-check on every poll.
        transcript:
          type: array
          items:
            $ref: '#/components/schemas/TranscriptMessage'
          nullable: true
          description: |
            Full message history projected onto the surface a desktop
            user with **dev mode off** would see. Specifically:

              - Assistant text is stripped of internal markup
                (`<internal>`, `<plan>`, `<suggestions>`, history-tool
                markers).
              - Tool calls are limited to the interactive widgets a
                non-dev user actually sees: `ask-questions`,
                `present-plan`, `contact-support`,
                `suggest-document-write`. The code the agent ran
                (`execute-code`), the VFS reads (`list-dir`,
                `read-file`, `grep`), and operational tools
                (`flag-session`) are dropped along with their results.
              - `reasoning` parts (dev-only in the UI) are dropped.
              - Empty assistant bubbles (text that was entirely
                internal markup and no public tool calls) are dropped.

            Opt-in only via `?include=transcript` on
            `GET /agents/{id}/runs/{runId}`. Default is `null`.
    RunTodo:
      type: object
      required:
        - pendingQuestions
        - nextActions
      properties:
        pendingQuestions:
          type: array
          items:
            $ref: '#/components/schemas/PendingQuestion'
          description: |
            Structured questions extracted from the agent's most
            recent `ask-questions` tool call, if any. When non-empty
            AND the chat_stream completed cleanly, `status` is
            promoted to `"needs_input"`. Answer by sending a
            follow-up run on the same agent
            (`POST /agents/{id}/runs`) with the user's answer as
            `prompt` — any free-text string is accepted; the agent
            parses it as natural language.
        nextActions:
          type: array
          items:
            $ref: '#/components/schemas/NextAction'
          description: |
            Structured next-action suggestions extracted from the agent's
            `<suggestions><action ...>...</action></suggestions>` block,
            if any. Surface the `label` to the user; act on `type` (when
            present) as a typed CTA the host can fire directly.
    ErrorEnvelope:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
        code:
          type: string
        details:
          type: object
          additionalProperties: true
        handoff:
          type: object
          description: |
            Present on 4xx errors the caller's user can resolve in-app — a
            forwardable link on the canonical app origin.
          required:
            - kind
            - url
            - label
          properties:
            kind:
              type: string
            url:
              type: string
              format: uri
            label:
              type: string
    RunStatus:
      type: string
      enum:
        - running
        - completed
        - needs_input
        - step_cap_hit
        - incomplete
        - cancelled
        - errored
        - timed_out
      description: |
        Single discriminator for the run's lifecycle. `needs_input`
        means the run completed cleanly but the agent's last
        assistant message included an `ask-questions` tool call;
        answer by sending a follow-up run on the same agent.
        `incomplete` means the model finished the step but the AI
        SDK could not parse a tool call it tried to emit (or it hit
        the output-token cap mid-tool-call), so the multi-step loop
        ended without the work being done — recoverable by sending
        a follow-up run on the same agent. Deploy restarts are
        auto-followed server-side, so callers never observe a
        `superseded` value.
    RunSteps:
      type: object
      required:
        - completed
        - max
      description: |
        Agent step progress. `completed` is the number of multi-step
        loop iterations the agent has actually executed; `max` is the
        plan-determined hard cap. Once `completed === max`, the run's
        `status` becomes `step_cap_hit`.
      properties:
        completed:
          type: integer
          minimum: 0
        max:
          type: integer
          minimum: 1
    RunAction:
      type: object
      required:
        - type
        - tableId
      description: |
        A single workspace-mutation event the agent performed. Discriminated
        by `type`; `tableId` is always present. Other fields are type-specific.
        v2 wire vocabulary speaks "leads", not "rows": the row-mutation
        action types are `leads_added`, `leads_deleted`, `leads_restored`.
      properties:
        type:
          type: string
          enum:
            - table_created
            - column_added
            - columns_updated
            - columns_deleted
            - leads_added
            - leads_deleted
            - leads_restored
            - columns_restored
            - table_restored
        tableId:
          type: string
          format: uuid
        tableName:
          type: string
          nullable: true
          description: Present on `table_created` and `table_restored`.
        columnId:
          type: string
          format: uuid
          nullable: true
          description: Present on `column_added`.
        columnName:
          type: string
          nullable: true
          description: Present on `column_added`.
        count:
          type: integer
          description: |
            Present on `columns_deleted`, `leads_deleted`, `leads_restored`,
            `columns_restored`. Number of items affected.
        leadCount:
          type: integer
          nullable: true
          description: Present on `leads_added`. Number of leads inserted.
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: Present on `columns_deleted` and `leads_deleted`.
    Table:
      type: object
      required:
        - object
        - id
        - workspaceId
        - name
        - leadCount
        - columns
        - credits
        - cells
        - url
        - createdAt
        - updatedAt
      properties:
        object:
          type: string
          const: table
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
        leadCount:
          type: integer
          description: |
            Non-deleted row count. v2 wire vocabulary speaks "leads"
            instead of "rows"; the underlying DB column is still
            `rows`.
        columns:
          type: array
          items:
            $ref: '#/components/schemas/TableColumn'
        credits:
          $ref: '#/components/schemas/CreditsLifetime'
          description: |
            Lifetime credit cost across every cell. Equals the sum of
            every `columns[].credits.lifetimeUsed`.
        cells:
          $ref: '#/components/schemas/CellsLiveness'
          description: |
            Table-level liveness rollup — sums every
            `columns[].cells`. `running > 0` means at least one cell
            on the table is still being processed (e.g., enrichment
            kicked off by a recent run hasn't fully settled). The
            calling agent should treat this as "still working", not
            "no data found".
        url:
          type: string
          format: uri
          description: >-
            Deep link a human can open. For programmatic row reads, use `GET
            /api/v2/tables/{tableId}/rows`.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        stats:
          $ref: '#/components/schemas/TableStats'
          description: Present only when caller passes `?include=stats`.
    TranscriptMessage:
      type: object
      properties:
        role:
          type: string
          enum:
            - user
            - assistant
            - tool
        content:
          type: string
        toolCalls:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              input: {}
        toolCallId:
          type: string
        name:
          type: string
        result:
          type: string
        truncated:
          type: boolean
    PendingQuestion:
      type: object
      required:
        - type
        - question
        - suggestedAnswers
        - freeformOption
      properties:
        type:
          type: string
          enum:
            - single-choice
            - confirm
          description: |
            Question type the agent emitted. `single-choice` lists
            options the user is expected to pick from; `confirm` is a
            single approval prompt.
        question:
          type: string
          description: Natural-language question text to surface to the user.
        suggestedAnswers:
          type: array
          items:
            type: string
          description: |
            Answers the agent proposed, in the order it listed them.
            For `single-choice` these are the agent's options; for
            `confirm` it is the single-entry array `["Confirm"]`. This
            is NOT a hard menu — the caller may always reply with any
            free-text via the follow-up run (see `freeformOption`).
            The field is here so a host LLM can bias its follow-up
            `prompt` toward what the agent expected.
        freeformOption:
          type: string
          description: |
            A stable, agent-agnostic CTA string clients can render as
            a final "your own answer" button next to `suggestedAnswers`.
            Always the literal `"Or something else"` in v2.0.
          example: Or something else
    NextAction:
      type: object
      required:
        - label
      properties:
        label:
          type: string
          description: Natural-language label the agent suggested.
        type:
          type: string
          enum:
            - upload_csv
            - find_more_leads
            - export_csv
            - input_required
            - send_messages
            - schedule_play
          description: |
            Optional typed CTA the host can act on directly. When
            absent, treat as a plain text suggestion the user can
            ask the agent to do next.
        tableSlug:
          type: string
          description: |
            Optional table reference for table-scoped types (e.g.
            `export_csv`).
    TableColumn:
      type: object
      required:
        - object
        - id
        - name
        - type
        - kind
        - slug
        - autoTrigger
        - credits
        - cells
      properties:
        object:
          type: string
          const: column
        id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          description: |
            Raw DB column type. Common values: `static` (user-entered), `code`
            (user-TS that runs per row, includes enrichments, scoring, and
            sequence columns), plus other internal-typed kinds for future
            extensibility. Treat as an open string. Prefer `kind` for
            classification — it splits `code` columns into `enrichment`,
            `score`, and `sequence`.
        kind:
          $ref: '#/components/schemas/ColumnKind'
          description: |
            Sequence-aware classification of this column. `sequence`
            columns draft outbound sequences — find them here rather than
            inspecting `type` (they share the `code` DB type with
            enrichments). Sequence columns are created via the agent or
            column code, never set through the upsert API.
        slug:
          type: string
          nullable: true
          description: Stable url-safe slug, or null when the column has none.
        autoTrigger:
          type: boolean
          description: |
            True when the column runs automatically on new leads.
            False for columns that only run when explicitly invoked
            (e.g. exports, manual-trigger code columns).
        credits:
          $ref: '#/components/schemas/CreditsLifetime'
          description: |
            Per-column historical cost. The table-level
            `credits.lifetimeUsed` is the sum of every column's
            `credits.lifetimeUsed` — the per-column breakdown is the
            primary surface for "which column is driving cost?".
        cells:
          $ref: '#/components/schemas/CellsLiveness'
          description: |
            Per-column liveness. `running > 0` means cells in this
            column are still being processed; the agent should not
            conclude "data not found" on empty cells until
            `running === 0`.
        stats:
          $ref: '#/components/schemas/ColumnStats'
          description: Present only when caller passes `?include=stats`.
        qualification:
          $ref: '#/components/schemas/ColumnQualification'
          description: |
            Present only when caller passes `?include=stats` AND this
            column has a relevance condition (i.e. acts as a quality
            filter).
    CreditsLifetime:
      type: object
      required:
        - lifetimeUsed
      properties:
        lifetimeUsed:
          type: integer
          description: |
            Sum of all settled cell-run charges, in credits. Stable
            and additive — once a row enrichment finishes it never
            moves backwards. Reservations (in-flight cell_runs) are
            not included.
    CellsLiveness:
      type: object
      required:
        - running
        - errored
      description: |
        Snapshot of cell-pipeline activity. The v2 run-finished signal
        is independent of the cell-pipeline-finished signal — when a
        run completes, downstream cells often still enrich in the
        background. Surfacing these counts lets the agent distinguish
        "still working on it" from "tried and failed", which is the
        difference between waiting and reporting a negative result.
      properties:
        running:
          type: integer
          minimum: 0
          description: |
            Number of cells currently being processed (a `cell_run` is
            in `waiting`, `queued`, or `running` for that cell).
            `running > 0` means the agent should NOT report "no data
            found" — wait and re-read.
        errored:
          type: integer
          minimum: 0
          description: |
            Number of cells that hit a settled failure. Includes
            `errored`, `out_of_credits`, `subscription_required`, and
            `connection_required`. The user-driven `stopped` state is
            intentionally excluded — it's a cancellation, not a
            failure to surface.
    TableStats:
      type: object
      required:
        - creditsPerLead
        - creditsPerQualifiedLead
        - findMoreEstimatedCredits
        - qualification
        - funnel
        - running
        - leadSources
        - hasUnknownTotalWithMore
      description: |
        Forward-looking table economics — the same numbers the desktop
        UI shows at the top of the table. Only present on responses
        that opted in via `?include=stats`.
      properties:
        creditsPerLead:
          type: number
          description: Estimated credits per sourced lead (pre-qualification).
        creditsPerQualifiedLead:
          type: number
          description: |
            Estimated credits per qualified lead, including the loss
            from dedup, exclusion, and filter cascade.
        findMoreEstimatedCredits:
          type: number
          description: Per-execution cost of the "Find More" code, in credits.
        qualification:
          $ref: '#/components/schemas/TableQualificationStats'
          nullable: true
          description: |
            Null when the table has no relevance conditions configured.
        funnel:
          $ref: '#/components/schemas/TableFunnelStats'
        running:
          $ref: '#/components/schemas/TableRunningStats'
        leadSources:
          type: array
          items:
            $ref: '#/components/schemas/TableLeadSourceStats'
        hasUnknownTotalWithMore:
          type: boolean
          description: |
            True when ≥1 active lead source has unknown TAM AND
            `has_more=true` (e.g. Twitter / LinkedIn post search).
            Consumers should render "X found (more available)" instead
            of a numeric TAM when set.
    ColumnKind:
      type: string
      enum:
        - input
        - enrichment
        - score
        - sequence
      description: |
        Sequence-aware API classification of a column:
          * `input` — user-entered (`static`) column; the only kind writable
            via `POST /tables/{tableId}/rows/upsert`.
          * `enrichment` — `code` column that runs per row to fetch/compute a value.
          * `score` — relevance / fit-score column.
          * `sequence` — `code` column that drafts an outbound sequence
            (`ctx.upsertSequence(...)` / `.draftMessage(...)`, or one that
            already has sequences). List its sequences via
            `GET /tables/{tableId}/sequences`. Created via the agent or column
            code only — never via the upsert API.
    ColumnStats:
      type: object
      required:
        - avgCreditsPerRun
        - callRate
        - totalRuns
      properties:
        avgCreditsPerRun:
          type: number
          description: Average credits charged when this column actually runs.
        callRate:
          type: number
          minimum: 0
          maximum: 1
          description: Fraction of leads that triggered this column (0-1).
        totalRuns:
          type: integer
          description: Lifetime count of cell runs feeding the average.
    ColumnQualification:
      type: object
      required:
        - pass
        - fail
        - unsure
        - total
      properties:
        pass:
          type: integer
        fail:
          type: integer
        unsure:
          type: integer
        total:
          type: integer
    TableQualificationStats:
      type: object
      required:
        - rate
        - fetchRate
        - qualifiedLeads
        - effectiveLeadsPerQualified
        - estimatedQualifiedLeads
      properties:
        rate:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
        fetchRate:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
          description: Filter-only qualification rate (excludes dedup / exclusion).
        qualifiedLeads:
          type: integer
        effectiveLeadsPerQualified:
          type: number
          description: Empirical leads-processed per qualified lead.
        estimatedQualifiedLeads:
          type: integer
          nullable: true
    TableFunnelStats:
      type: object
      required:
        - totalSourcedLeads
        - postStaticLeads
        - dedupedLeads
        - dedupRate
        - excludedLeads
        - excludedRate
      properties:
        totalSourcedLeads:
          type: integer
        postStaticLeads:
          type: integer
          description: Leads surviving dedup + exclusion + required static filters.
        dedupedLeads:
          type: integer
        dedupRate:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
        excludedLeads:
          type: integer
        excludedRate:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
    TableRunningStats:
      type: object
      required:
        - runningLeads
        - oldestActiveRunStartedAt
      properties:
        runningLeads:
          type: integer
          description: Leads with active (queued / running / waiting) cell runs.
        oldestActiveRunStartedAt:
          type: string
          format: date-time
          nullable: true
    TableLeadSourceStats:
      type: object
      required:
        - id
        - name
        - avgCreditsPerLead
        - totalCredits
        - totalLeads
        - source
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        avgCreditsPerLead:
          type: number
        totalCredits:
          type: number
        totalLeads:
          type: integer
        source:
          type: string
          enum:
            - historical
            - estimate
          description: |
            `historical` when the average is derived from real
            cost_events; `estimate` when it was computed by static
            regex analysis of the lead source's TypeScript code.
  responses:
    ValidationError:
      description: |
        Bad request body or semantic validation failure. Route-level
        codes include `VALIDATION_ERROR`, `MODEL_NOT_AVAILABLE`,
        `UNKNOWN_WORKSPACE`, `UNKNOWN_TABLE`, and
        `WORKSPACE_TABLE_MISMATCH`.
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ErrorEnvelope'
              - example:
                  error: Invalid request body
                  code: VALIDATION_ERROR
                  details:
                    issues:
                      - path: prompt
                        message: Required
    InsufficientCredits:
      description: |
        Org out of credits at admission time. `402` can also be
        `SUBSCRIPTION_REQUIRED` when the plan does not include API
        access; both shapes use the same `{ error, code }` envelope.
      content:
        application/json:
          schema:
            type: object
            required:
              - error
              - code
            properties:
              error:
                type: string
              code:
                type: string
                enum:
                  - INSUFFICIENT_CREDITS
                  - SUBSCRIPTION_REQUIRED
              creditsRequired:
                type: integer
              creditsAvailable:
                type: integer
              topUpUrl:
                type: string
              message:
                type: string
              hint:
                type: string
            example:
              error: Insufficient credits
              code: INSUFFICIENT_CREDITS
              creditsRequired: 5
              creditsAvailable: 2
              topUpUrl: https://origami.chat/settings/billing?source=api
              message: |
                Origami needs at least 5 credits to start any run; you have 2.
                Top up: https://origami.chat/settings/billing?source=api
    AgentNotFound:
      description: Agent not found (or belongs to another org)
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ErrorEnvelope'
              - example:
                  error: Agent not found
                  code: AGENT_NOT_FOUND
    AgentBusy:
      description: A run is already in flight on this agent
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              code:
                type: string
                enum:
                  - AGENT_BUSY
              currentRun:
                type: object
                properties:
                  id:
                    type: string
                  startedAt:
                    type: string
                    format: date-time
    ConcurrentLimit:
      description: Org-wide concurrent-agent cap reached (shared with the Origami UI).
      headers:
        Retry-After:
          schema:
            type: integer
          description: >-
            Seconds. Median wall-clock of the last 200 terminal runs in this org
            (fallback 60).
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              code:
                type: string
                enum:
                  - CONCURRENT_LIMIT_EXCEEDED
              limit:
                type: integer
              activeSessions:
                type: array
                items:
                  type: object
                  properties:
                    agentId:
                      type: string
                    name:
                      type: string
                    startedAt:
                      type: string
                      format: date-time
              canUpgrade:
                type: boolean
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: og_live_...

````