Rate limits and bulk data sync¶
The Public API enforces rate limits to protect platform stability. This guide covers the limits, how to handle them, and how to structure an initial bulk import safely.
Limits¶
Limits may change
These limits apply during the Beta test phase and may be adjusted based on usage patterns and platform stability needs.
| Scope | Limit |
|---|---|
| Default requests per second, per tenant, per endpoint, per HTTP method | 20 |
POST /api/v2/management/communications/, per company and franchise context |
10 per minute |
POST /api/v2/management/webhook-registrations/{id}/test/, per company and franchise context, all registrations together |
5 per minute |
Maximum page_size on list endpoints |
100 |
Each counter is scoped to one combination of HTTP method, endpoint and tenant.
GET and POST on the same endpoint therefore use separate counters, and two
different endpoints never share one. For a franchise integration, the tenant is
the franchisor and company pair, so each company you operate on carries its own
counter.
When the rate limit is exceeded, the API returns 429 Too Many Requests.
Handling 429 responses¶
A rejected request returns 429 with the standard error body:
No Retry-After header is returned. Base your wait on the window below rather
than looking for a header.
The default counter is a fixed 1-second window: it starts on your first request in that window and resets 1 second later, rather than sliding with each call. In practice:
- Wait 1 second after a default-limit
429. For communication sends and webhook tests, wait 60 seconds. Retry only after a definite rejection; a lost response must not trigger an automatic resend. - If the retry is also rejected, apply exponential backoff (2 s, 4 s, 8 s) and lower your sustained request rate: repeated rejections mean the calling pattern is above the limit, not that a single burst was unlucky.
- Do not retry immediately, and do not retry in a tight loop.
See Errors and status codes for general retry guidance.
Initial data sync (bulk import)¶
Use exports for large bulk syncs
The Public API is not recommended for full historical exports or large bulk data syncs. Use report exports when available, or contact your account manager to request an export.
The example below is only for limited initial syncs where using the Public API is necessary.
Pulling your full dataset for the first time is the highest-risk moment for rate limit
violations. A high-frequency full scan can exhaust the limit quickly and cause 429 errors
or timeouts on large datasets.
Recommended pattern:
- Break the import into date-range windows (one month at a time is a safe default).
- Use the maximum
page_sizeof 100 to minimize the number of requests. - Add a pause between requests (300 ms minimum; 300-500 ms recommended) to stay within the per-second limit.
- Do not repeat a full scan after the initial import. Switch to incremental sync or webhooks for ongoing updates.
# Example: paginate one month at a time to limit the result set
curl -X GET \
-H 'X-Api-Key: YOUR_API_KEY' \
-H 'X-Client-ID: YOUR_CLIENT_ID' \
-H 'X-Company-ID: YOUR_COMPANY_ID' \
'https://YOUR_ENV_DOMAIN/api/v2/management/clients/?page_size=100&page=1&joined_after_date=2025-01-31'
Replace YOUR_ENV_DOMAIN with public.staging.bsport.io (staging) or public.production.bsport.io (production). See Environments.
Increment page until next_page is null: that signals the last page. See
Pagination and filtering for the response
envelope and a reusable pagination loop.
Ongoing sync after the initial import¶
Once the initial import is complete, avoid rescanning the full dataset on each run.
Preferred approaches:
- Webhooks: create a V2 registration for a supported Public API event such as
client.created,booking.createdorclass.updatedto receive updates in near real time. See the event catalogue. - Incremental polling: use the endpoint's documented query parameters, such as
created_afterfor bookings and invoices. Response field names such ascreated_atare not necessarily accepted as query parameters. - Targeted lookups: when you only need to refresh a specific client or booking, fetch by ID rather than re-listing.
client-class-passes endpoint at scale¶
Filter this endpoint to avoid timeouts
GET /api/v2/management/client-class-passes/ can return 502 or 504 for companies
holding a large number of passes, because an unfiltered result set is expensive
to build. The request fails rather than returning partial data, so your
integration sees a timeout instead of an incomplete page.
Always send a narrow filter such as client_ids or a date range. Do not scan
this endpoint unfiltered, particularly during an initial import.