Skip to content

notion-client requests now 403 (Cloudflare) because no User-Agent is sent — workaround: pass one via ofetchOptions #710

Description

@danielrose7

Summary

As of early August 2026, Cloudflare in front of www.notion.so rejects api/v3 requests that carry no User-Agent header with a 403 (Cloudflare HTML block page). notion-client's fetch sets only Content-Type, and Node's undici adds no default UA, so every server-side call from Node fails.

This silently breaks any SSR/SSG/CI usage of notion-client — the same code that worked for years starts returning 403 with no library change.

Environment

  • notion-client 7.10.0 (latest)
  • Node 24 (undici fetch), reproduced both locally and on Vercel serverless

Reproduction

Same page ID, same endpoint, same request body — the UA header is the only variable:

Request Result
notion-client as-is (no User-Agent) 403, Cloudflare HTML block page (~95ms)
Same payload + curl/8.7.1 UA 200
Same payload + browser UA 200

Minimal repro without the library:

// 403:
await fetch('https://www.notion.so/api/v3/loadPageChunk', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ pageId, limit: 100, cursor: { stack: [] }, chunkNumber: 0, verticalColumns: false }),
})

// 200 — identical except for the UA header:
await fetch('https://www.notion.so/api/v3/loadPageChunk', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 ...' },
  body: /* same */,
})

Notion's status page shows no incident — this is bot protection on the unofficial private API, not an outage.

Workaround

ofetchOptions on the constructor merges into every request the client makes, so no library change is needed to unblock:

const notion = new NotionAPI({
  ofetchOptions: {
    headers: {
      'User-Agent':
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
    },
  },
})

Any non-empty realistic UA works (curl's does too).

Proposed fix

Have notion-client send a default User-Agent (overridable via ofetchOptions) so requests work out of the box again. Happy to send a PR if maintainers agree with the approach.

Possibly related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions