Skip to main content
The SDK provides type-safe error classes for handling different failure scenarios. All errors inherit from NotionClientError and include specific error codes.

Error Types

The SDK throws four distinct error types:

APIResponseError

Thrown when the Notion API returns an error response with a recognized error code.
import { APIResponseError, APIErrorCode } from '@notionhq/client'

try {
  await notion.pages.retrieve({ page_id: 'invalid-id' })
} catch (error) {
  if (APIResponseError.isAPIResponseError(error)) {
    console.log('API Error:', error.code)
    console.log('Status:', error.status)
    console.log('Message:', error.message)
    console.log('Request ID:', error.request_id)
  }
}
Properties:
code
APIErrorCode
The error code returned by the API (e.g., unauthorized, object_not_found).
status
number
HTTP status code (e.g., 404, 429, 500).
message
string
Human-readable error message from the API.
headers
Headers
Response headers, including retry-after for rate limit errors.
body
string
Raw response body text.
request_id
string | undefined
Notion’s request ID for debugging. Include this when contacting support.
additional_data
Record<string, string | string[]> | undefined
Additional context from validation errors.

RequestTimeoutError

Thrown when a request exceeds the configured timeout (default 60 seconds).
import { RequestTimeoutError } from '@notionhq/client'

const notion = new Client({
  auth: process.env.NOTION_API_KEY,
  timeoutMs: 10000, // 10 seconds
})

try {
  await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error) {
  if (RequestTimeoutError.isRequestTimeoutError(error)) {
    console.log('Request timed out after 10 seconds')
  }
}
Properties:
code
ClientErrorCode.RequestTimeout
Always "notionhq_client_request_timeout".

UnknownHTTPResponseError

Thrown when the API returns an error response that doesn’t match the expected error format.
import { UnknownHTTPResponseError } from '@notionhq/client'

try {
  await notion.request({ path: 'unknown-endpoint', method: 'get' })
} catch (error) {
  if (UnknownHTTPResponseError.isUnknownHTTPResponseError(error)) {
    console.log('Unexpected error:', error.status, error.body)
  }
}
Properties:
code
ClientErrorCode.ResponseError
Always "notionhq_client_response_error".
status
number
HTTP status code.
body
string
Raw response body text.

InvalidPathParameterError

Thrown when a path parameter contains path traversal sequences (e.g., ..) that could alter the request path.
import { InvalidPathParameterError } from '@notionhq/client'

try {
  // This will throw before making a request
  await notion.request({ path: '../admin/secrets', method: 'get' })
} catch (error) {
  if (InvalidPathParameterError.isInvalidPathParameterError(error)) {
    console.log('Invalid path parameter detected')
  }
}
Properties:
code
ClientErrorCode.InvalidPathParameter
Always "notionhq_client_invalid_path_parameter".

Error Codes

APIErrorCode

Error codes returned by the Notion API:
enum APIErrorCode {
  Unauthorized = "unauthorized",
  RestrictedResource = "restricted_resource",
  ObjectNotFound = "object_not_found",
  RateLimited = "rate_limited",
  InvalidJSON = "invalid_json",
  InvalidRequestURL = "invalid_request_url",
  InvalidRequest = "invalid_request",
  ValidationError = "validation_error",
  ConflictError = "conflict_error",
  InternalServerError = "internal_server_error",
  ServiceUnavailable = "service_unavailable",
}

ClientErrorCode

Error codes generated by the SDK client:
enum ClientErrorCode {
  RequestTimeout = "notionhq_client_request_timeout",
  ResponseError = "notionhq_client_response_error",
  InvalidPathParameter = "notionhq_client_invalid_path_parameter",
}

Type Guards

Use type guards to narrow error types:

isNotionClientError

Check if an error is any SDK error:
import { isNotionClientError } from '@notionhq/client'

try {
  await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error: unknown) {
  if (isNotionClientError(error)) {
    // error is now typed as NotionClientError
    console.log('Error code:', error.code)
    console.log('Error name:', error.name)
  } else {
    // Some other error (network, etc.)
    throw error
  }
}

isHTTPResponseError

Check if an error is an HTTP response error (has status, headers, body):
import { isHTTPResponseError } from '@notionhq/client'

try {
  await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error: unknown) {
  if (isHTTPResponseError(error)) {
    // error is APIResponseError | UnknownHTTPResponseError
    console.log('HTTP Status:', error.status)
    console.log('Response body:', error.body)
    console.log('Headers:', error.headers)
  }
}

Specific Error Type Guards

Each error class has its own type guard:
if (APIResponseError.isAPIResponseError(error)) { /* ... */ }
if (RequestTimeoutError.isRequestTimeoutError(error)) { /* ... */ }
if (UnknownHTTPResponseError.isUnknownHTTPResponseError(error)) { /* ... */ }
if (InvalidPathParameterError.isInvalidPathParameterError(error)) { /* ... */ }

Common Error Patterns

Handle Rate Limits

import { APIResponseError, APIErrorCode } from '@notionhq/client'

try {
  await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error) {
  if (
    APIResponseError.isAPIResponseError(error) &&
    error.code === APIErrorCode.RateLimited
  ) {
    const retryAfter = error.headers.get('retry-after')
    console.log(`Rate limited. Retry after ${retryAfter} seconds`)
    // The SDK automatically retries rate limits - this catch is for logging
  }
}
The SDK automatically retries rate limit errors (429) with exponential back-off. See Retries for details.

Handle Missing Resources

import { APIResponseError, APIErrorCode } from '@notionhq/client'

try {
  const page = await notion.pages.retrieve({ page_id: pageId })
  return page
} catch (error) {
  if (
    APIResponseError.isAPIResponseError(error) &&
    error.code === APIErrorCode.ObjectNotFound
  ) {
    console.log('Page not found or integration lacks access')
    return null
  }
  throw error
}

Handle Validation Errors

import { APIResponseError, APIErrorCode } from '@notionhq/client'

try {
  await notion.pages.create({
    parent: { database_id: 'db-id' },
    properties: { Title: { title: [{ text: { content: 'New Page' } }] } },
  })
} catch (error) {
  if (
    APIResponseError.isAPIResponseError(error) &&
    error.code === APIErrorCode.ValidationError
  ) {
    console.log('Validation failed:', error.message)
    console.log('Additional data:', error.additional_data)
  }
}

Retry on Server Errors

import { APIResponseError, APIErrorCode } from '@notionhq/client'

async function fetchWithRetry(pageId: string, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await notion.pages.retrieve({ page_id: pageId })
    } catch (error) {
      if (
        APIResponseError.isAPIResponseError(error) &&
        (error.code === APIErrorCode.InternalServerError ||
         error.code === APIErrorCode.ServiceUnavailable) &&
        attempt < maxAttempts
      ) {
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt))
        continue
      }
      throw error
    }
  }
}
The SDK automatically retries server errors (500, 503) for idempotent methods (GET, DELETE). See Retries.

Log Request IDs

Always log request_id for debugging and support:
import { isHTTPResponseError } from '@notionhq/client'

try {
  await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error) {
  if (isHTTPResponseError(error)) {
    console.error('Request failed:', {
      code: error.code,
      status: error.status,
      message: error.message,
      request_id: error.request_id, // Include when contacting support
    })
  }
}

Error Handling Best Practices

  1. Always use type guards - Don’t assume error types
  2. Log request IDs - Essential for debugging with Notion support
  3. Handle specific codes - Match on error.code for different behaviors
  4. Let retries work - The SDK auto-retries transient errors
  5. Validate inputs - Check IDs and parameters before making requests
import { isNotionClientError, APIErrorCode } from '@notionhq/client'

async function safeRetrievePage(pageId: string) {
  try {
    const page = await notion.pages.retrieve({ page_id: pageId })
    return { success: true, data: page }
  } catch (error: unknown) {
    if (!isNotionClientError(error)) {
      // Non-Notion error (network, etc.)
      throw error
    }

    if ('request_id' in error) {
      console.error('Request ID:', error.request_id)
    }

    if ('code' in error) {
      switch (error.code) {
        case APIErrorCode.ObjectNotFound:
          return { success: false, error: 'Page not found' }
        case APIErrorCode.Unauthorized:
          return { success: false, error: 'Unauthorized' }
        default:
          return { success: false, error: error.message }
      }
    }

    throw error
  }
}

Build docs developers (and LLMs) love