notion-reliability-patterns
'Graceful degradation when Notion is down: offline cache, retry with
Allowed Tools
Provided by Plugin
notion-pack
Claude Code skill pack for Notion (30 skills)
Installation
This skill is included in the notion-pack plugin:
/plugin install notion-pack@claude-code-plugins-plus
Click to copy
Instructions
Notion Reliability Patterns
Overview
Production reliability patterns for Notion integrations: retry with exponential backoff, a circuit breaker to prevent cascade failures, and graceful degradation (offline cache, health checks, fallback content) so users see stale data instead of errors when the API is unreachable. All patterns use Client from @notionhq/client and handle Notion-specific error codes.
The three layers compose in order — retry sits inside the circuit breaker, which sits inside the cache/fallback layer. Read the skeletons below to follow the workflow, then open references/implementation.md for the complete copy-paste code.
Prerequisites
@notionhq/clientv2.x installed (npm install @notionhq/client)lru-cachefor in-memory caching (npm install lru-cache)- Python:
notion-clientinstalled (pip install notion-client) NOTION_TOKENenvironment variable set- Understanding of circuit breaker and retry patterns
Authentication
All patterns authenticate with a Notion internal integration token read from the NOTIONTOKEN environment variable — new Client({ auth: process.env.NOTIONTOKEN }) (TS) or Client(auth=os.environ["NOTION_TOKEN"]) (Python). Never hardcode the token; the health check calls notion.users.me() to confirm the token is valid and the API reachable.
Instructions
Build the three layers in order. Each skeleton shows the shape; the full implementation lives in references/implementation.md.
Step 1: Retry with Exponential Backoff
Classify errors as transient (429, 500, 502, 503, timeouts, network) vs permanent (400/401/404), retry only the transient ones with exponential backoff + jitter, and honor the Retry-After header on rate limits.
async function retryWithBackoff<T>(fn: () => Promise<T>, opts = {}): Promise<T> {
// maxRetries=4, baseDelayMs=1000. Loop; on transient error wait
// baseDelayMs * 2^attempt (+ jitter), else rethrow. See implementation.md.
}
Full code (TypeScript + Python, including isTransientError and rate-limit handling): references/implementation.md Step 1.
Step 2: Circuit Breaker to Prevent Cascade Failures
During a sustained outage, stop hammering the API and fail fast. The breaker moves closed → open after N consecutive transient failures, waits a reset timeout, then tests recovery in half-open before returning to closed.
class NotionCircuitBreaker {
// failureThreshold=5, resetTimeoutMs=30_000, halfOpenSuccesses=2
async execute<T>(fn: () => Promise<T>): Promise<T> { /* ...see implementation.md */ }
getState() { /* { state, failures, lastFailure } for health checks */ }
}
Full class + CircuitOpenError: references/implementation.md Step 2.
Step 3: Graceful Degradation — Cache, Health Checks, Fallback
Wrap calls so a success refreshes an LRU cache and a failure serves the last-good cached value; expose a health check and static fallback content for the cache-cold case.
async function resilientQuery<T>(cacheKey, fn, fallbackContext?) {
// circuit.execute(() => retryWithBackoff(fn)) → cache on success,
// serve cache on failure, static fallback if cache is empty.
}
Full code — queryWithFallback, notionHealthCheck, getFallbackContent, resilientQuery, and the Python equivalents: references/implementation.md Step 3.
Output
- Retry with exponential backoff handling 429, 500, 502, 503 errors
- Circuit breaker preventing cascade failures (5 failures = circuit opens)
- Offline cache serving stale data when API is unavailable
- Health check endpoint returning healthy/degraded/down status
- Fallback content for zero-downtime user experience
- Combined resilient query pattern composing all layers
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Circuit stays open | Threshold too low for occasional errors | Increase failureThreshold to 10 |
| Stale cached data | Long TTL during extended outage | Add freshness indicator in UI, reduce TTL |
CircuitOpenError in logs |
API is down, circuit protecting | Expected behavior, check status.notion.com |
| Retries not helping | Error is permanent (400/401/404) | isTransientError filters these out |
| Health check shows degraded | Notion API latency > 2s | Normal during peak load, monitor trend |
| Memory growing | Large cache | Set max on LRU cache, reduce TTL |
Examples
Operational wiring — a pollable health endpoint and Prometheus alert rules — lives in references/examples.md:
- System Health Dashboard — expose
notionHealthCheck()asGET /api/health/notion, returning 200 for healthy/degraded and 503 for down. - Monitoring Alert Rules — Prometheus alerts that page when the circuit opens (
notioncircuitstate == 2) or when >50% of requests are served from cache.
Resources
- Notion Status Page
- Notion Request Limits
- Circuit Breaker Pattern
- LRU Cache
- Full implementation · Examples
Next Steps
For governance and policy enforcement, see notion-policy-guardrails.
For scaling beyond single-token limits, see notion-load-scale.