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

# Create a campaign on a table (agentic)

> Agentic campaign creation. Minimal body `{ instructions }` — no
column or channel; campaigns can be multi-channel and the agent
infers the rest from the instructions and the table. Delegates to
the chat agent via the v2 run path and responds `202 Accepted` with
`{ agent, run, table }` (poll `GET /agents/{agentId}/runs/{runId}`
for progress/result). The agent creates the campaign and drafts its
sequences (it does not send); once the run completes, the drafted
campaign shows up under `GET /tables/{tableId}/campaigns`. The
pre-restructure `POST /tables/{tableId}/sequences` spelling is the
legacy alias.




## OpenAPI

````yaml /openapi-v2.yaml post /tables/{tableId}/campaigns
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:
  /tables/{tableId}/campaigns:
    parameters:
      - $ref: '#/components/parameters/TableId'
    post:
      tags:
        - Campaigns
      summary: Create a campaign on a table (agentic)
      description: |
        Agentic campaign creation. Minimal body `{ instructions }` — no
        column or channel; campaigns can be multi-channel and the agent
        infers the rest from the instructions and the table. Delegates to
        the chat agent via the v2 run path and responds `202 Accepted` with
        `{ agent, run, table }` (poll `GET /agents/{agentId}/runs/{runId}`
        for progress/result). The agent creates the campaign and drafts its
        sequences (it does not send); once the run completes, the drafted
        campaign shows up under `GET /tables/{tableId}/campaigns`. The
        pre-restructure `POST /tables/{tableId}/sequences` spelling is the
        legacy alias.
      operationId: createTableCampaign
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - instructions
              properties:
                instructions:
                  type: string
                  minLength: 1
                  maxLength: 10000
                  description: Free-form description of the campaign to draft.
      responses:
        '202':
          description: Run admitted; the agent drafts the campaign in the background.
          content:
            application/json:
              schema:
                type: object
                required:
                  - agent
                  - run
                  - table
                properties:
                  agent:
                    $ref: '#/components/schemas/AgentSummary'
                  run:
                    $ref: '#/components/schemas/Run'
                  table:
                    type: object
                    required:
                      - object
                      - id
                      - name
                    properties:
                      object:
                        type: string
                        const: table
                      id:
                        type: string
                        format: uuid
                      name:
                        type: string
        '400':
          description: VALIDATION_ERROR.
        '402':
          description: INSUFFICIENT_CREDITS.
        '404':
          description: TABLE_NOT_FOUND.
        '409':
          description: AGENT_BUSY.
        '429':
          description: CONCURRENT_LIMIT_EXCEEDED.
components:
  parameters:
    TableId:
      in: path
      name: tableId
      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:
    AgentSummary:
      type: object
      required:
        - object
        - id
        - name
        - workspaceId
        - createdAt
      properties:
        object:
          type: string
          const: agent
        id:
          type: string
          format: uuid
        name:
          type: string
        workspaceId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
    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'
    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.
    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.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: og_live_...

````