Error Handling Model

The Saner Python SDK provides a structured exception handling system designed for reliable automation, predictable integrations, and faster debugging. Instead of returning ambiguous failures, the SDK raises clear, categorized exceptions that help developers immediately identify the cause of an issue.

Errors typically fall into four categories:

  • validation errors (client-side)
  • authentication errors
  • API response errors
  • retry and request failures, including rate limiting

All SDK exceptions inherit from a single base exception class, making them easy to manage in both simple scripts and enterprise workflows. Every exception also carries structured context (status code, response body, method, URL, attempt count) so you can branch on it programmatically instead of parsing the message string.

Note: Error handling behaves identically across all three client classes (SanerClient, PlatformClient, CvemClient); the examples below use flat resource access (client.Organization..., client.Patch...) as if client is a PlatformClient/CvemClient scoped to that resource's product, since that's the shortest form. On a SanerClient instance, prefix with the namespace instead: client.platform.Organization..., client.cvem.Patch.... Everything else about the exception shown is unchanged. See the "Client Types" guide for the full comparison.


Exception Hierarchy

The SDK defines a clean exception structure:

SanerError
├── SanerAuthError
├── SanerRequestError
│     └── SanerRateLimitError
└── SanerResponseError

This allows developers to:

  • catch all SDK errors together
  • handle authentication failures separately
  • detect rate limiting precisely, distinct from other network issues
  • detect retry/network issues precisely
  • identify malformed API responses quickly

Example:

from saner.exceptions import SanerError

try:
    response = client.Organization.get(organization="Example")
except SanerError as e:
    print(e)

Structured Exception Attributes

Every SanerError (and its subclasses) carries the following attributes, populated whenever the information is available:

AttributeTypeDescription
messagestrHuman-readable description of the failure
status_codeint | NoneHTTP status code, if a response was received
response_bodystr | NoneResponse body, redacted the same way logs are and truncated for very large bodies
methodstr | NoneHTTP method or RPC method name involved
urlstr | NoneFully-qualified request URL
attemptsint | NoneNumber of attempts actually made before the error was raised

Because response_body goes through the same redaction as the SDK's own logs, it's safe to forward a caught exception's fields into your own logging or APM stack (Sentry, Datadog, etc.) without re-checking it for credentials first. A clean body is passed through byte-for-byte, so this doesn't affect debugging output that never had a secret in it.

This means production error handlers can act on structured data instead of parsing text:

from saner.exceptions import SanerRequestError

try:
    response = client.Patch.getRemediationJobStatus(accountid="Default", name="job1")
except SanerRequestError as e:
    print("status:", e.status_code)
    print("attempts:", e.attempts)
    print("endpoint:", e.method, e.url)

Validation Errors (Client-Side)

Before sending a request, the SDK validates parameters locally using its validation engine.

Validation errors occur when:

  • required parameters are missing
  • incorrect parameter types are provided
  • enum values are invalid
  • formats (email, IP, MAC, date) are incorrect
  • password strength requirements fail
  • list/dict structures are invalid

Example:

response = client.Organization.get(organization=123)

Error:

TypeError: organization must be string

These errors stop execution immediately and prevent invalid requests from reaching the server.


Authentication Errors

Authentication failures occur when API credentials are invalid or permissions are insufficient.

Raised exception:

SanerAuthError

Triggered by:

  • HTTP 401 (Unauthorized)
  • HTTP 403 (Forbidden)

Example:

from saner.exceptions import SanerAuthError

try:
    response = client.Organization.get(organization="Example")
except SanerAuthError as e:
    print("Authentication failed:", e)

Authentication errors are never retried automatically because credentials must be corrected manually.


Rate Limit Errors

Rate limit failures occur when the Saner API has throttled your account due to too many requests.

Raised exception:

SanerRateLimitError

Triggered by:

  • HTTP 429 (Too Many Requests)

Unlike other failures, HTTP 429 responses are automatically retried by the SDK before this exception is ever raised, up to max_retries times, honoring the server's Retry-After header when present (capped at 60 seconds per attempt). SanerRateLimitError is only raised once retries are exhausted.

It carries one extra attribute beyond the base exception:

AttributeTypeDescription
retry_afterfloat | NoneSeconds the server asked you to wait, parsed from Retry-After, if provided

Example:

from saner.exceptions import SanerRateLimitError

try:
    response = client.Vulnerability.getAssets()
except SanerRateLimitError as e:
    print("Rate limited. Retry after:", e.retry_after, "seconds")

SanerRateLimitError is a subclass of SanerRequestError, so existing code that catches SanerRequestError will continue to catch rate-limit failures without any changes.

See the "Retry and Timeout Behaviour" guide for the full retry policy.


API Response Errors

Response errors occur when the server returns a response that cannot be parsed correctly.

Raised exception:

SanerResponseError

Typical causes:

  • invalid JSON returned by server
  • unexpected response structure
  • corrupted response payload
  • the response body exceeded the client's max_response_bytes limit (see the "Client Configuration" guide), raised before or immediately after the oversized body would have been fully read, so it never sits in memory

Example:

from saner.exceptions import SanerResponseError

try:
    response = client.Organization.get(organization="Example")
except SanerResponseError as e:
    print("Invalid response received:", e)

These errors indicate a server formatting issue rather than a connectivity failure.


Retry Failures and Request Errors

Request-level failures occur when communication with the server fails even after automatic retries.

Raised exception:

SanerRequestError

Triggered by:

  • connection failures
  • request timeouts
  • SSL verification failures
  • retry exhaustion after HTTP 500 / 502 / 503 / 504 responses
  • unexpected transport-level errors

Example:

from saner.exceptions import SanerRequestError

try:
    response = client.Organization.get(organization="Example")
except SanerRequestError as e:
    print("Request failed:", e)

Note: Not every failing request is retried the same number of times. Read-only and other idempotent calls are always retried on a transient failure; calls that create or modify data are only retried when they are known not to have reached the server, or when the client is configured with retry_on_write=True. See the "Retry and Timeout Behaviour" guide for the full policy. This affects how many attempts you'll see before SanerRequestError is raised.


Example: Basic Error Handling Pattern

For lightweight scripts:

try:
    response = client.Organization.get(organization="Example")
except Exception as e:
    print(e)

This captures all SDK errors and validation failures.


Example: Production-Grade Error Handling Pattern

For automation pipelines and integrations at scale:

from saner.exceptions import (
    SanerAuthError,
    SanerRateLimitError,
    SanerRequestError,
    SanerResponseError,
)

try:
    response = client.Organization.get(organization="Example")

except SanerAuthError:
    print("Check API credentials or permissions")

except SanerRateLimitError as e:
    print(f"Rate limited, back off for {e.retry_after or 'a while'} seconds")

except SanerRequestError as e:
    print(f"Network issue or retry limit reached (status={e.status_code}, attempts={e.attempts})")

except SanerResponseError:
    print("Unexpected server response format")

Note: SanerRateLimitError must be caught before SanerRequestError in an except chain, since it is a subclass of it. Python matches the first applicable except clause in order.

This enables smarter recovery strategies depending on failure type.


Example Error Messages Returned by the SDK

The SDK produces clear, human-readable error messages such as:

organization must be string
organization cannot be empty
email must be a valid email
password must contain a special character
Authentication failed (HTTP 401)
Rate limited (HTTP 429) after 4 attempts: ...
HTTP error 503 after 4 attempts
Timeout after 4 attempts
SSL error: certificate verify failed

Note: The attempt count in these messages reflects attempts actually made, not always max_retries + 1. A write operation that isn't eligible for retry (see the "Retry and Timeout Behaviour" guide) can fail after a single attempt even with max_retries=3 configured.

These messages help developers quickly identify integration mistakes without inspecting raw HTTP responses.


Benefits for Automation and Production Workflows

The structured exception system helps developers:

  • detect failures instantly
  • distinguish validation vs authentication vs rate-limiting vs network issues
  • implement retry-aware automation logic
  • reduce debugging time
  • build resilient multi-environment integrations
  • maintain predictable SDK behavior at scale

This makes the Saner Python SDK well-suited for both simple scripts and enterprise automation pipelines.


Did this page help you?