Skip to main content

Rate Limits & Retries

The SDK provides built-in handling for rate limits and automatic retries for transient failures. Understanding these mechanisms is crucial for building robust applications.

Rate Limiting Signals

When working with APIs, rate limiting is a common mechanism to prevent abuse and ensure fair usage. The Nexla SDK provides clear signals when rate limits are encountered and includes built-in mechanisms to handle these situations gracefully.

RateLimitError Exception

The SDK raises RateLimitError for HTTP 429 responses. The retry_after attribute is populated from the Retry-After header or JSON body when available.

Here's how to handle rate limit errors:

Rate Limit Detection
from nexla_sdk.exceptions import RateLimitError

def list_sources_with_backoff(client: NexlaClient):
try:
return client.sources.list(per_page=100)
except RateLimitError as exc:
wait = exc.retry_after or 30
print(f"Hit rate limit, retrying in {wait}s")
raise

Built-in Retry Logic

The SDK includes automatic retry logic for transient failures. RequestsHttpClient enables urllib3.Retry with exponential backoff (backoff_factor=0.5) for transient status codes 429, 502, 503, and 504, covering GET/POST/PUT/DELETE/PATCH calls.

Rate Limit Information

Understanding your current rate limit status and usage is essential for building applications that can adapt to API constraints. The SDK provides methods to query and monitor rate limit information.

Get Rate Limits

You can check your current rate limit status using the metrics API:

Check Rate Limits
# Get current rate limit information
rate_limits = client.metrics.get_rate_limits()
print(f"Rate limits: {rate_limits}")

Monitor Rate Limit Usage

For production applications, it's important to monitor rate limit usage to avoid hitting limits unexpectedly:

Rate Limit Monitoring
def monitor_rate_limits():
"""Monitor rate limit usage across resources"""
try:
rate_limits = client.metrics.get_rate_limits()

print("📊 Rate Limit Status:")
for resource_type, limits in rate_limits.items():
print(f" {resource_type}:")
print(f" Used: {limits.get('used', 'N/A')}")
print(f" Limit: {limits.get('limit', 'N/A')}")
print(f" Reset: {limits.get('reset_time', 'N/A')}")
print()
except Exception as e:
print(f"❌ Error getting rate limits: {e}")

monitor_rate_limits()

Retry Strategies

When API calls fail due to transient issues, implementing effective retry strategies can significantly improve application reliability. Here are several approaches you can use with the Nexla SDK.

Exponential Backoff

Implementing exponential backoff helps prevent overwhelming the API when retrying failed requests:

Exponential Backoff
import time
from nexla_sdk.exceptions import RateLimitError, ServerError

def exponential_backoff(func, max_retries=5):
"""Implement exponential backoff for retries"""
for attempt in range(max_retries):
try:
return func()
except RateLimitError as e:
wait_time = e.retry_after or (2 ** attempt)
if attempt < max_retries - 1:
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
raise
except ServerError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f"Server error. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
raise

Circuit Breaker Pattern

For high-availability applications, implementing a circuit breaker pattern can help prevent cascading failures:

Circuit Breaker
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failure_count = 0
self.last_failure_time = None
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN

def call(self, func):
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.timeout:
self.state = "HALF_OPEN"
else:
raise Exception("Circuit breaker is OPEN")

try:
result = func()
if self.state == "HALF_OPEN":
self.state = "CLOSED"
self.failure_count = 0
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.time()

if self.failure_count >= self.failure_threshold:
self.state = "OPEN"

raise

# Usage
breaker = CircuitBreaker()
result = breaker.call(lambda: client.flows.list())

Idempotency Expectations

Understanding which operations are safe to retry is crucial for building robust applications. Idempotent operations can be safely repeated without side effects, while non-idempotent operations require careful handling.

Safe Operations

Understanding which operations are safe to retry is important for implementing robust error handling:

  • GET, PUT, and DELETE endpoints are idempotent
  • Repeating the same call produces the same state (standard REST semantics)
  • POST endpoints create new resources

Handling Duplicates

When creating resources, you may encounter conflicts if the resource already exists. Here's how to handle this gracefully:

Duplicate Handling
from nexla_sdk.exceptions import ResourceConflictError

def create_or_get_destination(payload):
"""Create destination or get existing one"""
try:
destination = client.destinations.create(payload)
return destination
except ResourceConflictError:
# Resource already exists, find and return it
existing = client.destinations.list(name=payload["name"], per_page=1)
if existing:
return existing[0]
else:
raise Exception("Conflict but resource not found")

Safe Create/Update Pattern

A common pattern is to attempt creation first, then fall back to updating if the resource already exists:

Safe Create/Update
def safe_create_or_update(payload, resource_type="destination"):
"""Safely create or update a resource"""
try:
if resource_type == "destination":
return client.destinations.create(payload)
except ResourceConflictError:
# Fallback to updating the existing resource
if resource_type == "destination":
existing = client.destinations.list(name=payload["name"], per_page=1)
if existing:
return client.destinations.update(existing[0].id, payload)
else:
raise

Best Practices

Following established best practices for rate limiting and retries will help you build more reliable and efficient applications. These guidelines are based on industry standards and proven patterns.

1. Respect Server Wait Times

Respect Server Wait Times
def respect_server_wait(exc):
"""Respect server-provided wait times"""
if hasattr(exc, 'retry_after') and exc.retry_after:
wait_time = exc.retry_after
print(f"Server requests wait time: {wait_time}s")
time.sleep(wait_time)

2. Use Pagination for Large Datasets

Pagination for Large Datasets
def get_all_flows():
"""Get all flows using pagination"""
all_flows = []
page = 1
per_page = 100

while True:
try:
flows = client.flows.list(per_page=per_page, page=page)
if not flows:
break
all_flows.extend(flows)
page += 1
except RateLimitError as e:
wait_time = e.retry_after or 30
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)

return all_flows

3. Implement Jitter for Retries

Jittered Retries
import random

def jittered_backoff(base_wait, max_jitter=0.1):
"""Add jitter to prevent thundering herd"""
jitter = random.uniform(0, max_jitter)
return base_wait * (1 + jitter)

def retry_with_jitter(func, max_retries=3):
for attempt in range(max_retries):
try:
return func()
except RateLimitError as e:
if attempt < max_retries - 1:
base_wait = e.retry_after or (2 ** attempt)
wait_time = jittered_backoff(base_wait)
print(f"Retrying in {wait_time:.2f}s...")
time.sleep(wait_time)
continue
raise

Monitoring and Alerting

Proactive monitoring of rate limit usage helps prevent unexpected failures and allows you to optimize your application's API usage patterns. Implementing proper alerting ensures you can respond quickly to issues.

Rate Limit Monitoring

Rate Limit Monitoring
def monitor_rate_limits():
"""Monitor and alert on rate limit usage"""
try:
rate_limits = client.metrics.get_rate_limits()

for resource_type, limits in rate_limits.items():
used = limits.get('used', 0)
limit = limits.get('limit', 0)

if limit > 0:
usage_percent = (used / limit) * 100
if usage_percent > 80:
print(f"⚠️ High rate limit usage for {resource_type}: {usage_percent:.1f}%")
elif usage_percent > 90:
print(f"🚨 Critical rate limit usage for {resource_type}: {usage_percent:.1f}%")
except Exception as e:
print(f"❌ Error monitoring rate limits: {e}")

Next Steps

Learn More

For detailed rate limiting strategies and retry patterns, see the Rate Limits & Idempotency page in our Nexla SDK guides.