Network/API calls return {:ok, value} or {:error, %TypeSafeSDK.Error{}}.

case TypeSafeSDK.list_models(client) do
  {:ok, response} ->
    Enum.map(response.models, & &1.name)

  {:error, %TypeSafeSDK.Error{type: type, status: status, message: message}} ->
    IO.inspect(%{type: type, status: status, message: message}, label: "API failure")
end

error.type uses these primary values:

  • :bad_request (400)
  • :authentication (401)
  • :permission_denied (403)
  • :not_found (404)
  • :unprocessable_entity (422)
  • :rate_limit (429)
  • :internal_server (5xx)
  • :api_error for other unsuccessful statuses
  • :connection, :timeout, :response_validation, :configuration

The error also preserves status, body, headers, TypeSafe request ID, retry-after milliseconds, the raw Pristine.Response, and an endpoint string when Pristine transport metadata provides it. Successful SystemOneResponse and ListModelsResponse values likewise retain request_id and raw_http_response; use their request_id!/1 / raw_http_response!/1 accessors when metadata is required rather than optional.

TypeSafeSDK.RetryPolicy defaults to the supplied Python SDK's public values: two retries, 0.5-second exponential initial backoff, 5-second cap, 0.25 jitter, retrying 408/429/5xx plus connection/timeouts, honoring Retry-After, and a 30-second retry budget. The budget includes the first attempt and stops before a retry delay would reach the limit, preserving the last error. Set timeout: nil in the retry policy to remove that budget. Zero initial backoff or a zero cap disables backoff; Retry-After remains effective unless explicitly disabled.

Pristine 0.3.0 status-range prerequisite

The Python SDK retries every HTTP status from 500 through 599. TypeSafeSDK represents that rule with Pristine 0.3.0's provider-level status_retry_ranges contract rather than enumerating one hundred integer overrides or changing Pristine's global defaults. Exact caller-selected HTTP statuses continue to use exact provider overrides, while the default full 5xx set compacts to one 500..599 rule.

Per-call RetryPolicy values rebuild the provider profile used for that call, so replacing http_statuses really changes classification rather than only changing the backoff loop. respect_retry_after: false also clears Pristine's classified retry delay for that call.

0.2.0 local errors and policy queries

Strict constructors/preparation/evaluation use :invalid_request, with error.path component lists, field_path and structured details. Relational response failures retain :response_validation plus the original HTTP status/body/headers/request ID. Batch failures add details.batch_index; worker crashes use :task_exit, and worker timeouts use :timeout with details.scope == :batch. Missing required runtime guarantees use :runtime_capability. evaluate! / system_one! raise the normalized error.

TypeSafeSDK.Error.retryable?(error)
TypeSafeSDK.Error.retryable?(error, client.retry)
TypeSafeSDK.Error.retry_after(error) # milliseconds or nil

Retryability describes the error category under a policy, not remaining attempt budget or an instruction to automatically resubmit exhausted work. Validation and local batch-lifecycle errors are not classified as transport-retryable.

At-least-once execution and ambiguous failures

Retries can execute an evaluation more than once. A connection failure after submission does not prove that the server failed to process or bill the request. The SDK preserves the existing retry defaults; disable retry explicitly when the risk of replay outweighs resilience. A batch task timeout or local cancellation also cannot undo a remote operation. Never treat retryability as an exactly-once execution guarantee, and do not infer total billed tokens from the final successful response alone. Error bodies/details are available to the caller but must not be dumped into production telemetry without an explicit privacy review.

0.3.0 retry inheritance and cancellation

Client retry configuration is now inherited structurally by per-call keyword/map overrides: omitted keys keep the client value, explicit keys win, and retry: false disables retry for that call. TypeSafe resolves the policy only; Pristine 0.4 owns attempt execution, backoff, Retry-After handling, result classification and cancellation-aware retry waits.

cancellation: Pristine.Cancellation.new() is forwarded unchanged to Pristine. A cancelled operation surfaces as :cancelled, is non-retryable, and remains distinct from timeout/connection/API errors. Cancellation terminates verified local unary HTTP work when the configured Pristine transport advertises support, but it is not a remote rollback or exactly-once guarantee.

TypeSafeSDK.Error.metadata/1 exposes bounded structural diagnostics without raw bodies, credentials or question/state text.