Coda → Local Backup Guide for Claude Code
Last updated: 2026-03-07
Goal
Build and maintain a repeatable local backup script that exports:
- every owned Coda doc (or optionally all accessible docs)
- every page as Markdown (
.md) - every table and view as CSV (
.csv) - useful metadata JSON files alongside the backups
The result should be something I can run again later with a single script command.
What the Coda API supports right now
Confirmed capabilities
Use the public Coda API to:
-
List docs
Endpoint family:/docs -
List pages within a doc
Endpoint family:/docs/{docId}/pages -
Export page content as Markdown or HTML
Start export:POST /docs/{docId}/pages/{pageId}/export
Poll export:GET /docs/{docId}/pages/{pageId}/export/{requestId}
Output formats:markdownorhtml -
List tables / views
Endpoint family:/docs/{docId}/tables -
List columns for each table/view
Endpoint family:/docs/{docId}/tables/{tableId}/columns -
List rows for each table/view
Endpoint family:/docs/{docId}/tables/{tableId}/rows
Important constraints
- The Coda API lists docs that are accessible by the token holder and that the user has opened at least once.
- For a backup focused on “my stuff,” the script should default to owned docs only using
isOwner=true. - Page export is asynchronous:
- first request starts the export
- second endpoint must be polled until the export is done
- then the script downloads the temporary
downloadLink
- Coda uses pagination widely via
nextPageToken - Coda has rate limits, so the script must back off and retry on HTTP 429
Recommended backup strategy
Default behavior
By default, the script should:
- back up owned docs only
- export all pages, including hidden pages
- export both base tables and views
- save page content as
.md - save table/view data as
.csv - save metadata as
.json - keep a local manifest to support incremental runs later
Why this approach
This gives me:
- human-readable backups for narrative/canvas content (
.md) - spreadsheet-friendly structured data (
.csv) - enough metadata to reconstruct context later
- a clean workflow for Claude Code to improve or extend later
Output structure
Use this exact structure or something very close to it:
coda-backups/
latest -> 2026-03-07T101530Z/ # optional symlink or copied pointer file
2026-03-07T101530Z/
summary.json
manifest.json
docs/
Product Launch Hub__AbCDeFGH/
doc.json
pages.json
tables.json
pages/
Launch Status__canvas-IjkLmnO.md
Launch Status/
Subpage Name__canvas-XyZ123.md
tables/
table/
Launch Status__Tasks__grid-pqRst-U.csv
Launch Status__Tasks__grid-pqRst-U.columns.json
Launch Status__Tasks__grid-pqRst-U.table.json
view/
Launch Status__Open Tasks__grid-abc123.csv
Launch Status__Open Tasks__grid-abc123.columns.json
Launch Status__Open Tasks__grid-abc123.table.jsonFile naming rules
- sanitize filenames for Windows/macOS/Linux compatibility
- keep names human-readable
- append immutable Coda IDs to avoid collisions
- preserve parent page / parent table context where practical
Requirements for the script
1) Authentication
Token source
Prefer this order:
CODA_API_TOKENenvironment variable--token-file /path/to/token.txt- fail with a clear error if neither is provided
Token creation
The token is created in Coda account settings under API settings.
2) Command-line interface
The script should support these flags:
python3 coda_backup.py --output ./coda-backups
python3 coda_backup.py --output ./coda-backups --incremental
python3 coda_backup.py --output ./coda-backups --include-shared
python3 coda_backup.py --output ./coda-backups --skip-hidden-pages
python3 coda_backup.py --output ./coda-backups --workspace-id ws-123
python3 coda_backup.py --output ./coda-backups --strict-latest
python3 coda_backup.py --output ./coda-backups --no-viewsSuggested flags
--output--token-file--include-shared--workspace-id--skip-hidden-pages--no-views--incremental--strict-latest--log-level--max-retries
3) Docs backup logic
List docs
Call:
GET /docs- default params:
isOwner=true- optional
workspaceId limitset reasonably high
- follow
nextPageTokenuntil exhausted
Save doc metadata
For each doc:
- save the raw doc object to
doc.json - include at least:
idnamebrowserLinkownerownerNamecreatedAtupdatedAtdocSize
Incremental behavior
If --incremental is on:
- compare current doc
updatedAtwith prior manifest - still inspect pages/tables, because some APIs expose object-level timestamps more reliably than top-level doc summaries
4) Pages backup logic
List pages
For each doc:
GET /docs/{docId}/pages- paginate until complete
- save raw list to
pages.json
Page filtering
By default include:
- visible pages
- hidden pages
- subpages
If --skip-hidden-pages is passed:
- skip any page where
isEffectivelyHidden == true
Export each page to Markdown
For each page:
-
POST /docs/{docId}/pages/{pageId}/exportbody:{"outputFormat":"markdown"} -
Poll:
GET /docs/{docId}/pages/{pageId}/export/{requestId} -
When completed:
- read
downloadLink - download the Markdown file immediately
- save to disk as
.md
- read
Polling behavior
- sleep ~1 second between polls
- max wait should be reasonable, e.g. 60-120 seconds
- if timeout/failure happens:
- log error
- record failure in summary
- continue with next page
Incremental behavior for pages
If --incremental is enabled:
- compare page
updatedAtfrom current page listing with prior manifest - skip page export if unchanged and output file already exists
5) Tables / views backup logic
List tables
For each doc:
GET /docs/{docId}/tables- request both:
tableview
- save raw list to
tables.json
If --no-views is passed:
- still list tables, but export only objects where
tableType == "table"
Why include views by default
Views can represent meaningful filtered or curated slices that I may want preserved separately, even if they duplicate some base-table data.
Export table metadata
For each table/view:
- save the raw table object to:
tables/{tableType}/...table.json
Export columns
For each table/view:
GET /docs/{docId}/tables/{tableId}/columns- paginate until complete
- save columns metadata to:
...columns.json
Export rows
For each table/view:
GET /docs/{docId}/tables/{tableId}/rows- use:
valueFormat=simpleWithArrays
- do not rely on
useColumnNames=true - instead:
- fetch columns separately
- use immutable column IDs internally
- map them to human-readable CSV headers myself
CSV construction rules
Use this column order:
_row_id_row_name_row_index_created_at_updated_at_browser_link- actual Coda columns in the order returned by the columns endpoint
Handling duplicate column names
If duplicate column names exist:
- de-duplicate headers deterministically, for example:
StatusStatus__2Status__3
Serializing complex values
For CSV:
- scalars → plain values
- arrays → JSON string
- objects → JSON string
None→ empty string
Incremental behavior for tables
If --incremental is enabled:
- compare table
updatedAtwith prior manifest - if unchanged and output file exists, skip re-export
- later enhancement: use
nextSyncTokenfor true row-level delta sync
6) Rate limiting and retries
The script must gracefully handle:
429 Too Many Requests- transient
5xx - temporary page-export polling lag
Required retry strategy
Implement:
- exponential backoff
- jitter
- retry count cap
Example:
- first retry: ~1 sec
- second: ~2 sec
- third: ~4 sec
- add random jitter each time
Required behavior
- never crash the entire backup because one page or table fails
- record failures in
summary.json - continue processing remaining items
7) Manifest and summary files
manifest.json
Keep a machine-readable record of:
- run timestamp
- docs processed
- pages exported
- tables exported
- per-object
updatedAt - local output paths
- failures
Purpose:
- support
--incremental - give Claude Code a reliable state file to improve later
summary.json
Include:
- total docs found
- total docs processed
- total pages exported
- total tables exported
- total views exported
- skipped unchanged pages/tables
- failures with enough context to debug
8) Recommended implementation details
Language
Use Python 3.
Dependency preference
Prefer standard library only if practical.
If using one dependency, requests is acceptable, but standard-library-only is preferred for portability.
Path handling
Use pathlib.
Networking
Use either:
urllib.requestfrom stdlib, orrequests
Logging
Use Python logging instead of print spam.
Encoding
Always write:
- text files as UTF-8
- CSV as UTF-8 with newline handling that works cross-platform
9) Edge cases the implementation must handle
- duplicate page names
- duplicate table names
- duplicate column names
- hidden pages
- nested subpages
- empty tables
- very wide tables
- multiselect / array values
- temporary export link expiration
- shared docs vs owned docs
- docs with names that are invalid as folder names
- partial failure without aborting the whole run
10) Nice-to-have upgrades
Add these only after the basic script works:
--html-pagesoption- optional JSON dump of rows in addition to CSV
- true incremental sync using
nextSyncToken - a
latest/pointer orlatest.txt - gzip/zip compression of a completed backup run
- a dry-run mode
- parallel downloads with a safe concurrency cap
- a config file for defaults
Acceptance criteria
Claude Code should produce a script that satisfies all of these:
- I can run one command and get a full local backup
- page content ends up as
.md - tables/views end up as
.csv - script handles pagination automatically
- script handles async page export properly
- script handles 429 backoff/retries
- script does not die on one bad object
- output is organized and human-usable
- future runs can use
--incremental
Paste this into Claude Code
Use this prompt with Claude Code:
Build a production-usable Python 3 script named `coda_backup.py` that backs up my Coda account locally.
Requirements:
- Use the Coda public API.
- Default to owned docs only (`isOwner=true`), with an option to include shared docs.
- Export every page in every selected doc as Markdown by using the page export API:
- POST /docs/{docId}/pages/{pageId}/export with {"outputFormat":"markdown"}
- poll GET /docs/{docId}/pages/{pageId}/export/{requestId}
- download the resulting file from downloadLink
- Export every table and view as CSV.
- Fetch columns separately and map column IDs to stable, human-readable CSV headers.
- Do not depend on useColumnNames=true for correctness.
- Support pagination everywhere via nextPageToken.
- Implement retries with exponential backoff + jitter for 429 and transient 5xx errors.
- Support:
--output
--token-file
--include-shared
--workspace-id
--skip-hidden-pages
--no-views
--incremental
--strict-latest
--log-level
--max-retries
- Read the API token from CODA_API_TOKEN first, then fall back to --token-file.
- Save:
- doc metadata
- page list metadata
- table list metadata
- per-table metadata
- per-table columns metadata
- per-run summary.json
- per-run manifest.json
- Use pathlib.
- Prefer standard library only if reasonable.
- Keep going on partial failures and record them.
- Use UTF-8 everywhere.
- Make file and folder names safe across Windows/macOS/Linux.
- Append IDs to filenames/folders to avoid collisions.
- Include good comments and a helpful --help output.
Also add:
1. a short README section at the top of the file explaining setup and usage
2. a sample command for WSL/Linux/macOS
3. a sample command for Windows PowerShell
4. a short section listing known limitations and future improvements
Before finalizing, test the script for syntax correctness and obvious bugs.My recommended first run
WSL / Linux / macOS
export CODA_API_TOKEN="paste_your_token_here"
python3 coda_backup.py --output ./coda-backupsIncremental run
export CODA_API_TOKEN="paste_your_token_here"
python3 coda_backup.py --output ./coda-backups --incrementalPowerShell
$env:CODA_API_TOKEN="paste_your_token_here"
python .\coda_backup.py --output .\coda-backupsNotes for Claude Code
- Favor correctness and resilience over cleverness.
- Keep the first version serial and reliable.
- Avoid premature concurrency.
- Preserve enough metadata so the backup is still useful even if Coda changes later.
- Make the script easy for a non-expert to rerun.
Optional refinement after the first working version
After the first version works, ask Claude Code to:
- add a zip step
- add HTML export option for pages
- add row JSON dumps
- add sync-token-based incremental table exports
- add a simple restore-oriented manifest format
Sources to verify against while implementing
Use current official docs, especially:
- Coda API reference
- Coda help article about exporting data
- Coda account settings article for API token creation