Access the Coda API with managed OAuth authentication. Manage docs, pages, tables, rows, formulas, and controls with full CRUD operations.
# List your docs
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/coda/apis/v1/docs')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/coda/apis/v1/{resource}
Maton proxies requests to coda.io/apis/v1 and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Coda OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=coda&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'coda'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2026-02-12T01:38:10.500238Z",
"last_updated_time": "2026-02-12T01:38:33.545353Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "coda",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Coda connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/coda/apis/v1/docs')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
GET /coda/apis/v1/whoami
Returns information about the authenticated user.
GET /coda/apis/v1/docs
Query parameters:
isOwner - Show only owned docs (true/false)query - Search querysourceDoc - Filter by source doc IDisStarred - Show only starred docsinGallery - Show only gallery docsworkspaceId - Filter by workspacefolderId - Filter by folderlimit - Page size (default: 25, max: 200)pageToken - Pagination tokenPOST /coda/apis/v1/docs
Content-Type: application/json
{
"title": "My New Doc",
"sourceDoc": "optional-source-doc-id",
"timezone": "America/Los_Angeles",
"folderId": "optional-folder-id"
}
GET /coda/apis/v1/docs/{docId}
DELETE /coda/apis/v1/docs/{docId}
GET /coda/apis/v1/docs/{docId}/pages
Query parameters:
limit - Page sizepageToken - Pagination tokenPOST /coda/apis/v1/docs/{docId}/pages
Content-Type: application/json
{
"name": "New Page",
"subtitle": "Optional subtitle",
"parentPageId": "optional-parent-page-id"
}
GET /coda/apis/v1/docs/{docId}/pages/{pageIdOrName}
PUT /coda/apis/v1/docs/{docId}/pages/{pageIdOrName}
Content-Type: application/json
{
"name": "Updated Page Name",
"subtitle": "Updated subtitle"
}
DELETE /coda/apis/v1/docs/{docId}/pages/{pageIdOrName}
GET /coda/apis/v1/docs/{docId}/tables
Query parameters:
limit - Page sizepageToken - Pagination tokensortBy - Sort by fieldtableTypes - Filter by table typeGET /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}
GET /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/columns
Query parameters:
limit - Page sizepageToken - Pagination tokenGET /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/columns/{columnIdOrName}
GET /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/rows
Query parameters:
query - Filter rows by search queryuseColumnNames - Use column names instead of IDs in response (true/false)valueFormat - Value format (simple, simpleWithArrays, rich)sortBy - Sort by columnlimit - Page sizepageToken - Pagination tokenGET /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/rows/{rowIdOrName}
Query parameters:
useColumnNames - Use column names instead of IDsvalueFormat - Value formatPOST /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/rows
Content-Type: application/json
{
"rows": [
{
"cells": [
{"column": "Column Name", "value": "Cell Value"},
{"column": "Another Column", "value": 123}
]
}
],
"keyColumns": ["Column Name"]
}
keyColumns for upsert behavior (update if exists, insert if not)PUT /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/rows/{rowIdOrName}
Content-Type: application/json
{
"row": {
"cells": [
{"column": "Column Name", "value": "Updated Value"}
]
}
}
DELETE /coda/apis/v1/docs/{docId}/tables/{tableIdOrName}/rows/{rowIdOrName}
GET /coda/apis/v1/docs/{docId}/formulas
GET /coda/apis/v1/docs/{docId}/formulas/{formulaIdOrName}
GET /coda/apis/v1/docs/{docId}/controls
GET /coda/apis/v1/docs/{docId}/controls/{controlIdOrName}
GET /coda/apis/v1/docs/{docId}/acl/metadata
GET /coda/apis/v1/docs/{docId}/acl/permissions
POST /coda/apis/v1/docs/{docId}/acl/permissions
Content-Type: application/json
{
"access": "readonly",
"principal": {
"type": "email",
"email": "user@example.com"
}
}
Access values: readonly, write, comment
DELETE /coda/apis/v1/docs/{docId}/acl/permissions/{permissionId}
GET /coda/apis/v1/categories
GET /coda/apis/v1/resolveBrowserLink?url={encodedUrl}
Converts a Coda browser URL to API resource information.
GET /coda/apis/v1/mutationStatus/{requestId}
Check the status of an asynchronous mutation operation.
GET /coda/apis/v1/analytics/docs
Query parameters:
isPublished - Filter by published statussinceDate - Start date (YYYY-MM-DD)untilDate - End date (YYYY-MM-DD)limit - Page sizepageToken - Pagination tokenGET /coda/apis/v1/analytics/packs
GET /coda/apis/v1/analytics/updated
Coda uses cursor-based pagination with pageToken:
GET /coda/apis/v1/docs?limit=25
Response includes nextPageToken when more results exist:
{
"items": [...],
"href": "https://coda.io/apis/v1/docs?pageToken=...",
"nextPageToken": "eyJsaW1..."
}
Use the nextPageToken value as pageToken in subsequent requests.
Create, update, and delete operations return HTTP 202 with a requestId:
{
"id": "canvas-abc123",
"requestId": "mutate:9f038510-be42-4d16-bccf-3468d38efd57"
}
Check mutation status:
GET /coda/apis/v1/mutationStatus/mutate:9f038510-be42-4d16-bccf-3468d38efd57
Response:
{
"completed": true
}
Mutations are generally processed within a few seconds.
const response = await fetch(
'https://api.maton.ai/coda/apis/v1/docs?limit=10',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
console.log(data.items);
import os
import requests
response = requests.get(
'https://api.maton.ai/coda/apis/v1/docs',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'},
params={'limit': 10}
)
data = response.json()
for doc in data['items']:
print(f"{doc['name']}: {doc['id']}")
import os
import requests
headers = {'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
base_url = 'https://api.maton.ai/coda/apis/v1'
# Create doc
doc_response = requests.post(
f'{base_url}/docs',
headers=headers,
json={'title': 'My New Doc'}
)
doc = doc_response.json()
print(f"Created doc: {doc['id']}")
# Create page
page_response = requests.post(
f'{base_url}/docs/{doc["id"]}/pages',
headers=headers,
json={'name': 'First Page', 'subtitle': 'Created via API'}
)
page = page_response.json()
print(f"Created page: {page['id']}")
import os
import requests
headers = {'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
response = requests.post(
'https://api.maton.ai/coda/apis/v1/docs/{docId}/tables/{tableId}/rows',
headers=headers,
json={
'rows': [
{
'cells': [
{'column': 'Name', 'value': 'John Doe'},
{'column': 'Email', 'value': 'john@example.com'}
]
}
]
}
)
result = response.json()
print(f"Request ID: {result['requestId']}")
s0ekj2vV-vcanvas-curl -g when URLs contain brackets to disable glob parsingjq, environment variables may not expand correctly. Use Python examples instead.| Operation | Limit |
|---|---|
| ----------- | ------- |
| Reading data | 100 requests per 6 seconds |
| Writing data | 10 requests per 6 seconds |
| Writing doc content | 5 requests per 10 seconds |
| Listing docs | 4 requests per 6 seconds |
| Reading analytics | 100 requests per 6 seconds |
| Status | Meaning |
|---|---|
| -------- | --------- |
| 400 | Missing Coda connection or invalid request |
| 401 | Invalid or missing Maton API key |
| 404 | Resource not found |
| 409 | Doc not yet accessible (just created) |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Coda API |
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
coda. For example:https://api.maton.ai/coda/apis/v1/docshttps://api.maton.ai/apis/v1/docs共 2 个版本