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

# List tables (cursor-paginated; ?workspaceId=)



## OpenAPI

````yaml /openapi-v2.yaml get /tables
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:
    get:
      tags:
        - Workspace
      summary: List tables (cursor-paginated; ?workspaceId=)
      operationId: listTables
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - in: query
          name: workspaceId
          description: Scope the list to one workspace.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: A page of TableObjects in the canonical list envelope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ListEnvelope'
                  - type: object
                    required:
                      - items
                    properties:
                      items:
                        type: array
                        items:
                          $ref: '#/components/schemas/Table'
components:
  parameters:
    Cursor:
      in: query
      name: cursor
      required: false
      description: |
        Opaque pagination cursor. Pass the `nextCursor` from the previous
        page to fetch the next one; omit for the first page.
      schema:
        type: string
    Limit:
      in: query
      name: limit
      required: false
      description: |
        Max objects to return per page. Default 50, max 100 (the rows
        endpoint allows up to 200 and documents its own `limit`).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
  schemas:
    ListEnvelope:
      type: object
      description: |
        The canonical v2 list envelope. Every list endpoint returns this
        shape; per-endpoint `items` element types (and documented extras
        like `total`) are declared on each operation.
      required:
        - object
        - items
        - nextCursor
        - url
      properties:
        object:
          type: string
          const: list
        items:
          type: array
          items: {}
          description: The page of objects. Element type is per-endpoint.
        nextCursor:
          type: string
          nullable: true
          description: Cursor for the next page; `null` means this is the last page.
        url:
          type: string
          description: The path this list was fetched from (query string excluded).
    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`.
    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_...

````