NotionX / NotionX/react-notion-x
notion-client requests now 403 (Cloudflare) because no User-Agent is sent — workaround: pass one via ofetchOptions
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-client7.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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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