> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/makenotion/notion-sdk-js/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Handle errors from the Notion API with type-safe error classes

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.

```typescript theme={null}
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:**

<ParamField path="code" type="APIErrorCode">
  The error code returned by the API (e.g., `unauthorized`, `object_not_found`).
</ParamField>

<ParamField path="status" type="number">
  HTTP status code (e.g., 404, 429, 500).
</ParamField>

<ParamField path="message" type="string">
  Human-readable error message from the API.
</ParamField>

<ParamField path="headers" type="Headers">
  Response headers, including `retry-after` for rate limit errors.
</ParamField>

<ParamField path="body" type="string">
  Raw response body text.
</ParamField>

<ParamField path="request_id" type="string | undefined">
  Notion's request ID for debugging. Include this when contacting support.
</ParamField>

<ParamField path="additional_data" type="Record<string, string | string[]> | undefined">
  Additional context from validation errors.
</ParamField>

### RequestTimeoutError

Thrown when a request exceeds the configured timeout (default 60 seconds).

```typescript theme={null}
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:**

<ParamField path="code" type="ClientErrorCode.RequestTimeout">
  Always `"notionhq_client_request_timeout"`.
</ParamField>

### UnknownHTTPResponseError

Thrown when the API returns an error response that doesn't match the expected error format.

```typescript theme={null}
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:**

<ParamField path="code" type="ClientErrorCode.ResponseError">
  Always `"notionhq_client_response_error"`.
</ParamField>

<ParamField path="status" type="number">
  HTTP status code.
</ParamField>

<ParamField path="body" type="string">
  Raw response body text.
</ParamField>

### InvalidPathParameterError

Thrown when a path parameter contains path traversal sequences (e.g., `..`) that could alter the request path.

```typescript theme={null}
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:**

<ParamField path="code" type="ClientErrorCode.InvalidPathParameter">
  Always `"notionhq_client_invalid_path_parameter"`.
</ParamField>

## Error Codes

### APIErrorCode

Error codes returned by the Notion API:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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):

```typescript theme={null}
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:

```typescript theme={null}
if (APIResponseError.isAPIResponseError(error)) { /* ... */ }
if (RequestTimeoutError.isRequestTimeoutError(error)) { /* ... */ }
if (UnknownHTTPResponseError.isUnknownHTTPResponseError(error)) { /* ... */ }
if (InvalidPathParameterError.isInvalidPathParameterError(error)) { /* ... */ }
```

## Common Error Patterns

### Handle Rate Limits

```typescript theme={null}
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
  }
}
```

<Note>
  The SDK automatically retries rate limit errors (429) with exponential back-off. See [Retries](/concepts/retries) for details.
</Note>

### Handle Missing Resources

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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
    }
  }
}
```

<Info>
  The SDK automatically retries server errors (500, 503) for idempotent methods (GET, DELETE). See [Retries](/concepts/retries).
</Info>

### Log Request IDs

Always log `request_id` for debugging and support:

```typescript theme={null}
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

```typescript theme={null}
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
  }
}
```
