Skip to content

Error handling

One exception hierarchy. One try/except at the boundary.

The hierarchy

RtlsError                          (everything the SDK raises)
├── AuthenticationError            401 / login failure
├── PermissionDenied               403
├── NotFound                       404
├── Conflict                       409
├── ValidationError                400 / 422 with field_errors
├── RateLimited                    429 — carries retry_after_seconds
├── ServerError                    5xx
├── ConnectionError                transport-level (DNS, timeout, etc.)
├── PartialFailureError            compound rolled back; .step says where
├── ReportError                    report-job parent
│   ├── ReportFailed               server reported `error` in poll body
│   └── ReportTimeout              polling exceeded the deadline

Catch RtlsError at the top of your service to fail soft.

Common shape

Every exception carries:

  • status_code — HTTP code (or None for transport errors)
  • request_url / request_method — the call that failed
  • response_body — parsed body, redacted (no tokens/passwords)
  • error_envelope — the server's error message string
from rtls_sdk import RtlsError, NotFound

try:
    tag = client.tags.get("nope")
except NotFound as exc:
    log.info("missing tag: %s", exc.request_url)
except RtlsError as exc:
    log.error("RTLS call failed: %s (status=%s)", exc, exc.status_code)
    raise

401 — silent re-login

In the default credentials mode (username + password / from_env), 401 is invisible: the SDK silently re-authenticates and replays the request. You don't see an exception — you see the call succeed on the second attempt.

In BYO-token mode, the SDK can't re-login (no password stored) and raises AuthenticationError immediately. See BYO token.

Validation errors

ValidationError carries field_errors parsed from the server body:

from rtls_sdk import ValidationError

try:
    client.sites.create(name="")
except ValidationError as exc:
    for field, problems in (exc.field_errors or {}).items():
        log.warning("field=%s: %s", field, problems)

Compound rollback

Compounds (e.g. tags.create(attached_zone=...)) raise PartialFailureError when a step in the saga fails. The step attribute is a stable string identifying where:

from rtls_sdk import PartialFailureError

try:
    tag = client.tags.create(name="…", attached_zone="z-1")
except PartialFailureError as exc:
    if exc.step == "attach_zone":
        # tag was rolled back; safe to retry from scratch
        ...
    raise

See compound workflows for the step taxonomy.

Rate limits

RateLimited is the one exception the SDK does not retry for you. See rate limiting for the surface-don't-retry philosophy and a copy-pasteable retry helper.