Docs

Docs

Paging, retries and errors

How every list pages, how to retry a write safely, the headers every answer carries, and the error envelope with its codes.

These rules hold for every route in the Reference, whatever the product. A client written against them once works for every list and every write.

Paging a list

A list operation takes two query parameters and answers two fields beside its items:

FieldWhereMeaning
page_sizerequesthow many items at most; 0 or absent is the route's default, more than its maximum is the maximum
page_tokenrequestthe previous answer's next_page_token; absent for the first page
next_page_tokenanswerthe token for the next page; "" on the last page, always present
total_sizeanswerhow many items match, only on routes that can count them exactly; a route without it does not count
curl -H "Authorization: Bearer $INORBIT_TOKEN" \
  "https://api.inorbit.hr/v1/webhooks/endpoints/$ENDPOINT/deliveries?page_size=50"
{ "deliveries": [{ "id": "…" }], "next_page_token": "AXQ…" }

Ask again with page_token=AXQ… and the same other parameters, until next_page_token is "".

  • The token is opaque: send it back as it came, never build or change one.
  • A token belongs to the filters it came from. Sent with any other parameter changed, it is 400 bad_request naming page_token; to change a filter, start again from the first page.
  • A page can be shorter than page_size without being the last. Only an empty next_page_token says the list is done.
  • The order is newest first unless the route documents an order_by.
  • Where a route had a limit parameter before, it still works as an older name for page_size; page_size wins when both are sent.

A list answer with another page also carries a Link header (RFC 8288) to it, so a client that follows links pages without reading the field:

Link: </v1/webhooks/endpoints/ep_1/deliveries?page_size=50&page_token=AXQ…>; rel="next"

The OpenAPI document marks every list operation with x-iohr-list, naming its items and its page fields. A client generated with iohr uses it: beside each list method it adds an iterator over every item, which follows the token for you, stops fetching when you stop reading, and stops at the first error.

LanguageIterator
Gofor d, err := range api.Radar().AllListDigests(ctx, p) (iter.Seq2)
TypeScriptfor await (const d of api.radar.allListDigests())
Pythonfor d in api.radar.all_list_digests():, or async for on the asyncio client
Rustwhile let Some(d) = pages.next().await on api.radar().all_list_digests(&p)
Javafor (Digest d : api.radar().allListDigests()), or .stream()
C#await foreach (var d in client.Radar().AllListDigestsAsync())

Retrying a write

A write that creates or triggers something takes an optional Idempotency-Key header on the operations whose reference lists it, for example creating a webhook endpoint, sending a test event, retrying a delivery, creating a connection or a monitor, and adding or checking a domain. Use a fresh UUID per logical write, and send the same one again when you retry it:

curl -X POST https://api.inorbit.hr/v1/webhooks/endpoints \
  -H "Authorization: Bearer $INORBIT_TOKEN" \
  -H "Idempotency-Key: 6f1c2a9e-5b0d-4c7a-9e3f-2d8b1a4c7e10" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks", "event_types": ["webhook.test"]}'
What happensAnswer
the first call with a keyruns, and its answer (success or refusal) is kept for a day
a repeat with the same key and the same bodythe kept answer, with Idempotency-Replayed: true; nothing runs again
a repeat while the first call still runs409 conflict: wait and send it again
the same key with a different body422 unprocessable: use a new key for a new write
a key that is not 1 to 255 visible characters400 bad_request

Without the header a write runs every time it is sent, so a write without a key should not be retried blindly. Reads (GET) and PUT/DELETE are safe to repeat as they are.

What every answer carries

HeaderMeaning
x-request-idthe call's id, given by the edge. Keep it with your logs and quote it when you ask us about a call
Link: <…>; rel="next"on a list with another page (above)
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Resetthe per-key rate limit: the size of the window, what is left of it, and the seconds until it refills
Retry-Afteron a 429 and on any refusal with a retry detail: the seconds to wait
Idempotency-Replayed: trueon an answer repeated for an Idempotency-Key (above)

Errors

Every error, from the API or from the edge in front of it (a missing token, a scope the token lacks, the rate limit, an unknown route, a service that is down), is the same JSON envelope, never plain text:

{
  "code": "bad_request",
  "error": "page_token does not belong to these filters",
  "details": [{ "type": "field", "field": "page_token", "description": "belongs to other filters" }],
  "request_id": "0199a1f2-7c3e-7d41-9b2a-5e8f0c6d1a23"
}

code is a stable slug and the status follows it; error is a sentence for a person; details are typed entries (a field, an info with a machine-readable reason, a retry with after_seconds); request_id is the call's x-request-id. The codes, their statuses and what to do with each are on Errors. Which to retry:

RetryHow
429 rate_limitedafter Retry-After seconds
503 unavailable, 504 timeoutwith backoff (a growing, randomised wait), a few times
409 conflict on a write with a keysend the same key again after a moment
anything elsedo not retry: fix the request, or read the error

A write without an Idempotency-Key is retried only when you know it did not run.