or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-endpoints.mdclient.mderror-handling.mdhelpers.mdindex.md

error-handling.mddocs/

0

# Error Handling

1

2

Comprehensive error handling system with specific exception types for different API error conditions, HTTP errors, and timeout scenarios. The client provides detailed error information to help diagnose and handle issues appropriately.

3

4

## Capabilities

5

6

### Exception Hierarchy

7

8

#### Base HTTP Exceptions

9

10

```python { .api }

11

class RequestTimeoutError(Exception):

12

"""Exception for requests that timeout."""

13

14

code: str = "notionhq_client_request_timeout"

15

16

def __init__(self, message="Request to Notion API has timed out"):

17

"""

18

Initialize timeout error.

19

20

Parameters:

21

- message: str, error message (default provided)

22

"""

23

24

class HTTPResponseError(Exception):

25

"""Exception for HTTP errors."""

26

27

code: str = "notionhq_client_response_error"

28

status: int

29

headers: httpx.Headers

30

body: str

31

32

def __init__(self, response, message=None):

33

"""

34

Initialize HTTP response error.

35

36

Parameters:

37

- response: httpx.Response, the failed HTTP response

38

- message: str, optional custom error message

39

"""

40

```

41

42

#### API Response Exceptions

43

44

```python { .api }

45

class APIResponseError(HTTPResponseError):

46

"""An error raised by Notion API."""

47

48

code: APIErrorCode

49

50

def __init__(self, response, message, code):

51

"""

52

Initialize API response error.

53

54

Parameters:

55

- response: httpx.Response, the failed HTTP response

56

- message: str, error message from API

57

- code: APIErrorCode, specific error code from API

58

"""

59

```

60

61

### Error Codes

62

63

Enumeration of all possible API error codes returned by the Notion API.

64

65

```python { .api }

66

class APIErrorCode(str, Enum):

67

"""API error code enumeration."""

68

69

Unauthorized = "unauthorized"

70

"""The bearer token is not valid."""

71

72

RestrictedResource = "restricted_resource"

73

"""Given the bearer token used, the client doesn't have permission to perform this operation."""

74

75

ObjectNotFound = "object_not_found"

76

"""Given the bearer token used, the resource does not exist. This error can also indicate that the resource has not been shared with owner of the bearer token."""

77

78

RateLimited = "rate_limited"

79

"""This request exceeds the number of requests allowed. Slow down and try again."""

80

81

InvalidJSON = "invalid_json"

82

"""The request body could not be decoded as JSON."""

83

84

InvalidRequestURL = "invalid_request_url"

85

"""The request URL is not valid."""

86

87

InvalidRequest = "invalid_request"

88

"""This request is not supported."""

89

90

ValidationError = "validation_error"

91

"""The request body does not match the schema for the expected parameters."""

92

93

ConflictError = "conflict_error"

94

"""The transaction could not be completed, potentially due to a data collision. Make sure the parameters are up to date and try again."""

95

96

InternalServerError = "internal_server_error"

97

"""An unexpected error occurred. Reach out to Notion support."""

98

99

ServiceUnavailable = "service_unavailable"

100

"""Notion is unavailable. Try again later. This can occur when the time to respond to a request takes longer than 60 seconds, the maximum request timeout."""

101

```

102

103

### Error Utility Functions

104

105

```python { .api }

106

def is_api_error_code(code):

107

"""

108

Check if given code belongs to the list of valid API error codes.

109

110

Parameters:

111

- code: str, error code to check

112

113

Returns:

114

bool, True if code is a valid API error code

115

"""

116

```

117

118

## Usage Examples

119

120

### Basic Error Handling

121

122

```python

123

from notion_client import Client, APIResponseError, APIErrorCode

124

125

notion = Client(auth="your_token")

126

127

try:

128

page = notion.pages.retrieve(page_id="invalid_page_id")

129

except APIResponseError as error:

130

print(f"API Error: {error}")

131

print(f"Error Code: {error.code}")

132

print(f"HTTP Status: {error.status}")

133

print(f"Response Body: {error.body}")

134

```

135

136

### Specific Error Code Handling

137

138

```python

139

from notion_client import Client, APIResponseError, APIErrorCode

140

import logging

141

142

try:

143

response = notion.databases.query(

144

database_id="database_id_here",

145

filter={"property": "Status", "select": {"equals": "Active"}}

146

)

147

except APIResponseError as error:

148

if error.code == APIErrorCode.ObjectNotFound:

149

print("Database not found or not accessible")

150

elif error.code == APIErrorCode.Unauthorized:

151

print("Invalid or expired authentication token")

152

elif error.code == APIErrorCode.RestrictedResource:

153

print("Insufficient permissions to access this resource")

154

elif error.code == APIErrorCode.RateLimited:

155

print("Rate limit exceeded, please slow down requests")

156

elif error.code == APIErrorCode.ValidationError:

157

print("Invalid request parameters")

158

print(f"Details: {error.body}")

159

else:

160

logging.error(f"Unexpected API error: {error.code} - {error}")

161

```

162

163

### Comprehensive Error Handling

164

165

```python

166

from notion_client import (

167

Client,

168

APIResponseError,

169

HTTPResponseError,

170

RequestTimeoutError,

171

APIErrorCode

172

)

173

import time

174

import logging

175

176

def robust_api_call(notion, operation, max_retries=3):

177

"""

178

Make an API call with comprehensive error handling and retries.

179

180

Parameters:

181

- notion: Client instance

182

- operation: callable that performs the API operation

183

- max_retries: int, maximum number of retry attempts

184

185

Returns:

186

API response or None if all retries failed

187

"""

188

for attempt in range(max_retries + 1):

189

try:

190

return operation()

191

192

except APIResponseError as error:

193

if error.code == APIErrorCode.RateLimited:

194

if attempt < max_retries:

195

wait_time = 2 ** attempt # Exponential backoff

196

print(f"Rate limited. Waiting {wait_time} seconds before retry...")

197

time.sleep(wait_time)

198

continue

199

else:

200

print("Max retries reached for rate limiting")

201

raise

202

203

elif error.code == APIErrorCode.InternalServerError:

204

if attempt < max_retries:

205

wait_time = 2 ** attempt

206

print(f"Server error. Retrying in {wait_time} seconds...")

207

time.sleep(wait_time)

208

continue

209

else:

210

print("Max retries reached for server errors")

211

raise

212

213

elif error.code == APIErrorCode.ServiceUnavailable:

214

if attempt < max_retries:

215

wait_time = 5 * (attempt + 1) # Linear backoff for service issues

216

print(f"Service unavailable. Retrying in {wait_time} seconds...")

217

time.sleep(wait_time)

218

continue

219

else:

220

print("Max retries reached for service unavailability")

221

raise

222

223

elif error.code in [

224

APIErrorCode.Unauthorized,

225

APIErrorCode.ObjectNotFound,

226

APIErrorCode.RestrictedResource,

227

APIErrorCode.ValidationError

228

]:

229

# These errors won't be resolved by retrying

230

print(f"Non-retryable error: {error.code}")

231

raise

232

233

else:

234

print(f"Unknown API error: {error.code}")

235

raise

236

237

except RequestTimeoutError:

238

if attempt < max_retries:

239

wait_time = 2 ** attempt

240

print(f"Request timeout. Retrying in {wait_time} seconds...")

241

time.sleep(wait_time)

242

continue

243

else:

244

print("Max retries reached for timeouts")

245

raise

246

247

except HTTPResponseError as error:

248

print(f"HTTP error {error.status}: {error}")

249

if error.status >= 500 and attempt < max_retries:

250

# Retry server errors

251

wait_time = 2 ** attempt

252

print(f"Server error. Retrying in {wait_time} seconds...")

253

time.sleep(wait_time)

254

continue

255

else:

256

raise

257

258

except Exception as error:

259

print(f"Unexpected error: {error}")

260

raise

261

262

return None

263

264

# Usage example

265

notion = Client(auth="your_token")

266

267

def get_database():

268

return notion.databases.retrieve(database_id="database_id_here")

269

270

database = robust_api_call(notion, get_database)

271

if database:

272

print("Successfully retrieved database")

273

else:

274

print("Failed to retrieve database after all retries")

275

```

276

277

### Error Logging and Monitoring

278

279

```python

280

import logging

281

from notion_client import Client, APIResponseError, HTTPResponseError, RequestTimeoutError

282

283

# Set up logging

284

logging.basicConfig(

285

level=logging.INFO,

286

format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

287

)

288

logger = logging.getLogger(__name__)

289

290

notion = Client(

291

auth="your_token",

292

log_level=logging.DEBUG # Enable debug logging for API requests

293

)

294

295

def log_api_error(error, operation_name):

296

"""Log API errors with relevant details."""

297

if isinstance(error, APIResponseError):

298

logger.error(

299

f"API Error in {operation_name}: "

300

f"Code={error.code}, Status={error.status}, "

301

f"Message={str(error)}"

302

)

303

if error.code == APIErrorCode.ValidationError:

304

logger.error(f"Validation details: {error.body}")

305

elif isinstance(error, RequestTimeoutError):

306

logger.error(f"Timeout in {operation_name}: {str(error)}")

307

elif isinstance(error, HTTPResponseError):

308

logger.error(

309

f"HTTP Error in {operation_name}: "

310

f"Status={error.status}, Message={str(error)}"

311

)

312

else:

313

logger.error(f"Unexpected error in {operation_name}: {str(error)}")

314

315

try:

316

users = notion.users.list()

317

logger.info(f"Successfully retrieved {len(users['results'])} users")

318

except (APIResponseError, HTTPResponseError, RequestTimeoutError) as error:

319

log_api_error(error, "users.list")

320

```

321

322

### Error Context and Recovery

323

324

```python

325

from notion_client import Client, APIResponseError, APIErrorCode

326

327

class NotionClientWrapper:

328

"""Wrapper class with enhanced error handling and context."""

329

330

def __init__(self, auth_token):

331

self.notion = Client(auth=auth_token)

332

self.last_error = None

333

334

def safe_database_query(self, database_id, **kwargs):

335

"""Query database with error context and fallback strategies."""

336

try:

337

return self.notion.databases.query(database_id=database_id, **kwargs)

338

339

except APIResponseError as error:

340

self.last_error = error

341

342

if error.code == APIErrorCode.ObjectNotFound:

343

print(f"Database {database_id} not found. Checking if it exists...")

344

return self._handle_not_found_database(database_id)

345

346

elif error.code == APIErrorCode.ValidationError:

347

print("Query validation failed. Trying with simplified query...")

348

return self._retry_with_simple_query(database_id)

349

350

elif error.code == APIErrorCode.RestrictedResource:

351

print("Access restricted. Trying to get basic database info...")

352

return self._get_basic_database_info(database_id)

353

354

else:

355

raise # Re-raise unhandled errors

356

357

def _handle_not_found_database(self, database_id):

358

"""Handle database not found scenario."""

359

try:

360

# Try to retrieve the database directly

361

db_info = self.notion.databases.retrieve(database_id=database_id)

362

print("Database exists but may be empty")

363

return {"results": [], "database_info": db_info}

364

except APIResponseError:

365

print("Database truly does not exist or is not accessible")

366

return None

367

368

def _retry_with_simple_query(self, database_id):

369

"""Retry with a simpler query."""

370

try:

371

return self.notion.databases.query(

372

database_id=database_id,

373

page_size=10 # Reduce page size and remove filters

374

)

375

except APIResponseError:

376

print("Even simple query failed")

377

return None

378

379

def _get_basic_database_info(self, database_id):

380

"""Get basic database information when full access is restricted."""

381

try:

382

db_info = self.notion.databases.retrieve(database_id=database_id)

383

return {"results": [], "database_info": db_info, "access_limited": True}

384

except APIResponseError:

385

return None

386

387

# Usage

388

wrapper = NotionClientWrapper("your_token")

389

result = wrapper.safe_database_query("database_id_here")

390

391

if result is None:

392

print("Could not access database")

393

elif "access_limited" in result:

394

print("Limited access - only database metadata available")

395

else:

396

print(f"Retrieved {len(result['results'])} pages")

397

```

398

399

## Error Prevention Best Practices

400

401

### Validation Before API Calls

402

403

```python

404

import re

405

from notion_client import Client

406

407

def is_valid_notion_id(notion_id):

408

"""Check if a string is a valid Notion ID format."""

409

# Notion IDs are UUIDs with or without hyphens

410

uuid_pattern = r'^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$'

411

return re.match(uuid_pattern, notion_id.lower()) is not None

412

413

def safe_page_retrieve(notion, page_id):

414

"""Safely retrieve a page with pre-validation."""

415

if not is_valid_notion_id(page_id):

416

raise ValueError(f"Invalid page ID format: {page_id}")

417

418

try:

419

return notion.pages.retrieve(page_id=page_id)

420

except APIResponseError as error:

421

if error.code == APIErrorCode.ObjectNotFound:

422

print(f"Page {page_id} does not exist or is not accessible")

423

raise

424

```

425

426

### Rate Limiting Prevention

427

428

```python

429

import time

430

from collections import deque

431

from notion_client import Client

432

433

class RateLimitedClient:

434

"""Client wrapper with built-in rate limiting."""

435

436

def __init__(self, auth_token, requests_per_second=3):

437

self.notion = Client(auth=auth_token)

438

self.requests_per_second = requests_per_second

439

self.request_times = deque()

440

441

def _enforce_rate_limit(self):

442

"""Enforce rate limiting before making requests."""

443

now = time.time()

444

445

# Remove requests older than 1 second

446

while self.request_times and now - self.request_times[0] > 1.0:

447

self.request_times.popleft()

448

449

# If we've made too many requests, wait

450

if len(self.request_times) >= self.requests_per_second:

451

sleep_time = 1.0 - (now - self.request_times[0])

452

if sleep_time > 0:

453

time.sleep(sleep_time)

454

455

self.request_times.append(now)

456

457

def query_database(self, database_id, **kwargs):

458

"""Query database with rate limiting."""

459

self._enforce_rate_limit()

460

return self.notion.databases.query(database_id=database_id, **kwargs)

461

462

# Usage

463

rate_limited_notion = RateLimitedClient("your_token", requests_per_second=2)

464

results = rate_limited_notion.query_database("database_id_here")

465

```