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:

  1. List docs
    Endpoint family: /docs

  2. List pages within a doc
    Endpoint family: /docs/{docId}/pages

  3. 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: markdown or html

  4. List tables / views
    Endpoint family: /docs/{docId}/tables

  5. List columns for each table/view
    Endpoint family: /docs/{docId}/tables/{tableId}/columns

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

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

File 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:

  1. CODA_API_TOKEN environment variable
  2. --token-file /path/to/token.txt
  3. 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-views

Suggested 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
    • limit set reasonably high
  • follow nextPageToken until exhausted

Save doc metadata

For each doc:

  • save the raw doc object to doc.json
  • include at least:
    • id
    • name
    • browserLink
    • owner
    • ownerName
    • createdAt
    • updatedAt
    • docSize

Incremental behavior

If --incremental is on:

  • compare current doc updatedAt with 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:

  1. POST /docs/{docId}/pages/{pageId}/export body:

    {"outputFormat":"markdown"}
  2. Poll: GET /docs/{docId}/pages/{pageId}/export/{requestId}

  3. When completed:

    • read downloadLink
    • download the Markdown file immediately
    • save to disk as .md

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 updatedAt from 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:
    • table
    • view
  • 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:

  1. _row_id
  2. _row_name
  3. _row_index
  4. _created_at
  5. _updated_at
  6. _browser_link
  7. 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:
    • Status
    • Status__2
    • Status__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 updatedAt with prior manifest
  • if unchanged and output file exists, skip re-export
  • later enhancement: use nextSyncToken for 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

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.request from stdlib, or
  • requests

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:

  1. --html-pages option
  2. optional JSON dump of rows in addition to CSV
  3. true incremental sync using nextSyncToken
  4. a latest/ pointer or latest.txt
  5. gzip/zip compression of a completed backup run
  6. a dry-run mode
  7. parallel downloads with a safe concurrency cap
  8. 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.

WSL / Linux / macOS

export CODA_API_TOKEN="paste_your_token_here"
python3 coda_backup.py --output ./coda-backups

Incremental run

export CODA_API_TOKEN="paste_your_token_here"
python3 coda_backup.py --output ./coda-backups --incremental

PowerShell

$env:CODA_API_TOKEN="paste_your_token_here"
python .\coda_backup.py --output .\coda-backups

Notes 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:

  1. add a zip step
  2. add HTML export option for pages
  3. add row JSON dumps
  4. add sync-token-based incremental table exports
  5. 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