For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 null on the final page

    • items: the current page of results

  • Paging logic

    • Request next_offset while has_more is true

    • You’ve reached the end when has_more is false

    • GET /data/bookings is ordered by reservation_start (asc), then booking_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.

  1. Complete one bootstrap import and upsert every page locally.

  2. Save the highest updated_at from bookings and changed_at from pricing only after all pages complete successfully.

  3. On the next run, request:

    • /data/bookings?updated_since=<ISO-8601 timestamp>

    • /data/pricing?changed_since=<ISO-8601 timestamp>

  4. Use a small overlap (for example, one second), and upsert by stable identifiers so retries and boundary timestamps are safe.

  5. Continue requesting next_offset while has_more is true.

  6. Send the previous response's ETag in If-None-Match for the exact same URL. A 304 Not Modified response 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=bootstrap on every page;

  • follow next_offset until has_more is false;

  • you may page through both bookings and pricing;

  • do not combine bootstrap mode with updated_since or changed_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 from and to, for a window of at most 90 days;

  • pricing with okupai_property_id, from, and to, 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