Skip to content

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:

{
  "detail": "Rate limit exceeded",
  "code": "BSPORT-429"
}

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:

  1. 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.
  2. 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.
  3. 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:

  1. Break the import into date-range windows (one month at a time is a safe default).
  2. Use the maximum page_size of 100 to minimize the number of requests.
  3. Add a pause between requests (300 ms minimum; 300-500 ms recommended) to stay within the per-second limit.
  4. 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.created or class.updated to receive updates in near real time. See the event catalogue.
  • Incremental polling: use the endpoint's documented query parameters, such as created_after for bookings and invoices. Response field names such as created_at are 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.