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:
The error code returned by the API (e.g., unauthorized, object_not_found).
HTTP status code (e.g., 404, 429, 500).
Human-readable error message from the API.
Response headers, including retry-after for rate limit errors.
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".
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
- Always use type guards - Don’t assume error types
- Log request IDs - Essential for debugging with Notion support
- Handle specific codes - Match on
error.code for different behaviors
- Let retries work - The SDK auto-retries transient errors
- 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
}
}