Claude
Skills
Sign in
Back

notion-prod-checklist

Included with Lifetime
$97 forever

Execute Notion API production deployment checklist and readiness verification. Use when deploying Notion integrations to production, preparing for launch, verifying go-live readiness, or auditing an existing Notion integration. Trigger: "notion production checklist", "deploy notion integration", "notion go-live", "notion launch readiness", "notion prod audit".

Backend & APIssaasproductivitynotiondeploymentchecklist

What this skill does

# Notion API Production Deployment Checklist

## Overview

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](references/code-examples.md).

## Prerequisites

- **Node.js 18+** with `@notionhq/client` v2.x installed
- Working Notion integration tested in a development workspace
- Production Notion API token (internal) or OAuth credentials (public integration)
- Target databases and pages identified by ID
- Deployment platform configured (Vercel, Railway, AWS, etc.)

Verify SDK is installed:

```bash
node -e "const { Client } = require('@notionhq/client'); console.log('SDK loaded')" 2>/dev/null \
  || echo "MISSING: npm install @notionhq/client"
```

## Instructions

Work through each section sequentially. Mark items pass or fail. A single fail in sections 1-6 is a deployment blocker.

---

### Section 1: Token Stored in Environment Variables (Never Hardcoded)

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)
- [ ] No tokens in source code — verify: `grep -rn "ntn_\|secret_\|NOTION.*=.*ntn" --include="*.ts" --include="*.js" --include="*.env" .`
- [ ] No tokens in git history: `git log -p --all -S "ntn_" -- "*.ts" "*.js" "*.env"`
- [ ] `.env` and `.env.*` files are in `.gitignore`
- [ ] Token rotation procedure documented — who rotates, how to deploy new token without downtime

```typescript
// 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.

---

### Section 2: Integration Has Minimum Required Capabilities

Notion integrations request capability scopes at creation time. Production integrations must follow least-privilege.

- [ ] Integration capabilities reviewed at https://www.notion.so/my-integrations
- [ ] Only required capabilities enabled (no "Read user information" unless explicitly needed)
- [ ] "Insert content" vs "Update content" scoped appropriately
- [ ] No "Internal Integration Token" used for public-facing apps (use OAuth instead)

| 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.

---

### Section 3: All Target Pages/Databases Shared with Integration

The most common production issue: the integration works in dev but fails in prod because pages are not shared.

- [ ] Every database queried via `databases.query()` is shared with the integration
- [ ] Every page retrieved via `pages.retrieve()` is shared with the integration
- [ ] Parent pages for `pages.create()` are shared with the integration
- [ ] Sharing verified programmatically at deploy time (see [access verification script](references/code-examples.md))
- [ ] Sharing verification runs as part of deployment health check (not just once manually)
- [ ] Documented procedure for sharing new pages/databases post-deploy

**Fail criteria:** Any target page or database returns 404 (object_not_found) when accessed by the integration.

---

### Section 4: Rate Limit Handling (3 req/sec, Exponential Backoff)

Notion enforces a hard limit of 3 requests per second per integration. Exceeding this returns HTTP 429.

- [ ] Request rate limited to 3 req/sec maximum using a queue or semaphore
- [ ] Bulk operations use a concurrency limiter (e.g., `p-queue` with `intervalCap: 3, interval: 1000`)
- [ ] No unbounded `Promise.all()` over arrays of API calls
- [ ] Exponential backoff implemented for 429 responses (SDK handles this by default)
- [ ] Backoff caps at a reasonable maximum (e.g., 30 seconds) to avoid infinite waits
- [ ] SDK `@notionhq/client` built-in retry is enabled (default behavior, not explicitly disabled)
- [ ] Monitoring tracks 429 frequency to detect capacity issues

See [rate-limited queue setup](references/code-examples.md) for `p-queue` implementation pattern.

**Fail criteria:** Any code path that can issue more than 3 concurrent requests without queuing.

---

### Section 5: Pagination for All List Endpoints

All Notion list endpoints return paginated results (max 100 items per page). Failing to paginate silently drops data.

- [ ] Every `databases.query()` call handles pagination
- [ ] Every `blocks.children.list()` call handles pagination
- [ ] Every `search()` call handles pagination
- [ ] Every `users.list()` call handles pagination
- [ ] `page_size` explicitly set (default is 100, max is 100)
- [ ] No assumption that results fit in a single page
- [ ] Pagination tested with a database containing >100 items
- [ ] `start_cursor` passed correctly from `next_cursor` (not from offset arithmetic)

See [generic paginator](references/code-examples.md) for a reusable pagination helper.

**Fail criteria:** Any list endpoint that does not loop on `has_more === true`.

---

### Section 6: Error Handling with `isNotionClientError`

The Notion SDK provides `isNotionClientError` for typed error discrimination. Using generic catch blocks loses error context.

- [ ] All Notion API calls use try/catch with `isNotionClientError` for typed handling
- [ ] Error codes handled specifically: `object_not_found`, `validation_error`, `rate_limited`, `unauthorized`, `restricted_resource`, `conflict_error`
- [ ] Errors logged with: error code, request ID (for Notion support), and timestamp
- [ ] User-facing errors do not leak internal IDs or tokens
- [ ] 401 errors trigger immediate alerts (token revoked or expired)
- [ ] 400 validation errors include the full error body in logs
- [ ] Network errors (ECONNREFUSED, ETIMEDOUT) handled separately from API errors

See [typed error handler](references/code-examples.md) for discriminated error handling with `APIErrorCode`.

**Fail criteria:** Any API call with a bare `catch (e) { console.log(e) }` that loses error context.

---

### Section 7: Notion-Version Header Set (2022-06-28)

Notion API responses change between versions. Pinning the version prevents unexpected breaking changes.

- [ ] `notionVersion` explicitly set in Client constructor
- [ ] Version matches the one used during development and testing
- [ ] Current stable version: `2022-06-28`
- [ ] Team aware of Notion API changelog:

```typescript
const notion = new Client({
  auth: process.env.NOTION_TOKEN,
  notionVersion: '2022-06-28',  // Pin to tested version — do not omit
});
```

- [ ] If using raw HTTP calls, `Notion-Version` header is set explicitly
- [ ] API version upgrade plan documented (test in staging first, then update)

**Fail criteria:** Client created without explicit `notionVersion`, relying on SDK default that may change.

---

### Section 8: Retry Logic for 429/500/503 Responses

The `@notionhq/client` SDK retries automatically, but custom HTTP clients and edge cases need explicit retry logic.

- [ ] SDK default retry behavior verified (not disabled via constructor options)
- [ ] Custom HTTP calls (if any) implement r

Related in Backend & APIs