Skip to main content
While the Notion SDK provides convenient methods for all standard API endpoints, you can use the client.request() method to make custom API calls. This is useful for:
  • Testing new Notion API features before the SDK is updated
  • Debugging API responses
  • Building custom wrappers or abstractions
  • Working with beta or experimental endpoints

The request() Method

The request() method is the low-level foundation that all SDK methods use internally.

Method Signature

client.request<ResponseBody extends object>(
  args: RequestParameters
): Promise<ResponseBody>

RequestParameters

type RequestParameters = {
  path: string
  method: Method // "get" | "post" | "patch" | "delete"
  query?: QueryParams
  body?: Record<string, unknown>
  formDataParams?: Record<string, string | FileParam>
  headers?: Record<string, string>
  auth?: string | { client_id: string; client_secret: string }
}

Making Custom Requests

GET Request Example

Retrieve a page using the low-level API:
import { Client } from "@notionhq/client"

const notion = new Client({ auth: process.env.NOTION_TOKEN })

const page = await notion.request({
  path: "pages/your-page-id",
  method: "get",
})

console.log(page)

POST Request Example

Create a page with a custom request:
const newPage = await notion.request({
  path: "pages",
  method: "post",
  body: {
    parent: {
      type: "database_id",
      database_id: "your-database-id",
    },
    properties: {
      Name: {
        title: [
          {
            text: {
              content: "New Page from Custom Request",
            },
          },
        ],
      },
    },
  },
})

console.log(newPage.id)

PATCH Request Example

Update a block:
const updatedBlock = await notion.request({
  path: "blocks/block-id",
  method: "patch",
  body: {
    paragraph: {
      rich_text: [
        {
          text: {
            content: "Updated content",
          },
        },
      ],
    },
  },
})

DELETE Request Example

Delete a block:
const deletedBlock = await notion.request({
  path: "blocks/block-id",
  method: "delete",
})

console.log(deletedBlock.archived) // true

Query Parameters

Pass query parameters using the query option:
const blocks = await notion.request({
  path: "blocks/block-id/children",
  method: "get",
  query: {
    page_size: 50,
    start_cursor: "cursor-string",
  },
})

Custom Headers

Add custom headers to your request:
const response = await notion.request({
  path: "pages/page-id",
  method: "get",
  headers: {
    "X-Custom-Header": "value",
  },
})
The SDK automatically adds required headers like Notion-Version, Authorization, and Content-Type. You don’t need to include these manually.

Request-Level Authentication

Override the client’s default authentication for a single request:
const notion = new Client()

// Use a different token for this request
const page = await notion.request({
  path: "pages/page-id",
  method: "get",
  auth: "secret_different_token",
})

OAuth Authentication

For OAuth endpoints, pass client credentials:
const tokenResponse = await notion.request({
  path: "oauth/token",
  method: "post",
  body: {
    grant_type: "authorization_code",
    code: "auth-code",
  },
  auth: {
    client_id: "your-client-id",
    client_secret: "your-client-secret",
  },
})

Type Safety

You can provide a type parameter for the response:
type PageResponse = {
  object: "page"
  id: string
  created_time: string
  // ... other properties
}

const page = await notion.request<PageResponse>({
  path: "pages/page-id",
  method: "get",
})

// TypeScript knows page.id exists
console.log(page.id)
Or import types from the SDK:
import { GetPageResponse } from "@notionhq/client/build/src/api-endpoints"

const page = await notion.request<GetPageResponse>({
  path: "pages/page-id",
  method: "get",
})

Error Handling

The request() method throws the same errors as other SDK methods:
import { APIResponseError } from "@notionhq/client"

try {
  const page = await notion.request({
    path: "pages/invalid-id",
    method: "get",
  })
} catch (error) {
  if (APIResponseError.isAPIResponseError(error)) {
    console.error("API Error:", error.code)
    console.error("Status:", error.status)
    console.error("Message:", error.message)
  } else {
    console.error("Unknown error:", error)
  }
}

Automatic Features

The request() method includes all the built-in SDK features:

Automatic Retries

Rate-limited and server error requests are automatically retried with exponential back-off

Request Timeout

Requests timeout after 60 seconds by default (configurable via timeoutMs in client options)

Version Header

The SDK automatically sets the Notion-Version header to the supported API version

Logging

Requests are logged based on the client’s logLevel setting

Internal Request Flow

Here’s what happens when you call request():
  1. Path Validation - Checks for path traversal attacks (src/Client.ts:239)
  2. URL Construction - Builds full URL with base URL and query parameters (src/Client.ts:243)
  3. Headers - Adds authentication, version, and content-type headers (src/Client.ts:245-249)
  4. Retry Logic - Executes request with automatic retry for transient errors (src/Client.ts:252-258)
  5. Response Parsing - Parses JSON response or throws typed error (src/Client.ts:430-442)

Practical Example: Custom Endpoint

Imagine Notion releases a new beta endpoint for AI summaries:
interface AISummaryResponse {
  summary: string
  key_points: string[]
  request_id: string
}

async function getAISummary(pageId: string): Promise<AISummaryResponse> {
  return await notion.request<AISummaryResponse>({
    path: `pages/${pageId}/ai-summary`,
    method: "post",
    body: {
      model: "gpt-4",
      max_length: 500,
    },
  })
}

const summary = await getAISummary("your-page-id")
console.log(summary.summary)

When to Use request()

If Notion releases a new API endpoint that isn’t yet in the SDK, you can call it immediately using request().
For standard endpoints like pages.retrieve() or databases.query(), use the built-in methods. They provide better type safety and documentation.
// Prefer this
await notion.pages.retrieve({ page_id: "id" })

// Over this
await notion.request({ path: "pages/id", method: "get" })
The request() method is useful for debugging API responses or testing parameter combinations.

Advanced: Form Data Requests

For file uploads, use formDataParams instead of body:
const fileUpload = await notion.request({
  path: "file_uploads/upload-id/send",
  method: "post",
  formDataParams: {
    file: {
      filename: "document.pdf",
      data: fileBuffer, // Buffer or Blob
    },
    part_number: "1",
  },
})
When using formDataParams, don’t provide a body. The SDK automatically sets the correct multipart/form-data content type.

Build docs developers (and LLMs) love