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, 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.
Reading boards, tables, and rows
- List boards — returns every board your token can access, with an optional
qto 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), andsort. - 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>"] }
]
}
matchisand(default) oror.conditionsis an array of up to 20 conditions. Each names acolumn_id, anoperator, and (for most operators) avalue.- 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 liketoday,last_7_days,current_month, andis_empty/is_not_empty.
- Text:
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
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 row —
POSTwith an optional markdowndescriptionand acolumnsarray (which may be empty). Returns the created row. - Update a row —
PATCHwith a non-emptycolumnsarray. 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-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 format — CSV, 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 anasyncJobdescribing the job and, once ready, adownloadUrl. You can also supply awebhookUrlto 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
- Boards introduction — orientation, Quick Start, and parity.
- Boards in the API Reference — every board, table, row, comment, authenticate, and export endpoint with request/response schemas.
- Pagination — cursor conventions used by row comments.
- Rate Limits — per-endpoint limits (exports are limited more tightly than reads).
- Copera CLI and MCP server — the same board operations from the command line and from AI clients.