Skip to main content

Error Hierarchy

The Python SDK has a detailed error hierarchy with specific exception types for common HTTP status codes:
from keystoneos.exceptions import (
    APIError,              # Base for all API errors
    AuthenticationError,   # 401
    ForbiddenError,        # 403
    NotFoundError,         # 404
    ConflictError,         # 409
    ValidationError,       # 422
    RateLimitError,        # 429
    ServerError,           # 5xx
    WebhookVerificationError,
)

try:
    settlement = await client.settlements.get("non-existent-id")
except NotFoundError:
    print("Settlement does not exist")
except ConflictError as e:
    print(f"State conflict: {e.message}")
except ValidationError as e:
    print(f"Invalid input: {e.detail}")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after}s")
except ForbiddenError:
    print("Insufficient permissions")
except AuthenticationError:
    print("Check your M2M credentials")
except ServerError:
    print("KeyStone API issue")
except APIError as e:
    print(f"API error {e.status_code}: {e.message}")
ExceptionStatusPropertiesDescription
APIErroranymessage, status_code, detail, response_bodyBase class for all API errors
AuthenticationError401messageToken fetch failed or invalid
ForbiddenError403message, detailMissing scope or suspended platform
NotFoundError404messageResource not found
ConflictError409message, detailState conflict
ValidationError422message, detailRequest validation failed
RateLimitError429retry_after, detail, response_bodyRate limit exceeded
ServerError5xxmessage, status_codeServer-side error
WebhookVerificationError-messageSignature verification failed

Retry Behavior

Automatic retries on 429 and 5xx with exponential backoff and jitter:
SettingDefaultDescription
max_retries3Max retry attempts
base_delay0.5Base delay in seconds
max_delay30.0Maximum delay in seconds
retryable_status_codes{429, 500, 502, 503, 504}Which status codes trigger retry
Strategy: base_delay * 2^attempt * random(0.5, 1.5) (jitter prevents thundering herd) 429 responses: Respects the Retry-After header if present. Non-retryable: 4xx errors (except 429) fail immediately.

Recovery Patterns

Catching specific errors

try:
    await client.settlements.submit_compliance_decision(
        settlement_id, {"decision": "approve"}
    )
except ConflictError:
    # Settlement is no longer in COMPLIANCE_CHECKING state
    settlement = await client.settlements.get(settlement_id)
    print(f"Current state: {settlement.state}")
except ForbiddenError:
    # Missing compliance:write scope
    print("This environment doesn't have compliance permissions")

Rate limit handling

The SDK retries 429 automatically. For custom handling after exhaustion:
try:
    await client.settlements.list()
except RateLimitError as e:
    # All retries exhausted
    print(f"Rate limited. Retry after {e.retry_after} seconds")
    await asyncio.sleep(e.retry_after or 60)
    # Try again...

Idempotent retries

key = client.generate_idempotency_key()

# Safe to call multiple times - same key returns existing resource
settlement = await client.settlements.create({
    "idempotency_key": key,
    # ...
})