Skip to main content

How a Board Works

This page explains the board data model and the mechanics behind every board operation: how tables and rows relate, how typed columns and cells work, how rich-text and file attachments behave, how row comments and visibility are scoped, how row authentication works, and how filtering, sorting, and exports are evaluated. For a quick orientation and copy-paste examples, start with the Boards introduction.

The data model

Boards form a three-level hierarchy:

  • Board — the top-level container. A board groups related tables.
  • Table — belongs to a board. A table defines a set of columns and holds rows.
  • Row — a single record in a table. A row carries a cell value for each column, plus a markdown description and optional comments.
Board
└── Table (columns: Name, Status, Owner, Notes…)
├── Row #1 (cells: "Acme", "In progress", …)
├── Row #2
└── Row #3

Each row has two identifiers: an internal _id (a 24-character object id used in most endpoints) and a human-friendly row number (the small sequential number shown in the grid, e.g. 42). You can fetch a row by either one.

Columns and cells

A table's columns each have a columnId, a label, and a type. Column types include status, dropdown, labels, checkbox, text, paragraph, link, date, email, phone, website, number, duration, location, and users. Status/dropdown/labels columns expose their selectable options (each with an optionId, label, and color); status options also carry a statusGroup of TODO, IN_PROGRESS, or DONE.

When you read a row you get a columns array of cells, each keyed by columnId with a value. Link cells reference other rows; lookup cells surface values pulled from linked rows.

When you write a row you pass columns: [{ columnId, value }]. A few column types have special value semantics:

  • Link columns take an array of row-id strings. Passing [] clears the link. Linked rows must exist in the target table and the same workspace.
  • Rich-text (paragraph) columns take a markdown string. The content is seeded asynchronously, so it may appear a moment after the write returns.
  • Duration columns take a JSON number representing seconds. For example, 1800 represents 30 minutes. Formatted strings such as "00:30:00" are invalid.

Reading boards, tables, and rows

  • List boards — returns every board your token can access, with an optional q to filter by name or description.
  • Get a board / List tables / Get a table — drill into a board's structure and read a table's column definitions.
  • List rows — returns the rows of a table. Supports three query parameters that compose together: q (case-insensitive search across searchable columns), filter (a structured JSON filter, below), and sort.
  • Get a row — by internal id, or by row number using the dedicated row-number endpoint.

Filtering rows

The filter query parameter is a JSON string with this shape:

{
"match": "and",
"conditions": [
{ "column_id": "<columnId>", "operator": "contains", "value": "acme" },
{ "column_id": "<statusColumnId>", "operator": "includes", "value": ["<optionId>"] }
]
}
  • match is and (default) or or.
  • conditions is an array of up to 20 conditions. Each names a column_id, an operator, and (for most operators) a value.
  • The valid operators depend on the column's type:
    • Text: equals, not_equals, contains, not_contains, starts_with, ends_with, is_empty, is_not_empty.
    • Number: equals, not_equals, gt, gte, lt, lte, includes, not_includes, is_empty, is_not_empty.
    • Select (status / dropdown / labels): equals, not_equals, includes, not_includes, is_empty, is_not_empty (values are option ids).
    • Checkbox: equals, not_equals, is_empty, is_not_empty.
    • Date: equals, before, after, between (value is a [startISO, endISO] pair), plus relative no-value operators like today, last_7_days, current_month, and is_empty / is_not_empty.

An unknown column id or an operator that doesn't apply to the column type returns a 400.

Sorting rows

The sort query parameter is a comma-separated list of columnId:direction entries, where direction is asc (default) or desc:

?sort=statusColumnId:asc,createdColumnId:desc
note

The HTTP sort query string uses the columnId:direction form. Some SDK examples express sorting as an array of { column, dir } objects — both describe the same ordering, just in different shapes.

Writing rows

  • Create a rowPOST with an optional markdown description and a columns array (which may be empty). Returns the created row.
  • Update a rowPATCH with a non-empty columns array. Only the columns you send are changed; the rest are left untouched.
  • Delete a row — permanently removes the row.

Bots act like users: an action your token performs can trigger the board's automations, so programmatic writes integrate with the same workflows your team uses.

Row description and rich-text columns

Both a row's description and any rich-text column are markdown. Read them with the corresponding …/md GET endpoint, which returns { "content": "…" }.

To write them, POST an operation of replace, append, or prepend plus the markdown content. These writes are processed asynchronously and return HTTP 202 Accepted — the content lands a moment later.

File attachments

FILE columns store one or more private file references on the row cell. Through the Public API you can upload a new file directly to a FILE column cell with multipart/form-data, download an attached file by its fileId, or remove an attachment reference from the cell.

The upload endpoint appends the uploaded file to the existing cell value and returns the attachment metadata (fileId, name, MIME type, and size). The remove endpoint only detaches that fileId from the row cell; it does not delete the underlying private file record or bytes.

File-column cells and comment attachments can be downloaded directly. The download endpoints stream the raw file bytes (with the appropriate content-type and filename headers) rather than returning a signed URL.

Row comments

Rows support threaded comments, each with a visibility:

  • internal — visible only to workspace members.
  • external — visible to everyone with row access, including guests.

List comments supports cursor pagination (after / before) and a visibility filter of all (default), internal, or external. Create a comment takes HTML content and a visibility that defaults to internal.

Authenticating a row

Tables can act as a lightweight credential store. The authenticate endpoint takes an identifier column + value and a password column + value, and returns the matching row (with password columns masked) when the credentials are valid:

  • 400 — no row matches the identifier.
  • 401 — the password is incorrect.

This lets you build sign-in style checks against a board table without exposing the password cell.

Exporting a table view

The export endpoint renders a table view to a file. You POST the viewId and a formatCSV, XLSX, JSON, MARKDOWN, HTML, PDF, ZIP, or ICS — along with optional filters, sort, column selection, and per-format options.

Exports run synchronously or asynchronously depending on size:

  • Small exports return 200 OK with the rendered file inline (text formats as UTF-8, binary formats base64-encoded).
  • Large exports (and PDF/ZIP, which are always async, or any request with forceAsync: true) return 202 Accepted with an asyncJob describing the job and, once ready, a downloadUrl. You can also supply a webhookUrl to be notified when the file is ready.

Authentication & scope

Board endpoints accept a full Personal Access Token (cp_pat_) or an integration API key (cp_key_), and require the access_boards scope. See Authentication for token types and Error Handling for the standard error shape.

Reference