NotionX / NotionX/react-notion-x

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

Open Beginner friendly
#710 4 comments 15 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
5.4k
Forks
645
PR merge metrics
No merged PRs in 30d

Description

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

  • #487 "Pre-rendering pages on Github Actions causes 403" (2023) — CI runners also use UA-less Node fetch, so it's plausibly the same root cause; the workaround above may fix it too.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the NotionAPI constructor and the request configuration that merges ofetchOptions into client calls. Use the provided fetch comparison to verify that requests include a default User-Agent while still allowing the ofetchOptions override, and confirm the reproduced Node/Vercel request no longer receives the Cloudflare 403.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.