Rate Limits & Idempotency
Rate Limiting Signals
- The SDK raises
RateLimitErrorfor HTTP429responses.retry_afteris populated from theRetry-Afterheader or JSON body when available (nexla_sdk/client.py). RequestsHttpClientenablesurllib3.Retrywith exponential backoff (backoff_factor=0.5) for transient status codes429,502,503, and504, coveringGET/POST/PUT/DELETE/PATCHcalls (nexla_sdk/http_client.py).- Call
client.metrics.get_rate_limits()to inspect per-resource quotas returned by the API.
from nexla_sdk import NexlaClient
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
When building automation, combine the SDK's retry hints with your own queueing or circuit breaker logic. The built-in retries cover only a handful of attempts; long-running jobs should still honour the server provided wait time.
Idempotency Expectations
GET,PUT, andDELETEendpoints exposed by each resource are idempotent; repeating the same call produces the same state (standard REST semantics).POSTendpoints create new resources. If a duplicate is attempted, Nexla responds with409 Conflict, which the SDK maps toResourceConflictError. You can treat that exception as a signal that the resource already exists.- Use natural identifiers to avoid duplicate POSTs. For example, look up a source by name or metadata before calling
client.sources.create(...). - Many resources expose copy helpers (
copy,copy_entire_tree, etc.) that are idempotent if you supply the same options. Prefer those when cloning flows instead of scripting bespoke object creation. - For streaming destinations, align retries with connector semantics: Kafka sinks are safe to retry because offsets are controlled server side, while file-based sinks may require manual cleanup of partially written files.
A pattern for safe create/update retries:
from nexla_sdk.exceptions import ResourceConflictError
payload = {...}
try:
destination = client.destinations.create(payload)
except ResourceConflictError:
# Fallback to updating the existing resource instead of failing the workflow
existing = client.destinations.list(name=payload["name"], per_page=1)
if existing:
destination = client.destinations.update(existing[0].id, payload)
else:
raise
Always log the operation, resource_type, and resource_id attributes exposed on exceptions; they originate from the SDK and speed up investigations when replays are required.