Configuration
Retry Configuration

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

OptionDefaultDescription
max_retries3Maximum retry attempts
backoff_factor2.0Exponential backoff multiplier
retryable_statuses(429, 500, 502, 503, 504)Status codes that trigger retry
respect_retry_afterTrueHonor Retry-After header
max_backoff60.0Maximum wait between retries (seconds)
retry_count_by_statusNonePer-status retry counts
jitterTrueAdd randomization to backoff
jitter_min0.5Minimum jitter multiplier
jitter_max1.5Maximum 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) * jitter

Example Timeline

With backoff_factor=2.0, max_backoff=60.0:

AttemptBase WaitWith Jitter (0.5-1.5x)
12s1-3s
24s2-6s
38s4-12s
416s8-24s
532s16-48s
660s (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?

CodeMeaningShould Retry?
400Bad Request❌ No - fix your request
401Unauthorized❌ No - client handles token refresh
403Forbidden❌ No - permission issue
404Not Found❌ No - resource doesn't exist
408Request Timeout✅ Yes - transient
429Rate Limited✅ Yes - wait and retry
500Internal Error✅ Yes - might be transient
502Bad Gateway✅ Yes - proxy issue
503Unavailable✅ Yes - server overloaded
504Gateway 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