diff --git a/packages/notion-client/readme.md b/packages/notion-client/readme.md index 0cab80e0..f3e38eec 100644 --- a/packages/notion-client/readme.md +++ b/packages/notion-client/readme.md @@ -36,6 +36,44 @@ const collectionData = await api.getCollectionData( ) ``` +### Using Custom API Base URL + +**Important:** Due to recent changes in Notion's internal API you may need to specify your Notion site's subdomain when accessing public pages with collections/databases to avoid HTTP 530 errors. + +```ts +import { NotionAPI } from 'notion-client' + +// For public Notion pages, use your site's subdomain +const api = new NotionAPI({ + apiBaseUrl: 'https://your-subdomain.notion.site/api/v3' +}) + +const page = await api.getPage('your-page-id') +``` + +**How to find your subdomain:** + +1. Open your Notion page in a web browser +2. Look at the URL: `https://your-subdomain.notion.site/Page-Title-xxxxx` +3. Copy the `your-subdomain` part +4. Use it in the `apiBaseUrl` parameter + +**Example:** + +If your Notion page URL is `https://acme-corp.notion.site/Team-Wiki-abc123`, then: + +```ts +const api = new NotionAPI({ + apiBaseUrl: 'https://acme-corp.notion.site/api/v3' +}) +``` + +**Troubleshooting:** + +- ❌ `Error 530` when querying collections → Update `apiBaseUrl` with your subdomain +- ❌ Using the old endpoint `https://www.notion.so/api/v3` → Switch to your site's URL +- ✅ Private pages with `authToken` → The default endpoint usually works fine + ### Fetch a database's content You can pass a database ID to the `getPage` method. The response is an object which contains several important properties: diff --git a/packages/notion-client/src/notion-api.test.ts b/packages/notion-client/src/notion-api.test.ts index d3b92df9..59b14733 100644 --- a/packages/notion-client/src/notion-api.test.ts +++ b/packages/notion-client/src/notion-api.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from 'vitest' +import { expect, test, vi } from 'vitest' import { NotionAPI } from './notion-api' @@ -47,3 +47,75 @@ for (const pageId of pageIdFixturesFailure) { await expect(() => api.getPage(pageId)).rejects.toThrow() }) } + +test('NotionAPI.getPage should log a helpful error on 530 errors', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}) + + const pageId = 'bdecdf15-0d0e-40cb-9f34-12be132335d4' // Use a valid UUID format + const collectionId = 'collection-id' + const viewId = 'view-id' + + const api = new NotionAPI() + + // Spy on the private fetch method to isolate the mock to this test + const fetchSpy = vi + .spyOn(api as any, 'fetch') + .mockImplementation(async (params: any) => { + const { endpoint } = params + if (endpoint === 'loadPageChunk') { + return { + recordMap: { + block: { + [pageId]: { + value: { + id: pageId, + type: 'collection_view_page', + collection_id: collectionId, + view_ids: [viewId] + } + } + }, + collection: { + [collectionId]: { + value: { id: collectionId, name: [['Test Collection']] } + } + }, + collection_view: { + [viewId]: { + value: { id: viewId, type: 'table' } + } + } + } + } + } + + if (endpoint === 'queryCollection') { + const error: any = new Error('Response code 530') + error.status = 530 + error.response = { status: 530 } + throw error + } + + return {} + }) + + const page = await api.getPage(pageId, { + fetchCollections: true, + throwOnCollectionErrors: false + }) + + expect(page).toBeTruthy() + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Notion API Error 530: Collection query failed') + ) + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining( + "Solution: Configure NotionAPI with your site's subdomain" + ) + ) + + fetchSpy.mockRestore() + consoleErrorSpy.mockRestore() +}) diff --git a/packages/notion-client/src/notion-api.ts b/packages/notion-client/src/notion-api.ts index 9866a892..1b6cc03f 100644 --- a/packages/notion-client/src/notion-api.ts +++ b/packages/notion-client/src/notion-api.ts @@ -192,6 +192,29 @@ export class NotionAPI { // It's possible for public pages to link to private collections, // in which case Notion returns a 400 error. This may be that or it // may be something else. + + // Special handling for 530 errors (API endpoint issues) + const is530Error = + err.response?.status === 530 || + err.status === 530 || + err.message?.includes('530') + + if (is530Error) { + console.error( + '\n Notion API Error 530: Collection query failed\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + + ' This usually means the API endpoint needs to be updated.\n\n' + + " Solution: Configure NotionAPI with your site's subdomain:\n\n" + + ' const api = new NotionAPI({\n' + + " apiBaseUrl: 'https://YOUR-SUBDOMAIN.notion.site/api/v3'\n" + + ' })\n\n' + + ' Find your subdomain in your Notion page URL:\n' + + ' https://YOUR-SUBDOMAIN.notion.site/...\n\n' + + ' Learn more: https://github.com/NotionX/react-notion-x/tree/master/packages/notion-client#using-custom-api-base-url\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ) + } + console.warn( 'NotionAPI collectionQuery error', { pageId, collectionId, collectionViewId },