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 (orNonefor transport errors)request_url/request_method— the call that failedresponse_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.