Pagination and errors.
Every list endpoint in the API shares one pagination model, and every error shares one shape. Handle them once in your client and the whole surface behaves consistently.
Cursor pagination
List responses are wrapped in an envelope:
{
"next": "https://api.liberty91.com/api/v1/iocs/?cursor=cD0yMDI2LTA3LTAx",
"previous": null,
"results": [ ... ]
}Follow next until it is null, and you have walked the full result set. The next
value is a complete URL, so you do not need to build cursor parameters yourself.
Cursors are opaque; do not parse or store them beyond the current walk.
A page holds at most 25 records, and 25 is also the default. You can pass a smaller
?page_size=, but a larger one is clamped to 25 rather than rejected, so the only way to
get more records is to follow next. A first request needs no page parameter at all:
curl "https://api.liberty91.com/api/v1/iocs/" \
-H "X-API-Key: $LIBERTY91_API_KEY"A complete walk in Python looks like this:
import requests
url = "https://api.liberty91.com/api/v1/iocs/"
headers = {"X-API-Key": LIBERTY91_API_KEY}
iocs = []
while url:
page = requests.get(url, headers=headers).json()
iocs.extend(page["results"])
url = page["next"]Cursor pagination stays consistent while you walk it, even as new data arrives, which is
exactly what you want when syncing IOCs on a schedule. Combine it with the since
filter on the IOC list to fetch only what is new since your last run.
Errors
Errors use standard HTTP status codes with a small JSON body:
{ "detail": "Missing scope: iocs.read" }| Status | Meaning | What to do |
|---|---|---|
400 | An unknown filter value, a malformed date, or an invalid body | Read detail; it names the field |
401 | Missing, malformed, revoked or expired API key | Check the header and the key's validity |
403 | Key lacks the required scope | Grant the scope named in detail |
404 | Object not found, or not in your account | Verify the ID belongs to your tenant |
409 | The user who created the key is no longer active on the account | Rotate the key |
429 | Rate limited or monthly credits exhausted | Back off for the Retry-After seconds; check X-Credits-Remaining to tell the two apart |
503 | An upstream dependency is temporarily unavailable | Retry with backoff; write operations are not retried server-side, so resubmit them |
A filter value we do not recognise is a 400, not an empty result set. A
mistyped sector or status silently returning zero rows would read as "nothing
matches", which is the opposite of the truth.
Failed requests (4xx and 5xx) never consume credits, so defensive retry logic costs you nothing beyond the rate limit.
Frequently asked questions
How does pagination work in the Liberty91 API?
List endpoints use cursor pagination. Each response contains next and previous URLs
alongside the results array; follow next until it is null and you have the full set.
What is the maximum page size?
25 items, which is also the default. A larger page_size is clamped to 25 rather than
rejected, so follow next to get the rest of the set.
What format are API errors in?
Standard HTTP status codes with a JSON body of the form {"detail": "message"}. 4xx and
5xx responses are never charged credits.