coda
$
npx mdskill add vm0-ai/vm0-skills/codaManage Coda documents, tables, and workspaces via API.
- Enables agents to create, read, update, and delete Coda content.
- Integrates with Coda API using Bearer token authentication.
- Executes requests when users reference Coda terms or share links.
- Returns structured data including doc metadata and workspace details.
SKILL.md
.github/skills/codaView on GitHub ↗
---
name: coda
description: Coda API for docs, tables, rows, and pages. Use when user mentions "Coda", "coda.io", shares a Coda doc link, "Coda table", "Coda doc", or asks to read/write a Coda workspace.
---
## Troubleshooting
If requests fail, run `zero doctor check-connector --env-name CODA_TOKEN` or `zero doctor check-connector --url https://coda.io/apis/v1/whoami --method GET`
## Base URL
```
https://coda.io/apis/v1
```
## Authentication
All requests use a Bearer token:
```
Authorization: Bearer $CODA_TOKEN
```
## Core APIs
### Verify Token
```bash
curl -s "https://coda.io/apis/v1/whoami" --header "Authorization: Bearer $CODA_TOKEN" | jq '{name, loginId, type, tokenName, scoped, workspace: .workspace.name}'
```
Returns the user/token info. Use this to confirm the connector is working before running other calls.
Docs: https://coda.io/developers/apis/v1#operation/whoami
### List Docs
```bash
curl -s "https://coda.io/apis/v1/docs?limit=25" --header "Authorization: Bearer $CODA_TOKEN" | jq '.items[] | {id, name, href, browserLink, ownerName}'
```
Useful query parameters:
- `isOwner=true` — only docs you own
- `query=<search-text>` — name search
- `workspaceId=<workspace-id>` — filter by workspace
- `limit=<1-100>` — page size (default 25)
- `pageToken=<token>` — follow `.nextPageToken` for pagination
Docs: https://coda.io/developers/apis/v1#operation/listDocs
### Create a Doc
Write to `/tmp/coda_doc.json`:
```json
{
"title": "Project Tracker",
"timezone": "America/Los_Angeles"
}
```
```bash
curl -s -X POST "https://coda.io/apis/v1/docs" --header "Authorization: Bearer $CODA_TOKEN" --header "Content-Type: application/json" -d @/tmp/coda_doc.json | jq '{id, name, browserLink}'
```
Optional fields:
- `sourceDoc` — copy an existing doc by ID
- `folderId` — place the doc in a specific folder (defaults to your private folder)
Docs: https://coda.io/developers/apis/v1#operation/createDoc
### Get a Doc
Replace `<your-doc-id>` with your actual doc ID (the 10-character code after `_d` in a Coda URL, e.g. `abc123xyz0`):
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{id, name, browserLink, workspace: .workspace.name, ownerName, updatedAt}'
```
Docs: https://coda.io/developers/apis/v1#operation/getDoc
### List Pages in a Doc
Replace `<your-doc-id>` with your actual doc ID:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/pages?limit=50" --header "Authorization: Bearer $CODA_TOKEN" | jq '.items[] | {id, name, browserLink, contentType, isHidden}'
```
Docs: https://coda.io/developers/apis/v1#operation/listPages
### Get a Page
Replace `<your-doc-id>` and `<your-page-id>` with actual IDs:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/pages/<your-page-id>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{id, name, subtitle, contentType, parent: .parent.name}'
```
Docs: https://coda.io/developers/apis/v1#operation/getPage
### Export Page Content
Page content export is a two-step async flow: request export, then poll for the signed download URL.
Write to `/tmp/coda_export.json`:
```json
{
"outputFormat": "markdown"
}
```
`outputFormat` accepts `html` or `markdown`.
Step 1 — request export. Replace `<your-doc-id>` and `<your-page-id>`:
```bash
curl -s -X POST "https://coda.io/apis/v1/docs/<your-doc-id>/pages/<your-page-id>/export" --header "Authorization: Bearer $CODA_TOKEN" --header "Content-Type: application/json" -d @/tmp/coda_export.json | jq '{id, status, href}'
```
Step 2 — poll until `status` is `complete`, then download. Replace `<your-request-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/pages/<your-page-id>/export/<your-request-id>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{status, downloadLink}'
```
When `status: "complete"`, fetch the content from `downloadLink` (no auth header needed — it is a pre-signed URL):
```bash
curl -s "<download-link-from-previous-response>"
```
Docs: https://coda.io/developers/apis/v1#tag/Pages/operation/beginContentExport
### List Tables in a Doc
Replace `<your-doc-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/tables?limit=50" --header "Authorization: Bearer $CODA_TOKEN" | jq '.items[] | {id, name, tableType, rowCount, browserLink}'
```
Use `tableType=table` to exclude views, or `tableType=view` to list only views.
Docs: https://coda.io/developers/apis/v1#operation/listTables
### Get a Table
Replace `<your-doc-id>` and `<your-table-id>` (the table ID from the previous call, or the table name):
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{id, name, rowCount, displayColumn: .displayColumn.name}'
```
Docs: https://coda.io/developers/apis/v1#operation/getTable
### List Columns
Replace `<your-doc-id>` and `<your-table-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/columns" --header "Authorization: Bearer $CODA_TOKEN" | jq '.items[] | {id, name, display, format: .format.type}'
```
Docs: https://coda.io/developers/apis/v1#operation/listColumns
### List Rows
Replace `<your-doc-id>` and `<your-table-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/rows?useColumnNames=true&limit=100" --header "Authorization: Bearer $CODA_TOKEN" | jq '.items[] | {id, name, values}'
```
Key query parameters:
- `useColumnNames=true` — return column names as keys in `values` (default keys are column IDs)
- `valueFormat=simpleWithArrays` — easier-to-read values (`simple`, `simpleWithArrays`, or `rich`)
- `query=<column-id-or-name>:"<value>"` — server-side filter, e.g. `query=Status:"Done"`
- `sortBy=natural` — preserve table's natural sort order
- `limit=<1-500>` and `pageToken=<token>` for pagination
Docs: https://coda.io/developers/apis/v1#operation/listRows
### Get a Single Row
Replace `<your-doc-id>`, `<your-table-id>`, and `<your-row-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/rows/<your-row-id>?useColumnNames=true" --header "Authorization: Bearer $CODA_TOKEN" | jq '{id, name, values}'
```
Docs: https://coda.io/developers/apis/v1#operation/getRow
### Insert / Upsert Rows
Rows use the `cells` array with `column` set to either the column ID or column name.
Write to `/tmp/coda_rows.json`:
```json
{
"rows": [
{
"cells": [
{"column": "Name", "value": "Design review"},
{"column": "Status", "value": "In Progress"},
{"column": "Due", "value": "2026-05-01"}
]
},
{
"cells": [
{"column": "Name", "value": "Launch plan"},
{"column": "Status", "value": "To Do"}
]
}
],
"keyColumns": ["Name"]
}
```
- Omit `keyColumns` to always insert new rows.
- Include `keyColumns` (array of column IDs/names) to upsert: rows matching on those columns are updated instead of inserted.
Replace `<your-doc-id>` and `<your-table-id>`:
```bash
curl -s -X POST "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/rows?disableParsing=false" --header "Authorization: Bearer $CODA_TOKEN" --header "Content-Type: application/json" -d @/tmp/coda_rows.json | jq '{requestId, addedRowIds}'
```
Returns `202 Accepted` with a `requestId`. Writes are asynchronous — poll `/apis/v1/docs/{docId}/mutationStatus/{requestId}` for completion if you need to block on it.
Docs: https://coda.io/developers/apis/v1#operation/upsertRows
### Update a Row
Write to `/tmp/coda_row_update.json`:
```json
{
"row": {
"cells": [
{"column": "Status", "value": "Done"}
]
}
}
```
Replace `<your-doc-id>`, `<your-table-id>`, and `<your-row-id>`:
```bash
curl -s -X PUT "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/rows/<your-row-id>" --header "Authorization: Bearer $CODA_TOKEN" --header "Content-Type: application/json" -d @/tmp/coda_row_update.json | jq '{id, requestId}'
```
Docs: https://coda.io/developers/apis/v1#operation/updateRow
### Delete Rows
Write to `/tmp/coda_delete.json`:
```json
{
"rowIds": ["i-aBcDeFg", "i-HiJkLmN"]
}
```
Replace `<your-doc-id>` and `<your-table-id>`:
```bash
curl -s -X DELETE "https://coda.io/apis/v1/docs/<your-doc-id>/tables/<your-table-id>/rows" --header "Authorization: Bearer $CODA_TOKEN" --header "Content-Type: application/json" -d @/tmp/coda_delete.json | jq '{requestId, rowIds}'
```
Docs: https://coda.io/developers/apis/v1#operation/deleteRows
### Check Mutation Status
Write operations are asynchronous and return a `requestId`. Poll this endpoint until `completed: true` before assuming a mutation is applied.
Replace `<your-doc-id>` and `<your-request-id>`:
```bash
curl -s "https://coda.io/apis/v1/docs/<your-doc-id>/mutationStatus/<your-request-id>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{completed, warning}'
```
Docs: https://coda.io/developers/apis/v1#operation/getMutationStatus
### Resolve a Coda URL
Translate a browser URL (e.g. a doc link copied from the Coda UI) into its corresponding API resource (doc, page, table, column, or row) with IDs.
```bash
curl -s "https://coda.io/apis/v1/resolveBrowserLink?url=<url-encoded-coda-link>" --header "Authorization: Bearer $CODA_TOKEN" | jq '{type, resource: .resource.id, href: .resource.href}'
```
Docs: https://coda.io/developers/apis/v1#operation/resolveBrowserLink
## Finding IDs
- **Doc ID**: the 10-character code after `_d` in a Coda URL. `https://coda.io/d/My-Doc_dABC123xyz0/...` → doc ID is `ABC123xyz0`.
- **Page ID**: last segment after `#_` in a page URL, or use **List Pages** above.
- **Table / Column / Row IDs**: prefixed strings like `grid-...`, `c-...`, `i-...`. Use the list endpoints above, or **Resolve a Coda URL** to extract them from a browser link.
Alternative to IDs: most row/table endpoints also accept the human-readable name (e.g. `Tasks` instead of `grid-xyz`). Pass `useColumnNames=true` on row reads to return column names in the `values` object.
## Value Formats
When reading rows, the `valueFormat` query parameter controls how cell values are serialized:
| Value | Description |
|---|---|
| `simple` | Scalars as primitives; references rendered as display values |
| `simpleWithArrays` | Same as `simple` but multi-value cells stay as arrays (recommended) |
| `rich` | Full structured objects with formatting metadata — verbose |
When writing rows, wrap string/number/boolean values directly: `{"column": "Name", "value": "Foo"}`. For multi-select or lists, pass an array: `{"column": "Tags", "value": ["red", "blue"]}`. Date columns accept ISO-8601 strings.
## Pagination
List endpoints return a `nextPageToken` when there are more items:
```bash
# First request
curl -s "https://coda.io/apis/v1/docs?limit=50" --header "Authorization: Bearer $CODA_TOKEN" | jq '{items: (.items | length), nextPageToken}'
# Follow-up: replace <your-page-token> with the value above
curl -s "https://coda.io/apis/v1/docs?limit=50&pageToken=<your-page-token>" --header "Authorization: Bearer $CODA_TOKEN"
```
## Rate Limits
- Most endpoints: around 100 requests/minute per token
- Bulk writes: combine multiple rows into a single upsert request rather than one request per row
- 429 response → back off and retry with exponential delay
## API Reference
- API root: https://coda.io/developers/apis/v1
- API token settings: https://coda.io/account (Account Settings → API Settings)