Error Handling
The Intapp REST Client provides structured exceptions for different error scenarios.
Exception Hierarchy
RestClientError (base)
├── HttpError # HTTP response errors (4xx, 5xx)
├── AuthenticationError # Authentication failures
│ └── TokenExpiredError
├── RequestError # Network/connection errors
├── ResponseError # Response parsing errors
├── RetryExhaustedError # All retries failed
└── CircuitBreakerOpenError # Circuit breaker trippedBasic Error Handling
from intapp_rest_client import (
RestClient,
HttpError,
RequestError,
AuthenticationError
)
client = RestClient(base_url="https://api.example.com", api_key="key")
try:
data = client.get("/api/resources")
except HttpError as e:
print(f"HTTP {e.status_code}: {e.message}")
except RequestError as e:
print(f"Network error: {e}")
except AuthenticationError as e:
print(f"Auth failed: {e}")
finally:
client.close()HttpError
Raised for HTTP response errors (status codes 4xx and 5xx).
from intapp_rest_client import HttpError
try:
data = client.get("/api/resources")
except HttpError as e:
print(f"Status: {e.status_code}")
print(f"Message: {e.message}")
print(f"Response body: {e.response_body}")
print(f"Headers: {e.headers}")
print(f"Request ID: {e.request_id}")
# Special properties
if e.is_rate_limited:
print(f"Retry after: {e.retry_after} seconds")
if e.is_server_error:
print("Server-side issue")
if e.is_client_error:
print("Client-side issue (check your request)")HttpError Properties
| Property | Type | Description |
|---|---|---|
status_code | int | HTTP status code |
message | str | Error message |
response_body | Any | Response body (dict or str) |
headers | dict | Response headers |
request_id | str | Request tracking ID |
retry_after | int | None | Seconds from Retry-After header, if present |
is_rate_limited | bool | True if 429 status |
is_server_error | bool | True if 5xx status |
is_client_error | bool | True if 4xx status |
is_retryable | bool | True for typical transient statuses (429, 5xx) |
Handling Specific Status Codes
try:
data = client.get("/api/resources/123")
except HttpError as e:
if e.status_code == 404:
print("Resource not found")
elif e.status_code == 403:
print("Permission denied")
elif e.status_code == 429:
print(f"Rate limited, wait {e.retry_after}s")
elif e.is_server_error:
print("Server error, try again later")
else:
raiseRequestError
Raised for network and connection issues.
from intapp_rest_client import RequestError
try:
data = client.get("/api/resources")
except RequestError as e:
print(f"Network error: {e}")
print(f"Request ID: {e.request_id}")
# Common causes:
# - DNS resolution failed
# - Connection refused
# - Connection timeout
# - SSL/TLS errorsAuthenticationError
Raised when authentication fails.
from intapp_rest_client import AuthenticationError, TokenExpiredError
try:
data = client.get("/api/resources")
except TokenExpiredError as e:
print("Token expired and could not be refreshed")
except AuthenticationError as e:
print(f"Authentication failed: {e}")
# Check credentialsRetryExhaustedError
Raised when all retry attempts have been exhausted inside the retry transport.
from intapp_rest_client import RetryExhaustedError
try:
data = client.get("/api/resources")
except RetryExhaustedError as e:
print(str(e))
if e.last_error is not None:
print(f"Last underlying error: {e.last_error}")đź’ˇ
Use e.last_error for the last low-level exception from the transport. The message on RetryExhaustedError summarizes attempts; there are no attempts or last_status_code attributes.
CircuitBreakerOpenError
Raised only when using RetryTransport / AsyncRetryTransport with a CircuitBreaker. Default RestClient usage does not enable a circuit breaker.
from intapp_rest_client import CircuitBreakerOpenError
try:
...
except CircuitBreakerOpenError as e:
print("Circuit breaker is open")
if e.reset_time is not None:
print(f"Reset hint: {e.reset_time}s")Async Error Handling
Error handling works the same in async code:
import asyncio
from intapp_rest_client import RestClient, HttpError, RequestError
async def fetch_data():
async with RestClient(base_url="https://api.example.com", api_key="key") as client:
try:
return await client.aget("/api/resources")
except HttpError as e:
print(f"HTTP error: {e.status_code}")
return None
except RequestError as e:
print(f"Network error: {e}")
return None
asyncio.run(fetch_data())Common Patterns
Graceful Degradation
def get_with_fallback(client, primary_endpoint, fallback_endpoint):
"""Try primary endpoint, fall back to secondary."""
try:
return client.get(primary_endpoint)
except HttpError as e:
if e.status_code >= 500:
return client.get(fallback_endpoint)
raiseRetry with Different Parameters
def get_with_reduced_scope(client, endpoint, params):
"""Retry with smaller batch size on failure."""
try:
return client.get(endpoint, params=params)
except HttpError as e:
if e.status_code == 400 and params.get("limit", 0) > 100:
# Try with smaller batch
params["limit"] = 100
return client.get(endpoint, params=params)
raiseLogging Errors
import logging
logger = logging.getLogger(__name__)
def fetch_with_logging(client, endpoint):
try:
return client.get(endpoint)
except HttpError as e:
logger.error(
"HTTP error",
extra={
"status_code": e.status_code,
"request_id": e.request_id,
"endpoint": endpoint
}
)
raise
except RequestError as e:
logger.error(
"Network error",
extra={
"request_id": e.request_id,
"endpoint": endpoint,
"error": str(e)
}
)
raiseCollecting Errors in Batch Operations
def process_batch(client, items):
"""Process items and collect errors."""
results = []
errors = []
for item in items:
try:
result = client.post("/api/items", json=item)
results.append(result)
except HttpError as e:
errors.append({
"item": item,
"status": e.status_code,
"message": e.message
})
return results, errors
results, errors = process_batch(client, items)
print(f"Processed {len(results)}, failed {len(errors)}")Error Response Bodies
Many APIs return structured error responses:
try:
client.post("/api/resources", json=invalid_data)
except HttpError as e:
if isinstance(e.response_body, dict):
error_code = e.response_body.get("errorCode")
error_details = e.response_body.get("details", [])
print(f"Error {error_code}: {error_details}")
else:
print(f"Error: {e.response_body}")Best Practices
- Catch specific exceptions: Don't catch
RestClientErrorblindly - Log request IDs: Essential for debugging with support
- Handle rate limits gracefully: Check
is_rate_limitedandretry_after - Distinguish client vs server errors: Client errors (4xx) won't succeed on retry
- Use structured logging: Include status codes and request IDs
Next Steps
- Retry Configuration - Configure retry behavior
- Logging - Set up error logging
- Reference: Exceptions - Full exception API