Retry Configuration
The Intapp REST Client includes intelligent retry logic for handling transient failures and rate limits.
Default Behavior
By default, the client:
- Retries 3 times on retriable errors
- Uses exponential backoff (2x multiplier)
- Retries on status codes: 429, 500, 502, 503, 504
- Honors Retry-After headers
- Adds jitter to prevent thundering herd
from intapp_rest_client import RestClient
# Default retry behavior
client = RestClient(
base_url="https://api.example.com",
api_key="your-key"
)Custom Configuration
from intapp_rest_client import RestClient, RetryConfig
client = RestClient(
base_url="https://api.example.com",
api_key="your-key",
retry_config=RetryConfig(
max_retries=5,
backoff_factor=2.0,
retryable_statuses=(429, 500, 502, 503, 504),
respect_retry_after=True,
max_backoff=60.0,
jitter=True
)
)RetryConfig Options
| Option | Default | Description |
|---|---|---|
max_retries | 3 | Maximum retry attempts |
backoff_factor | 2.0 | Exponential backoff multiplier |
retryable_statuses | (429, 500, 502, 503, 504) | Status codes that trigger retry |
respect_retry_after | True | Honor Retry-After header |
max_backoff | 60.0 | Maximum wait between retries (seconds) |
retry_count_by_status | None | Per-status retry counts |
jitter | True | Add randomization to backoff |
jitter_min | 0.5 | Minimum jitter multiplier |
jitter_max | 1.5 | Maximum jitter multiplier |
Per-Status Retry Counts
Configure different retry counts for different error types:
from intapp_rest_client import RetryConfig
retry_config = RetryConfig(
max_retries=3, # Default for unlisted statuses
retry_count_by_status={
429: 10, # Rate limits: more retries
500: 2, # Internal errors: fewer retries
502: 5, # Bad gateway: medium
503: 5, # Service unavailable: medium
504: 3, # Gateway timeout: default
}
)Rate limit errors (429) often benefit from more retries since the service is just temporarily overloaded. Server errors (500) may indicate a bug, so fewer retries are appropriate.
Backoff Calculation
The wait time between retries is calculated as:
wait_time = min(backoff_factor ^ attempt, max_backoff) * jitterExample Timeline
With backoff_factor=2.0, max_backoff=60.0:
| Attempt | Base Wait | With Jitter (0.5-1.5x) |
|---|---|---|
| 1 | 2s | 1-3s |
| 2 | 4s | 2-6s |
| 3 | 8s | 4-12s |
| 4 | 16s | 8-24s |
| 5 | 32s | 16-48s |
| 6 | 60s (max) | 30-60s |
Retry-After Header
When the server returns a Retry-After header, the client respects it:
# Server returns: HTTP 429 with Retry-After: 30
# Client waits exactly 30 seconds before retry
retry_config = RetryConfig(
respect_retry_after=True, # Default
max_backoff=120.0 # Still applies as upper limit
)If Retry-After exceeds max_backoff, the client uses max_backoff instead.
Jitter
Jitter prevents the "thundering herd" problem when many clients retry simultaneously:
retry_config = RetryConfig(
jitter=True, # Enable jitter
jitter_min=0.5, # Minimum: 50% of base wait
jitter_max=1.5, # Maximum: 150% of base wait
)Without Jitter
All clients retry at the same time → server gets overwhelmed again:
Client A: [fail] --2s-- [retry] --4s-- [retry] --8s-- [retry]
Client B: [fail] --2s-- [retry] --4s-- [retry] --8s-- [retry]
Client C: [fail] --2s-- [retry] --4s-- [retry] --8s-- [retry]With Jitter
Retries are spread out → server can recover:
Client A: [fail] --1.2s-- [retry] --5.1s-- [retry] --7.8s-- [retry]
Client B: [fail] --2.8s-- [retry] --3.5s-- [retry] --9.2s-- [retry]
Client C: [fail] --1.9s-- [retry] --4.7s-- [retry] --11.3s- [retry]Circuit breaker (advanced)
RestClient does not enable a circuit breaker. The package exports CircuitBreaker, CircuitBreakerConfig, and CircuitBreakerOpenError for use with RetryTransport / AsyncRetryTransport when you construct your own httpx stack. See CircuitBreakerConfig and CircuitBreakerOpenError.
Copying config without certain statuses
RetryConfig.without_retry_on_statuses(*statuses) returns a new config that removes the given codes from retryable_statuses (and from retry_count_by_status). Useful for custom transports or mirroring internal behavior where 429 must not be retried.
Disable Retries
For testing or when you want to handle retries yourself:
retry_config = RetryConfig(
max_retries=0 # No retries
)Which Status Codes to Retry?
| Code | Meaning | Should Retry? |
|---|---|---|
| 400 | Bad Request | ❌ No - fix your request |
| 401 | Unauthorized | ❌ No - client handles token refresh |
| 403 | Forbidden | ❌ No - permission issue |
| 404 | Not Found | ❌ No - resource doesn't exist |
| 408 | Request Timeout | ✅ Yes - transient |
| 429 | Rate Limited | ✅ Yes - wait and retry |
| 500 | Internal Error | ✅ Yes - might be transient |
| 502 | Bad Gateway | ✅ Yes - proxy issue |
| 503 | Unavailable | ✅ Yes - server overloaded |
| 504 | Gateway Timeout | ✅ Yes - proxy timeout |
Custom Status Codes
retry_config = RetryConfig(
retryable_statuses=(408, 429, 500, 502, 503, 504, 599)
)Logging Retries
The client logs retry attempts automatically:
import logging
# Enable debug logging to see retry details
logging.basicConfig(level=logging.DEBUG)
# Or configure the client's logger specifically
logger = logging.getLogger("intapp_rest_client.RestClient")
logger.setLevel(logging.DEBUG)Example log output:
INFO - Request: GET https://api.example.com/data
INFO - Response: 429 (123.4ms)
DEBUG - Retry 1/5 for 429, waiting 2.3s
INFO - Request: GET https://api.example.com/data
INFO - Response: 200 (89.1ms)Best Practices
Production Configuration
retry_config = RetryConfig(
max_retries=5,
backoff_factor=2.0,
max_backoff=60.0,
retry_count_by_status={
429: 10, # Be patient with rate limits
500: 2, # Don't hammer broken services
},
jitter=True,
respect_retry_after=True
)High-Throughput Batch Jobs
retry_config = RetryConfig(
max_retries=3,
backoff_factor=1.5, # Faster backoff
max_backoff=30.0, # Don't wait too long
retry_count_by_status={
429: 5, # Rate limits expected
500: 1, # Move on quickly
}
)Interactive Applications
retry_config = RetryConfig(
max_retries=2, # Don't keep user waiting
backoff_factor=1.0, # Fast retries
max_backoff=5.0, # Max 5 second wait
)Next Steps
- Connection Pooling - Optimize connections
- Error Handling - Handle errors after retries exhausted
- Async Operations - Retry behavior in async contexts