This proxy mirrors the Workflowy API at /api/v1/* endpoints, adding caching, rate limiting, and authentication.
Workflowy API Reference: https://beta.workflowy.com/api-reference/
If the API_KEY environment variable is set, all /api/v1/* endpoints require authentication.
Header: Authorization: Bearer <API_KEY>
Example:
curl -H "Authorization: Bearer your-api-key" http://localhost:8168/api/v1/targetsUnauthenticated endpoints (no authentication required):
GET /healthcheckGET /readyGET /version
All errors follow the Workflowy error format:
{
"error": "Error description"
}Status Codes:
400 Bad Request- Invalid request401 Unauthorized- Missing or invalid API key404 Not Found- Resource not found429 Too Many Requests- Rate limited500 Internal Server Error- Server error502 Bad Gateway- Upstream Workflowy error504 Gateway Timeout- Upstream request timeout
All responses include cache status headers:
| Header | Values | Description |
|---|---|---|
X-Cache |
HIT, MISS, STALE |
Cache status |
X-Cache-Source |
response, node-cache |
Where the cached data came from |
X-Cache-Age |
Integer | Age of cached entry in seconds |
X-Cache-Bypass-Ignored |
rate-limit |
Present when bypass was ignored |
Add X-Cache-Bypass: true header to force a fresh fetch from Workflowy.
Note: Cache bypass is ignored for rate-limited endpoints when within the rate limit window.
Health check endpoint. Always returns healthy if the server is running.
Response:
{
"status": "healthy"
}Readiness check endpoint. Returns healthy when the server is ready to accept requests.
Response:
{
"status": "healthy"
}Returns version information about the running instance.
Response:
{
"commit": "abc123def",
"date": "2024-01-15T10:30:00Z"
}Note: Returns null values if version.json is not present (local development).
The WorkFlowy API uses a bucket-based rate limit:
- Each bucket is 60 seconds long.
- Within a bucket, you can make 60 requests.
- If you exceed the limit, the API returns HTTP 429 with a message indicating how long until the current bucket resets and you can make requests again.
- Because buckets are fixed time windows (not sliding windows), up to 120 requests can succeed within ~20 seconds if timed across a bucket boundary — 60 at the end of one bucket plus 60 at the start of the next.
- The
/nodes-exportendpoint has a stricter limit of 1 request per bucket.
All endpoints share this rate limit unless noted otherwise.
All node endpoints proxy the official WorkFlowy API.
List children of a node, optionally with nested tree structure.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Query Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
parent_id |
No | (root) | ID of the parent node (use inbox for root nodes). Omit to get root nodes. |
depth |
No | 0 |
(Proxy-only) How many levels of children to nest in the response. 0 = flat list (no children expanded), 1 = include immediate children, etc. Not part of the official WorkFlowy API. |
Depth behavior (Proxy-only):
depth=0(default): Returns a flat list of nodes. Each node haschildren: nullandhas_childrenindicating whether children exist.depth=1: Returns nodes with their immediate children nested in thechildrenarray. Children at the depth limit havechildren: null.depth=N: Recursively nests children up to N levels deep.
When depth > 0, the proxy requires the export cache to build the tree. If the cache is empty, it automatically fetches /nodes-export from Workflowy first.
Nodes are sorted by priority (ascending) at every level of the tree.
Examples:
# Get root nodes (flat)
curl "http://localhost:8168/api/v1/nodes"
# Get children of a specific node
curl "http://localhost:8168/api/v1/nodes?parent_id=abc123"
# Get root nodes with one level of children
curl "http://localhost:8168/api/v1/nodes?depth=1"
# Get children of a node with 2 levels of nesting
curl "http://localhost:8168/api/v1/nodes?parent_id=abc123&depth=2"
# Workflowy-compatible style
curl -G http://localhost:8168/api/v1/nodes \
-H "Authorization: Bearer <API_KEY>" \
-d "parent_id=inbox"Response (depth=0):
{
"nodes": [
{
"id": "abc123",
"name": "Node name",
"note": null,
"parent_id": "parent123",
"priority": 1000,
"completed": false,
"layoutMode": "bullets",
"children": null,
"has_children": true,
"createdAt": 1234567890,
"modifiedAt": 1234567890,
"completedAt": null
}
]
}Response (depth=1):
{
"nodes": [
{
"id": "abc123",
"name": "Node name",
"note": null,
"parent_id": "parent123",
"priority": 1000,
"completed": false,
"layoutMode": "bullets",
"children": [
{
"id": "child456",
"name": "Child node",
"parent_id": "abc123",
"priority": 2000,
"completed": false,
"layoutMode": "bullets",
"children": null,
"has_children": false,
"createdAt": 1234567890,
"modifiedAt": 1234567890
}
],
"has_children": true,
"createdAt": 1234567890,
"modifiedAt": 1234567890,
"completedAt": null
}
]
}has_children values (Proxy-only):
| Value | Meaning |
|---|---|
true |
Node has children (known from export cache) |
false |
Node has no children (known from export cache) |
null |
Unknown (export cache not available, e.g. response served from Workflowy API directly) |
Get a single node by ID.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Example:
curl http://localhost:8168/api/v1/nodes/abc123Response:
{
"id": "abc123",
"name": "Node name",
"note": "Node note",
"parent_id": "parent123",
"completed": null,
"layout_mode": null,
"children": []
}Create a new node.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Request Body:
{
"parent_id": "parent123",
"name": "New node name",
"note": "Optional note"
}Response:
{
"status": "ok"
}Cache Effect: Invalidates parent's children cache.
Update an existing node.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Request Body:
{
"name": "Updated name",
"note": "Updated note"
}Response:
{
"status": "ok"
}Cache Effect: Invalidates the node's cache entry.
Delete a node.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Example:
curl -X DELETE http://localhost:8168/api/v1/nodes/abc123Response:
{
"status": "ok"
}Cache Effect: Invalidates the node's cache and all parent caches.
Move a node to a new parent.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Request Body:
{
"parent_id": "new_parent123",
"position": 0
}Response:
{
"status": "ok"
}Cache Effect: Invalidates all parent caches.
Mark a node as complete.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Example:
curl -X POST http://localhost:8168/api/v1/nodes/abc123/completeResponse:
{
"status": "ok"
}Cache Effect: Invalidates the node's cache entry.
Mark a node as incomplete.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Path Parameters:
| Parameter | Description |
|---|---|
id |
Node ID |
Example:
curl -X POST http://localhost:8168/api/v1/nodes/abc123/uncompleteResponse:
{
"status": "ok"
}Cache Effect: Invalidates the node's cache entry.
Export all nodes from Workflowy. This endpoint is rate limited. Proxies the official WorkFlowy API.
WorkFlowy Rate Limit: 1 request per 60-second bucket (see WorkFlowy Rate Limiting).
Proxy Rate Limit: Minimum 60 seconds between calls (configurable via NODES_EXPORT_CACHE_TTL_SECS).
Example:
curl http://localhost:8168/api/v1/nodes-exportResponse: Complete tree of all nodes.
Cache Effect:
- Caches the full export response
- Populates the node cache for fast individual lookups
Note: Cache bypass header is ignored when rate limited.
Warm the node cache without returning the full export data. Useful for pre-warming caches. This endpoint is not part of the WorkFlowy API.
Rate Limit: Same as /nodes-export.
Example:
curl http://localhost:8168/api/v1/nodes-export-cacheResponse:
{
"status": "ok",
"cached_nodes": 1234
}List all targets (shortcuts/starred items). Proxies the official WorkFlowy API.
WorkFlowy Rate Limit: 60 requests per 60-second bucket (see WorkFlowy Rate Limiting).
Example:
curl http://localhost:8168/api/v1/targetsResponse: Array of target objects.
{
"id": "string",
"name": "string | null",
"note": "string | null",
"parent_id": "string | null",
"priority": "number | null",
"completed": "boolean | null",
"layoutMode": "string | null",
"children": "Node[] | null",
"has_children": "boolean | null",
"createdAt": "number | null",
"modifiedAt": "number | null",
"completedAt": "number | null"
}| Field | Type | Description |
|---|---|---|
id |
string | Unique node identifier |
name |
string? | Node title/name |
note |
string? | Node description/note |
parent_id |
string? | Parent node ID (null for root) |
priority |
number? | Sort priority (lower = higher in list) |
completed |
boolean? | Whether the node is completed |
layoutMode |
string? | Layout mode (e.g. "bullets") |
children |
Node[]? | (Proxy-only) Child nodes (populated when depth > 0, null otherwise). Not part of the official WorkFlowy API. |
has_children |
boolean? | (Proxy-only) true if children exist, false if none, null if unknown. Not part of the official WorkFlowy API. |
createdAt |
number? | Creation timestamp (Unix seconds) |
modifiedAt |
number? | Last modification timestamp (Unix seconds) |
completedAt |
number? | Completion timestamp (Unix seconds, null if not completed) |