From notion-pack
Executes production deployment checklist for Notion API integrations (Node.js/@notionhq/client), verifying auth security, rate limits, pagination, errors, monitoring, and OAuth lifecycle.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin notion-packThis skill is limited to using the following tools:
Structured 12-section checklist for deploying Notion API integrations to production. Covers authentication security, capability scoping, page sharing, rate limit compliance, pagination correctness, error handling, API versioning, retry logic, monitoring, graceful degradation, data validation, and OAuth token lifecycle. Each section maps to a specific failure mode observed in production Notion i...
Secures Notion API integrations with token storage, OAuth2 flows, least-privilege capabilities, page access auditing, and credential rotation using @notionhq/client.
Interacts with Notion API via REST: authenticate, CRUD pages/databases/blocks/comments, pagination, error handling using curl/jq or scripts. For Notion tasks.
Controls Notion via Python SDK: create/query pages and databases, append blocks (headings, paragraphs, code, callouts). Triggers on Notion API, page creation, database queries.
Share bugs, ideas, or general feedback.
Structured 12-section checklist for deploying Notion API integrations to production. Covers authentication security, capability scoping, page sharing, rate limit compliance, pagination correctness, error handling, API versioning, retry logic, monitoring, graceful degradation, data validation, and OAuth token lifecycle. Each section maps to a specific failure mode observed in production Notion integrations.
This skill produces a verified pass/fail report. Every item is actionable and testable — no aspirational guidance. Full code examples for each section are in references/code-examples.md.
@notionhq/client v2.x installedVerify SDK is installed:
node -e "const { Client } = require('@notionhq/client'); console.log('SDK loaded')" 2>/dev/null \
|| echo "MISSING: npm install @notionhq/client"
Work through each section sequentially. Mark items pass or fail. A single fail in sections 1-6 is a deployment blocker.
Production tokens must never appear in source code, config files committed to git, or client-side bundles.
NOTION_TOKEN loaded from environment variable or secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, Vercel env vars)grep -rn "ntn_\|secret_\|NOTION.*=.*ntn" --include="*.ts" --include="*.js" --include="*.env" .git log -p --all -S "ntn_" -- "*.ts" "*.js" "*.env".env and .env.* files are in .gitignore// CORRECT: Token from environment
const notion = new Client({ auth: process.env.NOTION_TOKEN });
// WRONG: Hardcoded token — immediate security incident
const notion = new Client({ auth: 'ntn_R8dkf92jfKLsd9f2...' });
Fail criteria: Any token found in source, git history, or client bundle.
Notion integrations request capability scopes at creation time. Production integrations must follow least-privilege.
| Capability | Enable if |
|---|---|
| Read content | Reading pages or databases |
| Update content | Modifying existing pages/blocks |
| Insert content | Creating new pages or appending blocks |
| Read comments | Reading page comments |
| Create comments | Adding comments to pages |
| Read user info | Resolving user names/emails (rarely needed) |
Fail criteria: Integration has capabilities it does not use in production code paths.
The most common production issue: the integration works in dev but fails in prod because pages are not shared.
databases.query() is shared with the integrationpages.retrieve() is shared with the integrationpages.create() are shared with the integrationFail criteria: Any target page or database returns 404 (object_not_found) when accessed by the integration.
Notion enforces a hard limit of 3 requests per second per integration. Exceeding this returns HTTP 429.
p-queue with intervalCap: 3, interval: 1000)Promise.all() over arrays of API calls@notionhq/client built-in retry is enabled (default behavior, not explicitly disabled)See rate-limited queue setup for p-queue implementation pattern.
Fail criteria: Any code path that can issue more than 3 concurrent requests without queuing.
All Notion list endpoints return paginated results (max 100 items per page). Failing to paginate silently drops data.
databases.query() call handles paginationblocks.children.list() call handles paginationsearch() call handles paginationusers.list() call handles paginationpage_size explicitly set (default is 100, max is 100)start_cursor passed correctly from next_cursor (not from offset arithmetic)See generic paginator for a reusable pagination helper.
Fail criteria: Any list endpoint that does not loop on has_more === true.
isNotionClientErrorThe Notion SDK provides isNotionClientError for typed error discrimination. Using generic catch blocks loses error context.
isNotionClientError for typed handlingobject_not_found, validation_error, rate_limited, unauthorized, restricted_resource, conflict_errorSee typed error handler for discriminated error handling with APIErrorCode.
Fail criteria: Any API call with a bare catch (e) { console.log(e) } that loses error context.
Notion API responses change between versions. Pinning the version prevents unexpected breaking changes.
notionVersion explicitly set in Client constructor2022-06-28const notion = new Client({
auth: process.env.NOTION_TOKEN,
notionVersion: '2022-06-28', // Pin to tested version — do not omit
});
Notion-Version header is set explicitlyFail criteria: Client created without explicit notionVersion, relying on SDK default that may change.
The @notionhq/client SDK retries automatically, but custom HTTP clients and edge cases need explicit retry logic.
Retry-After header respected when present on 429 responsesSee retry with exponential backoff for implementation pattern.
Fail criteria: Retrying 400/401/404 errors, or no retry on 429/5xx.
Production Notion integrations must have observability. Silent failures erode data integrity.
GET /health/notion)notion-incident-runbook skill for triage steps| Alert | Condition | Severity |
|---|---|---|
| Auth failure | Any 401/403 response | P1 — token may be revoked |
| High error rate | >5% of requests failing in 5min window | P2 |
| Sustained rate limiting | >10 429s in 5min | P2 — review request patterns |
| High latency | P95 > 3000ms over 5min | P3 |
| Notion outage | status.notion.com incident or >50% 5xx | P2 — activate fallback |
Fail criteria: No alerting on auth failures or sustained errors.
Notion experiences outages (check https://status.notion.com). The application must not crash when the API is unavailable.
See cache with fallback for LRU cache implementation with source tracking.
Fail criteria: Application returns 500 to end users when Notion API is unreachable.
Notion rejects malformed property values with 400 validation errors. Validate before sending.
pages.create() (required by Notion)2026-04-01 or 2026-04-01T09:00:00.000-05:00rich_text: []blocks.children.append()See property validator for a validation function that catches common issues.
Fail criteria: 400 validation errors occurring in production due to unvalidated property data.
Public integrations using OAuth must handle token lifecycle. Internal integrations can skip this section.
POST /v1/oauth/tokenbot_id and workspace_id stored alongside the access token for multi-tenant routingSee OAuth token exchange for the authorization code exchange implementation.
Fail criteria: OAuth tokens stored in plaintext, or no handling for token revocation (401 responses).
After completing all 12 sections, produce a deployment readiness report:
NOTION PRODUCTION READINESS REPORT
===================================
Date: YYYY-MM-DD
Integration: [integration name]
Environment: [production|staging]
Section 1: Token Security [PASS/FAIL]
Section 2: Capability Scoping [PASS/FAIL]
Section 3: Page/DB Sharing [PASS/FAIL]
Section 4: Rate Limit Handling [PASS/FAIL]
Section 5: Pagination [PASS/FAIL]
Section 6: Error Handling [PASS/FAIL]
Section 7: API Version Pinned [PASS/FAIL]
Section 8: Retry Logic [PASS/FAIL]
Section 9: Monitoring [PASS/FAIL]
Section 10: Graceful Degradation [PASS/FAIL]
Section 11: Data Validation [PASS/FAIL]
Section 12: OAuth (if applicable) [PASS/FAIL/N/A]
BLOCKING FAILURES (Sections 1-6): [count]
NON-BLOCKING ISSUES (Sections 7-12): [count]
VERDICT: [READY TO DEPLOY / BLOCKED — fix N items]
| Scenario | Detection | Response |
|---|---|---|
| Token not in env vars | process.env.NOTION_TOKEN is undefined | Abort deploy, log setup instructions |
| Page not shared | 404 object_not_found on retrieve | List unshared targets, block deploy |
| Rate limit exceeded | 429 response despite queueing | Reduce concurrency, check for competing integrations |
| Validation error (400) | isNotionClientError with validation_error | Log full error body, fix property data |
| Auth failure (401) | isNotionClientError with unauthorized | Alert ops, rotate token, re-deploy |
| Notion outage (5xx) | Multiple 500/502/503 in sequence | Activate cache/fallback mode |
| Property type mismatch | 400 on pages.create or pages.update | Run property validator, fix schema mapping |
| Pagination missed | Query returns exactly 100 results | Audit code for missing has_more loops |
#!/usr/bin/env bash
set -euo pipefail
echo "=== Notion Production Smoke Test ==="
# 1. Token is set
if [ -z "${NOTION_TOKEN:-}" ]; then
echo "FAIL: NOTION_TOKEN not set"
exit 1
fi
echo "PASS: NOTION_TOKEN is set (${#NOTION_TOKEN} chars)"
# 2. Token works (auth check)
AUTH_RESULT=$(curl -s -w "\n%{http_code}" \
https://api.notion.com/v1/users/me \
-H "Authorization: Bearer ${NOTION_TOKEN}" \
-H "Notion-Version: 2022-06-28")
HTTP_CODE=$(echo "$AUTH_RESULT" | tail -1)
BODY=$(echo "$AUTH_RESULT" | head -n -1)
if [ "$HTTP_CODE" = "200" ]; then
BOT_NAME=$(echo "$BODY" | jq -r '.name // "unknown"')
echo "PASS: Auth OK — bot name: $BOT_NAME"
else
echo "FAIL: Auth returned HTTP $HTTP_CODE"
echo "$BODY" | jq . 2>/dev/null || echo "$BODY"
exit 1
fi
# 3. Target database accessible (set NOTION_TARGET_DB to test)
DB_ID="${NOTION_TARGET_DB:-}"
if [ -n "$DB_ID" ]; then
DB_RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
"https://api.notion.com/v1/databases/${DB_ID}" \
-H "Authorization: Bearer ${NOTION_TOKEN}" \
-H "Notion-Version: 2022-06-28")
if [ "$DB_RESULT" = "200" ]; then
echo "PASS: Target database accessible"
else
echo "FAIL: Target database returned HTTP $DB_RESULT — is it shared with the integration?"
exit 1
fi
fi
echo "=== Smoke Test Complete ==="
See full production initialization for complete setup with rate limiting, version pinning, and log levels.
@notionhq/client on npm — Official SDK documentationAfter passing the production checklist, continue with related skills for ongoing operations. For initial setup and authentication, see notion-install-auth. For rate limit deep-dive, see notion-rate-limits. For error troubleshooting, see notion-common-errors. For incident response, see notion-incident-runbook. For API version migration, see notion-upgrade-migration. For monitoring setup, see notion-observability.