Migrate from V1 to V2¶
V1 retires on January 15, 2027
The legacy V1 routes outside /api/v1/management/ are removed on November 30, 2026.
Migrate before these dates.
Use this guide to move a REST integration from V1 to V2. It lists every route, field, filter and ID that changes, and the order in which to check them. It does not cover webhook payloads or MCP: see Webhooks and MCP.
Before you switch¶
Moving to V2 requires changes to URLs, field names and stored ID mappings. Start with services and teachers: some IDs keep the same field name but refer to different records, so requests succeed and return the wrong data.
What does not change: the base URL, authentication headers, tenant headers, the pagination envelope, rate limits and the error shape. See Getting started and Pagination and filtering.
Confirm that V2 responds in your environment before you switch:
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/monitoring/health/ping/'
Replace YOUR_ENV_DOMAIN with public.staging.bsport.io (staging) or public.production.bsport.io (production). See Environments.
The comparison in this guide uses the V1 schema and the V2 schema.
Routes¶
Use /api/v2/management/ for V2. Three paths also change. Read the table from left to
right to find the V2 route and what to update.
| V1 route | V2 route | Migration action |
|---|---|---|
/management/passes/ |
/management/class-passes/ |
Update list and detail URLs and generated resource names. |
/management/client-passes/ |
/management/client-class-passes/ |
Update list and detail URLs and the enable and disable action URLs. |
/monitoring/health/anon_ping/ |
/monitoring/health/anon-ping/ |
Update anonymous health-check URLs if you use them. |
V2 has no /passes/ or /client-passes/ aliases.
The legacy routes under /api/v1/ have no direct V2 copies. They are removed on
November 30, 2026. Read the table to find the Management endpoint that replaces each one.
| Legacy V1 route | Replacement in V2 |
|---|---|
/bookings/ |
/management/bookings/ |
/members/ |
/management/clients/ |
/contracts/ |
/management/subscriptions/ |
/billing_plans/ |
/management/client-subscriptions/ |
/payment_packs/ |
/management/client-class-passes/ |
/consumer_payment_packs/ |
/management/client-class-passes/ |
Field names also change between the legacy and Management responses. Check the V2 reference for each resource and use the tables below for the renamed fields.
Service and teacher IDs¶
Class services¶
On classes, service_id changes from an internal scheduling record ID to a public service
ID, returned by /api/v2/management/services/. The service_id filter uses the new ID
too.
Reload services and classes for each company to rebuild your mappings. Reusing a V1 number
can select an unrelated V2 service. Legacy classes without a linked service return
service_id: null.
Teachers¶
Read the table by surface. Each row shows what the ID meant in V1, what it means in V2, and what to change.
| Surface | V1 | V2 | Required adjustment |
|---|---|---|---|
Class teacher_id and filter |
Effective teacher: the substitute when assigned, otherwise the main teacher. Already a company-specific teacher ID. | Main teacher. substitute_teacher_id is separate. |
Use current_teacher_id to filter the teacher actually running a class. For display, prefer the substitute when present. |
Appointment teacher_id |
Platform-wide coach ID. | Company-specific teacher ID returned by /teachers/. |
Rebuild cached teacher references. Do not reuse the V1 number. |
Booking teacher_id, substitute_teacher_id and teacher_ids filter |
Coach IDs. | Company-specific teacher IDs returned by /teachers/. |
Update joins and filters to the V2 Teachers ID space. |
Teacher IDs are company-specific: the same person can have different IDs in two companies. Allow null main-teacher IDs on legacy classes and bookings. IDs returned by the Teachers endpoint itself are unchanged.
Classes, appointments and bookings¶
Classes and appointments¶
Read the table by resource. Each row is one rename.
| Resource | V1 | V2 |
|---|---|---|
| Classes and appointments | is_cancelled |
is_canceled |
| Classes and appointments | cancellable_until |
cancelable_until |
| Appointments | cancelled_at |
canceled_at |
| Appointments, response and list filter | service_id |
appointment_service_id |
| Appointments, request body and response | slot_id |
appointment_slot_id |
| Classes | is_bookable_by_members |
is_bookable_by_clients |
| Classes | current_waiting_list_size |
current_waitlist_size |
| Classes | max_waiting_list_size |
max_waitlist_size |
On appointments, appointment_service_id keeps the old service_id value and
appointment_slot_id keeps the old slot_id value. Classes keep the name service_id,
with the different ID meaning described above. V2 also adds level_id and
substitute_teacher_id to classes.
Bookings¶
Read the table by surface. Each row is one change.
| Surface | V1 | V2 |
|---|---|---|
| Create payload, response and list filter | client_pass_id |
client_class_pass_id |
| Create payload | session_ids alias |
Use class_ids. It is required and non-empty. |
| Response | session_id, session_start_at aliases |
Use class_id, class_start_at. |
| Response | location_id |
venue_id |
| List filters | session_id, session_ids |
class_id, class_ids |
| List filters | session_started_after_date, session_started_before_date, taking a date |
class_start_after, class_start_before, taking a timestamp. A plain date is read as midnight in the company's time zone. |
| List filters | has_session_ended, is_workshop_session |
has_class_ended, is_workshop_class |
| List filter | location_ids |
venue_ids |
| List filter | meta_activity_ids |
service_ids |
| Ordering | class_start_date, session_start_date |
class_start_at |
The activity_ids filter is removed. To use service_ids, first resolve the public
services that correspond to your old activity IDs.
Rename expand[] paths as well, for example client_pass_id -> client_class_pass_id,
and parse expanded objects with their V2 field names.
Clients, notes, tags and teachers¶
Client creation¶
Read the table from left to right to convert a V1 creation body.
| V1 input | V2 input |
|---|---|
membership |
client_reference |
membership.membership_id |
client_reference.client_reference_number |
phone.phone_number |
Top-level phone_number string |
address.zipcode |
address.zip_code |
Creation requires client_reference.joined_date. Marketing-consent fields also belong
inside client_reference.
A V2 creation request looks like this (synthetic data):
{
"firstname": "Alex",
"lastname": "Example",
"client_reference": {
"joined_date": "2026-10-06",
"client_reference_number": "external-123",
"is_email_accepted": false
},
"phone_number": "+33612345678",
"address": {"zip_code": "75001"}
}
Client responses and nested notes¶
Read the table from left to right. Each row is one field.
| V1 | V2 |
|---|---|
membership_id |
client_reference_number |
default_billing_establishment_id |
default_billing_venue_id |
default_establishment_billing_location_id |
Removed. No like-for-like field is published. |
client_account |
Removed. client_account_balance is the client's balance. |
general_terms_and_conditions_accepted_date |
general_terms_and_conditions_accepted_at |
general_terms_of_use_accepted_date |
general_terms_of_use_accepted_at |
notes[].member |
notes[].client_id |
notes[].date |
notes[].creation_date |
notes[].editable |
notes[].is_editable |
notes[].highlighted |
notes[].is_highlighted |
Client ordering credit_account_balance |
client_account_balance |
Both date fields, birth_date and joined_date, use YYYY-MM-DD. The V2 schema declares
joined_date non-nullable: check legacy records when you update generated client types.
V2 adds PATCH /clients/{id}/. Omitted fields stay unchanged. An address object updates
only its supplied fields, and address: null leaves the address intact. Email can be set
at creation but is rejected in PATCH. V1 has no PUT operation, so there is nothing to
migrate.
Other field changes and validation¶
Read the table by resource.
| Resource | V1 | V2 |
|---|---|---|
| Client-note response and ordering | created_on |
creation_date |
| Tags and tag groups | date_created |
created_at |
| Teachers | first_name, last_name, phone |
firstname, lastname, phone_number |
| Tag creation | Optional, nullable free-form color |
Required lowercase #rrggbb string. Null is not accepted. |
Tag and untag actions and tag-group references require positive IDs from the authenticated company.
Class passes and client class passes¶
Class-pass response mapping¶
Read the table from left to right. Each row maps a V1 /passes/ field to its V2
/class-passes/ field.
| V1 field | V2 field |
|---|---|
meta_activity_ids |
service_ids |
establishment_ids |
venue_ids |
applies_for_payroll |
is_used_for_payroll |
grants_door_access |
is_door_access_granted |
no_show_penalty_active |
is_no_show_penalty_active |
pass_category_id |
class_pass_category_id |
should_allow_guest_pass |
is_guest_allowed |
tags_on_client_pass_creation |
on_purchase_tag_ids |
theoretical_margin_value |
estimated_revenue_amount |
linked_private_pass_id |
linked_appointment_pass_id |
validity_daterange |
validity_period |
bookkeeping_account_id |
Removed. No replacement in this response. |
Inside validity_period, rename lower to start_date, upper to end_date, and
empty to is_empty. Keep reading bounds to determine whether the end date is included.
The object can be null.
Rename the list filter category_id to class_pass_category_id. The rename
meta_activity_ids -> service_ids preserves the existing IDs: both refer to the same
record.
Client class passes¶
Read the table from left to right.
| V1 | V2 |
|---|---|
pass_id |
class_pass_id |
pass_template_instance_id |
class_pass_template_instance_id |
master_client_pass_id |
master_client_class_pass_id |
linked_private_client_pass_id |
linked_client_appointment_pass_id |
List filters pass_id, pass_ids |
class_pass_id, class_pass_ids |
List filter client_id |
Use client_ids, including for a single client. |
Enable and disable query parameter spread_to_shared_passes |
should_spread_to_shared_passes |
V2 declares purchased_date and is_disabled read-only. Use the enable and disable
actions to change state: PATCH is not supported. Rename expansions too:
expand[]=pass_id becomes expand[]=class_pass_id.
Subscriptions¶
V2 keeps /subscriptions/ and /client-subscriptions/ for reads. New subscription writes
and pause operations are not part of V2. Read the table by surface.
| Surface | V1 | V2 |
|---|---|---|
| Subscription response | contract_template_id |
subscription_template_id |
| Subscription response | contract_terms |
subscription_terms |
| Subscription response | contract_terms_pdf_link |
subscription_terms_pdf_link |
| Subscription response | tags_on_first_billing |
on_first_billing_tag_ids |
Both subscription resources, response and benefit_type filter |
pass |
class_pass |
Replace pass with class_pass in response parsing and in benefit_type filters.
appointment_pass and pack are unchanged.
New in V2¶
The V2 objects with no V1 equivalent need no migration. Read Other objects added in V2 for what each one is, and V2 workflows and field conventions for how to use them.
When you adopt invoices, check the amount units: every amount, nested line items included,
is a decimal string in major currency units. For communications, a retry can send duplicates
and processed does not mean delivered.
Webhooks and MCP¶
This guide covers the REST API. MCP and webhooks follow different rules.
MCP. MCP tools use the V2 contract, with V2 field names and IDs. You do not migrate anything on the MCP side, but the tool list is the V2 one. See the MCP overview.
Webhooks. The payload version belongs to the registration:
- An existing registration keeps delivering V1 payloads and historical event names such as
offer-update. Itsdatakeeps V1 field names and IDs, for example bookingsession_idand classservice_id. It does not change on its own. This includes registrations that were created through a V2 route before payload versioning was introduced. - A new registration is always created through
/api/v2/management/webhook-registrations/. Creating one through V1 returns410with codeWHR-002. It delivers V2 payloads with canonical event names such asclass.updated, and itsdatamatches the V2 response of the same resource, for example bookingclass_id.
To move an existing integration to V2 payloads, create a new registration for the V2 event, then disable the old one once your receiver handles the new shape. Use a separate receiver URL for each version while both exist, because a registration cannot be switched from V1 to V2. See Webhook event payloads and existing V1 registrations.
Migration checklist¶
Work through the steps in order.
- List the routes, fields, filters, ordering values and expansions your integration uses, including scheduled exports.
- Update them using the tables above. Regenerate your API client from the V2 schema if you use one, and check required fields, nullable IDs and each operation's error responses.
- Reload service and teacher mappings per company. Test classes with substitutes and legacy records with null references.
- Compare reads on test data. Check filter results: an obsolete parameter can leave a query broader than intended.
- Test writes outside production, including invalid IDs, tag colors, omitted fields and explicit nulls. V1 and V2 share data, so sending the same write to both can create duplicates.
- Plan webhooks separately: existing registrations keep V1 payloads, and new ones use V2. Create the V2 registration next to the V1 one, compare the deliveries, then disable the V1 registration.
- Switch one flow at a time. Keep the V1 mappings available for rollback. Returning to V1 does not undo writes already made through V2.