A Rust-based API proxy for Workflowy with caching, rate limiting, and authentication support.
- API Proxy: Mirrors Workflowy API endpoints at
/api/v1/* - Caching: Smart caching with thundering herd prevention
- Response cache for individual requests
- Node cache populated from exports for fast lookups
- Stale cache fallback on upstream errors
- Tree Nesting:
GET /api/v1/nodes?depth=Nreturns nested children up to N levels deep, sorted by priority - Rate Limiting: Enforced rate limit for
/nodes-exportendpoint - Authentication: Optional API key authentication via Bearer token
- Cache Bypass:
X-Cache-Bypass: trueheader to force fresh data - Cache Headers:
X-Cache,X-Cache-Age,X-Cache-Sourceresponse headers
docker run -d \
-p 8168:8168 \
-e WORKFLOWY_API_KEY=your-workflowy-api-key \
ghcr.io/christianriesen/workflowy-proxy:latest# Clone the repository
git clone https://github.com/ChristianRiesen/workflowy-proxy.git
cd workflowy-proxy
# Build and run
cargo run --releaseAll configuration is done via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
WORKFLOWY_API_KEY |
Yes | - | Your Workflowy API key |
WORKFLOWY_API_URL |
No | https://workflowy.com/api |
Workflowy API base URL |
API_KEY |
No | - | Proxy authentication key (empty = no auth) |
PORT |
No | 8168 |
Server port |
READ_CACHE_TTL_SECS |
No | 60 |
Cache TTL for read operations (seconds) |
NODES_EXPORT_CACHE_TTL_SECS |
No | 60 |
Minimum interval between export calls |
REQUEST_TIMEOUT_SECS |
No | 180 |
Request timeout (seconds) |
CONNECT_TIMEOUT_SECS |
No | 5 |
Connection timeout (seconds) |
MAX_REQUEST_SIZE |
No | 52428800 |
Max request body size (50MB) |
GRACEFUL_SHUTDOWN_TIMEOUT_SECS |
No | 30 |
Graceful shutdown timeout |
PROACTIVE_CACHE_ENABLED |
No | false |
Enable proactive cache warming |
PROACTIVE_CACHE_MISS_THRESHOLD |
No | 2 |
Cache misses required to trigger proactive caching |
PROACTIVE_CACHE_WINDOW_SECS |
No | 60 |
Time window (seconds) for counting cache misses |
| Endpoint | Description |
|---|---|
GET /healthcheck |
Health check (always returns healthy) |
GET /ready |
Readiness check |
GET /version |
Version information (commit, build date) |
These endpoints proxy the official WorkFlowy API.
| Endpoint | Description | WorkFlowy Rate Limit |
|---|---|---|
GET /api/v1/nodes?parent_id=X&depth=N |
List children of a node (with optional tree nesting) | 60 per 60s bucket |
GET /api/v1/nodes/:id |
Get a single node | 60 per 60s bucket |
POST /api/v1/nodes |
Create a new node | 60 per 60s bucket |
POST /api/v1/nodes/:id |
Update a node | 60 per 60s bucket |
DELETE /api/v1/nodes/:id |
Delete a node | 60 per 60s bucket |
POST /api/v1/nodes/:id/move |
Move a node | 60 per 60s bucket |
POST /api/v1/nodes/:id/complete |
Mark node as complete | 60 per 60s bucket |
POST /api/v1/nodes/:id/uncomplete |
Mark node as incomplete | 60 per 60s bucket |
GET /api/v1/nodes-export |
Export all nodes | 1 per 60s bucket |
GET /api/v1/targets |
List targets/shortcuts | 60 per 60s bucket |
WorkFlowy Rate Limiting: The WorkFlowy API uses a bucket-based rate limit. Each bucket is 60 seconds long and allows 60 requests. When the limit is exceeded, the API returns HTTP 429 with a message indicating how long until the current bucket resets. Because buckets are fixed windows, up to 120 requests can succeed within ~20 seconds if timed across a bucket boundary (60 at the end of one bucket + 60 at the start of the next). The /nodes-export endpoint has a stricter limit of 1 request per bucket.
These endpoints are provided by the proxy and are not part of the WorkFlowy API.
| Endpoint | Description |
|---|---|
GET /api/v1/nodes-export-cache |
Warm cache without returning data |
See docs/API.md for detailed API documentation.
If API_KEY is set, all requests to /api/v1/* endpoints require a Bearer token:
curl -H "Authorization: Bearer your-api-key" http://localhost:8168/api/v1/targetsSystem endpoints (/healthcheck, /ready, /version) do not require authentication.
All responses include cache status headers:
X-Cache:HIT,MISS, orSTALEX-Cache-Source:responseornode-cacheX-Cache-Age: Age of cached entry in seconds (when applicable)X-Cache-Bypass-Ignored: Present when bypass was requested but ignored due to rate limiting
Add X-Cache-Bypass: true header to force a fresh fetch (bypasses cache).
Note: Cache bypass is ignored for rate-limited endpoints (/nodes-export) when within the rate limit window.
When multiple concurrent requests arrive for the same uncached data, only the first request fetches from Workflowy. Other requests wait and receive the cached result.
When enabled (PROACTIVE_CACHE_ENABLED=true), the proxy detects frequent cache misses and automatically triggers a /nodes-export call to warm the cache. This is useful when many different nodes are being requested that aren't yet cached.
Recommended: Enable proactive caching for optimal performance.
- Tracks cache misses within a sliding time window (
PROACTIVE_CACHE_WINDOW_SECS) - Triggers warming when miss count exceeds threshold (
PROACTIVE_CACHE_MISS_THRESHOLD) - Respects the rate limit for
/nodes-exportcalls
# Run tests
cargo test
# Run with debug logging
RUST_LOG=debug cargo run
# Format code
cargo fmt
# Run clippy
cargo clippyMIT License - see LICENSE