Errors & pagination
Handle the SDK's typed exception hierarchy, understand automatic retries, and page through list responses.
The SDK turns HTTP failures into a small hierarchy of typed exceptions and pages list responses with a predictable shape. Both behave identically on the synchronous and asynchronous clients.
The exception hierarchy
Every error raised by the SDK derives from RankError. API responses with a non-2xx status
become an APIError subclass chosen by status code; transport problems become
APIConnectionError (or its subclass APITimeoutError).
RankError
├── APIError
│ ├── AuthenticationError (401)
│ ├── PermissionDeniedError (403)
│ ├── NotFoundError (404)
│ ├── ConflictError (409)
│ ├── UnprocessableEntityError (422)
│ ├── RateLimitError (429)
│ └── InternalServerError (5xx)
├── APIConnectionError
└── APITimeoutError
All of these are importable directly from rank (for example rank.NotFoundError).
APIError attributes
Any APIError carries the full context of the failed request:
| Attribute | Type | Description |
|---|---|---|
message | str | Human-readable error description. |
status_code | int | HTTP status code. |
body | Any | Parsed response body (JSON when available, otherwise the raw string). |
request | httpx.Request | The request that failed. |
response | httpx.Response | The response received. |
RateLimitError additionally exposes retry_after — the value of the Retry-After header in
seconds, or None if the server did not send one.
Handling errors
Catch the specific subclass you care about, and fall back to the broader APIError for anything
else. Because the subclasses are ordered from specific to general, list the narrow ones first.
import rank
with rank.Rank() as client:
try:
client.pentests.retrieve(999999)
except rank.NotFoundError as e:
print(f"Not found: {e.message} (status {e.status_code})")
except rank.AuthenticationError:
print("Invalid or missing API key")
except rank.PermissionDeniedError:
print("Insufficient permissions")
except rank.UnprocessableEntityError as e:
print(f"Validation error: {e.body}")
except rank.RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except rank.InternalServerError:
print("Server error, try again later")
except rank.APITimeoutError:
print("Request timed out")
except rank.APIConnectionError:
print("Network error — check your connection")
except rank.APIError as e:
print(f"API error {e.status_code}: {e.message}") import rank
async def fetch(client: rank.AsyncRank, pentest_id: int):
try:
return await client.pentests.retrieve(pentest_id)
except rank.NotFoundError as e:
print(f"Not found: {e.message} (status {e.status_code})")
except rank.RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except rank.APIError as e:
print(f"API error {e.status_code}: {e.message}") Automatic retries
The client automatically retries transient failures — network errors, timeouts and 5xx
responses — up to max_retries times (default 2). Once the retries are exhausted, the final
error is raised as one of the exceptions above.
Client errors in the 4xx range (validation, not found, permission denied, …) are not
retried, because retrying them would never succeed; they are raised immediately. Tune the
behaviour per client:
import rank
# Retry network/5xx failures up to 4 times
client = rank.Rank(max_retries=4)
# Disable retries entirely
client = rank.Rank(max_retries=0)
See Client setup for the related timeout option.
Pagination
List methods accept page and per_page keyword arguments and return a response object with
two parts: items, the records on the current page, and pagination, the metadata describing
where you are in the result set.
import rank
with rank.Rank() as client:
response = client.pentests.list(page=1, per_page=20)
for p in response.items:
print(p.id, p.name)
meta = response.pagination
print(f"Page {meta.current_page} of {meta.total_pages} — {meta.total} total")
Pagination metadata
The pagination object exposes these fields:
| Field | Description |
|---|---|
current_page | The page you just fetched. |
total_pages | Total number of pages available. |
per_page | Items requested per page. |
total | Total number of records across all pages. |
count | Number of records on the current page. |
Iterating every page
To walk the whole result set, request pages until current_page reaches total_pages:
import rank
with rank.Rank() as client:
page = 1
while True:
response = client.pentests.list(page=page, per_page=50)
for p in response.items:
print(p.id, p.name)
if response.pagination.current_page >= response.pagination.total_pages:
break
page += 1
Auto-paging helpers
For convenience, the SDK also ships page helpers — SyncPage and AsyncPage (in
rank._utils._pagination) — that wrap a page of results and expose:
has_next_page()— whether another page exists.next_page()— fetch the next page (await it onAsyncPage).auto_paging_iter()— iterate over every item across all pages, fetching as it goes.
# Iterate all items without managing page numbers yourself
for item in page.auto_paging_iter():
print(item)
With errors and pagination handled, revisit the Resources tour for the full set of list endpoints, or build something end-to-end from the Cookbook.