Pagination & idempotency
Cursor pagination
List endpoints (GET /bookings, GET /invoices) return newest first and use cursors.
| Query param | Default | Meaning |
|---|---|---|
limit |
25 | Page size, 1–100 |
cursor |
— | The nextCursor from the previous page |
{
"data": [ { "id": "bkg_42", "…": "…" } ],
"hasMore": true,
"nextCursor": "42"
}
Loop until hasMore is false:
let cursor;
do {
const url = new URL("https://searchurtrip.com/api/v1/bookings");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const page = await (await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } })).json();
page.data.forEach(process);
cursor = page.hasMore ? page.nextCursor : undefined;
} while (cursor);
Treat the cursor as opaque. Because pages are keyed on a monotonic id, new bookings created while you paginate never cause duplicates or skipped items.
Idempotency
Networks fail. If a POST /bookings times out you cannot tell whether the booking was created. The Idempotency-Key header makes retrying safe.
POST /api/v1/bookings
Idempotency-Key: 3f0d6a7e-9c5b-4f0a-a1b9-2c1f7c9d5e10
- Required on
POST /bookings(8–100 characters; a UUID is ideal). Missing →400 idempotency_key_required. - Generate one key per logical booking attempt and reuse it for every retry of that attempt.
- Same key + same body → the original response is returned again with the header
Idempotent-Replayed: true. No second booking is created. - Same key + different body →
422 idempotency_key_reuse. - Keys are remembered for 48 hours and are scoped to your client.
KEY_ID=$(uuidgen)
curl -X POST https://searchurtrip.com/api/v1/bookings -H "Idempotency-Key: $KEY_ID" … # creates
curl -X POST https://searchurtrip.com/api/v1/bookings -H "Idempotency-Key: $KEY_ID" … # same booking, Idempotent-Replayed: true
POST /bookings/{id}/cancel is naturally safe to retry: a second cancel returns 409 conflict and changes nothing.