Pagination
How pagination works
List endpoints use offset-based pagination.
Parameters
limit: number of items to return (
/data/bookings: default 100, max 1000;/data/pricing: default 1000, max 5000)offset: number of items to skip (default: 0)
Response fields
total: total items available after filters
limit: the limit you requested/applied
offset: the offset you requested/applied
has_more: whether another page exists
next_offset: the next offset, or
nullon the final pageitems: the current page of results
Paging logic
Request
next_offsetwhilehas_moreistrueYou’ve reached the end when
has_moreisfalseGET /data/bookingsis ordered byreservation_start(asc), thenbooking_date(asc)
Example request
// Server-side (Node 18+). Don’t expose API keys in the browser.
(async () => {
const BASE_URL = 'https://api.okup.ai';
const API_KEY = process.env.OKUPAI_API_KEY;
const res = await fetch(`${BASE_URL}/data/bookings?from=2025-01-01&to=2025-01-31&limit=200&offset=0`, {
headers: { 'X-API-Key': API_KEY },
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
console.log(data);
})();import os, requests
BASE_URL = "https://api.okup.ai"
API_KEY = os.environ["OKUPAI_API_KEY"]
r = requests.get(
f"{BASE_URL}/data/bookings",
headers={"X-API-Key": API_KEY},
params={"from": "2025-01-01", "to": "2025-01-31", "limit": 200, "offset": 0},
timeout=30,
)
r.raise_for_status()
print(r.json())Iterate pages
Incremental synchronization policy
Repeatedly downloading all historical data is slow, creates avoidable load, and increases the chance that a long sync fails partway through. Delta synchronization transfers only rows that changed and is safer to retry.
Before your deadline, broad requests still succeed but include:
The JSON body also contains an additive warning:
Existing response fields are unchanged. Pricing warnings use changed_since as required_parameter.
Recommended delta workflow
Complete one bootstrap import and upsert every page locally.
Save the highest
updated_atfrom bookings andchanged_atfrom pricing only after all pages complete successfully.On the next run, request:
/data/bookings?updated_since=<ISO-8601 timestamp>/data/pricing?changed_since=<ISO-8601 timestamp>
Use a small overlap (for example, one second), and upsert by stable identifiers so retries and boundary timestamps are safe.
Continue requesting
next_offsetwhilehas_moreistrue.Send the previous response's
ETaginIf-None-Matchfor the exact same URL. A304 Not Modifiedresponse has no JSON body.
Advance your saved timestamp only after every page succeeds. Run one sync job per OkupAI account. As a starting point, poll pricing every 5 minutes and bookings every 15 minutes.
Initial or recovery bootstrap
Use bootstrap mode only for an initial import or disaster recovery:
The first request with sync_mode=bootstrap&offset=0 opens a 30-minute window for that API key. During the window:
include
sync_mode=bootstrapon every page;follow
next_offsetuntilhas_moreisfalse;you may page through both bookings and pricing;
do not combine bootstrap mode with
updated_sinceorchanged_since.
Only one bootstrap may start per API key in any 24-hour period. Starting another after the 30-minute window but before the cooldown ends returns 429 Too Many Requests; wait for Retry-After. A nonzero offset without an active window returns 422—when eligible, restart at offset=0.
Bounded operational reads
These focused requests remain available without delta or bootstrap mode:
bookings with both
fromandto, for a window of at most 90 days;pricing with
okupai_property_id,from, andto, for a window of at most 90 days.
These exemptions are for operational lookups, not repeated account-wide synchronization.
Enforcement errors
An unbounded request after your deadline returns:
A bootstrap started during its cooldown returns:
If the API returns another 429, wait for Retry-After and add random jitter before retrying.
Last updated