Skip to content

Pagination and filtering

The examples in this guide use V2. For V1, see Pagination and filtering (V1).

List endpoints return paginated results. Use pagination to control result size and filtering to narrow the dataset to what your integration needs.

Pagination

Most list endpoints accept:

  • page: the page number to return. Defaults to 1.
  • page_size: how many results per page. Defaults to 50, maximum 100.
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=2&page_size=50'

Replace YOUR_ENV_DOMAIN with public.staging.bsport.io (staging) or public.production.bsport.io (production). See Environments.

Response shape

A paginated response wraps the records in an envelope:

{
  "count": 253,
  "next": "https://YOUR_ENV_DOMAIN/api/v2/management/clients/?page=3&page_size=50",
  "next_page": 3,
  "previous": "https://YOUR_ENV_DOMAIN/api/v2/management/clients/?page=1&page_size=50",
  "results": []
}
Field Meaning
count Total number of records matching the request, across all pages.
next Full URL of the next page, or null on the last page.
next_page Page number of the next page, or null on the last page.
previous Full URL of the previous page, or null on the first page.
results The records for the current page.

Paginate through every page

Stop when next_page is null. Prefer this over comparing the length of results against page_size: the envelope states the end of the collection directly, so your loop does not depend on a full final page.

def fetch_all(path, params=None):
    results = []
    page = 1
    while page is not None:
        response = requests.get(
            f"{BASE_URL}{path}",
            headers=HEADERS,
            params={**(params or {}), "page": page, "page_size": 100},
        )
        response.raise_for_status()
        body = response.json()
        results.extend(body["results"])
        page = body["next_page"]
    return results

Tune page size for performance

Start with a moderate page_size (25-100) and adjust based on latency and the amount of data you need to process.

Ordering

Use the ordering parameter to sort results. Prefix a field with - (dash) for descending order, and separate several fields with commas to sort on more than one key.

ordering=joined_date
ordering=-joined_date
ordering=lastname,-joined_date

Ordering is available on a defined set of list endpoints, and each one accepts its own list of sortable fields:

Endpoint Sortable fields
/api/v2/management/clients/ client_account_balance, email, fullname, joined_date, lastname
/api/v2/management/bookings/ created_at, class_start_at
/api/v2/management/client-class-passes/ available_credits_amount, purchased_date, starting_date, ending_date
/api/v2/management/client-subscriptions/ created_at, first_billing_date, name, recurrent_price
/api/v2/management/class-passes/ name, price
/api/v2/management/subscriptions/ name, recurrent_price
/api/v2/management/client-notes/ creation_date, id
/api/v2/management/tags/ tag_group_id, id
/api/v2/management/tag-groups/ id, name
/api/v2/management/appointment-passes/ name, price
/api/v2/management/client-appointment-passes/ available_credits_amount, purchased_at
/api/v2/management/coupons/ created_at, expiration_date, name
/api/v2/management/coupon-usages/ discount_amount, used_at
/api/v2/management/gift-cards/ fixed_amount, name, validity_days
/api/v2/management/invoices/ created_at, issued_at, total_amount
/api/v2/management/invoice-items/ id, total_amount

Each endpoint's API Reference entry lists the same set, and is authoritative if the two ever differ.

Unsupported ordering fields are ignored, not rejected

A field that an endpoint does not support is discarded rather than returning an error. The request succeeds with 200 and the results come back in the endpoint's default order. A misspelled field name therefore fails silently: check the sort order of the response when you first wire up ordering.

Filtering

Filters are exposed as query parameters on each endpoint. Common patterns include:

  • *_ids for multiple IDs (comma-separated, e.g. client_ids=123,456)
  • *_after and *_before on a timestamp field (e.g. created_after for created_at)
  • *_after_date and *_before_date on a date field (e.g. joined_after_date for joined_date)

See Date and timestamp filters.

Default filters

Some endpoints apply a default filter when you omit the parameter, so an unfiltered request does not always return the complete dataset.

GET /api/v2/management/classes/ and /api/v2/management/appointments/ filter on the start datetime through starts_after and starts_before (ISO 8601). Each one applies its own default when omitted:

Parameter Omitted value
starts_after now
starts_before now + 30 days

A request to /classes/ with no parameters therefore returns only the next 30 days of classes. Pass starts_after explicitly to read classes that have already taken place, and starts_before to read further ahead.

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/classes/?starts_after=2026-01-01&starts_before=2026-03-31T23:59:59.999999'

Date and timestamp filters

Two filter families take different formats. Read the table to pick the format for the parameter you send.

Filter family Format Where it applies
*_after_date, *_before_date Date only, YYYY-MM-DD. Do not send a timestamp. clients (joined_*), client-class-passes (purchased_*)
*_after, *_before ISO 8601 timestamp, preferably with its UTC offset bookings (created_*, class_start_*), client-appointment-passes (purchased_*), invoices (created_*, issued_*), invoice-items (invoice_issued_*), coupon-usages (used_*), classes and appointments (starts_*)

Both bounds are inclusive. A timestamp without an offset is read in the company's time zone, or in the time zone X-Timezone-Name names when you send it, and so is a plain date sent to a timestamp filter: created_before=2026-03-11 means midnight at the start of March 11, 2026, so it leaves that day out. See Dates, time zones and amounts.

Filtering example

Combine filters as query parameters on the same request.

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/bookings/?client_id=123&class_start_after=2025-01-01'

Validate filters per endpoint

The filter set differs per endpoint. Names follow the patterns above, and the date format depends on the filter family. Check the API reference for the endpoint before adding a filter to your integration.

Supported Management GET list and detail endpoints can replace selected related-resource IDs with their complete objects. Repeat expand[] for multiple fields and use dot notation for nested relationships, up to two levels.

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/bookings/?expand[]=client_id&expand[]=class_id.teacher_id'

Without expansion, class_id is an integer. With expand[]=class_id, the same class_id field contains the class object. An unsupported field or a path deeper than two levels returns 400 Bad Request. Each endpoint's API Reference lists its supported expansion paths. An absent relationship remains null; an unresolved related resource keeps its scalar ID.