Scheduling integrations
For scheduling and workforce partners: the scopes to ask for, reading the schedule, writing substitutions, and syncing the schedule and staff roster without missing a change.
Start with Business API keys for how the brand issues your key, how to present it, idempotency and rate-limit headers. This guide covers what a scheduling integration does with it.
Scopes for a scheduling or workforce partner
| Scope | Grants |
|---|---|
sites:read | Locations, timezones and opening hours |
staff:read | The roster, with each instructor's sites, home site, working hours and the class types they may teach |
sessions:read | Class types and the schedule, with who is teaching and who is subbing |
sessions:write | Substituting, assigning and removing instructors |
Webhook subscriptions cannot be created with a key; the brand sets them up for you — see Webhooks.
Reading the schedule
GET /public-api/v2/sessions?startDate=…&endDate=…lists every site the key can see. Narrow withsiteId, repeated or comma-separated for several.- Each session's
instructorscarries arole(INSTRUCTOR,ASSISTANT,SUBSTITUTE) and, on a substitute,substitutingFor— the instructor whose class it is. - Pages are cursor-based: follow
nextCursoruntilhasMoreis false. A page can be short at a site boundary; it is not the end unlesshasMoresays so.
A staff member's qualifiedSessionTypeIds is always the exact list of class types they may teach, taken from the “Available with” instructors on the site's schedule templates. A class type is listed when it has no templates, when any of its templates names nobody (anyone may teach it), or when any of them names this person.
Writing a substitution
POST /public-api/v2/sessions/{id}/substitute-instructor
Idempotency-Key: 3b4f…
{
"siteId": "…",
"instructorToRemoveId": "…",
"substituteInstructorId": "…",
"notifyMembers": true,
"notifyInstructors": false
}By default booked members are emailed and neither instructor is. Set notifyMembers: false if you have already told the class, and notifyInstructors: true to email both instructors. The response is the class as it now stands. A recurring occurrence that was not yet materialised comes back with a new id — use that one from then on.
Send an Idempotency-Key on every write you might retry.
Staying in sync
Pass updatedSince (an ISO-8601 instant) to GET /sessions and GET /staff to receive only what changed: a session whose class or instructors changed, a staff member whose profile, sites or teachable class types changed. Store the time you started each pull and send it as the next pull's updatedSince.
Keep polling even with webhooks on. When staff edit a recurring class "for this and all future" occurrences, occurrences far enough ahead that Hapana has not created them yet send no event of their own. They appear in your next updatedSince pull.
Rate limits are per brand and shared by every key it issues (see Rate limits).
Recommended cadence for a five-site brand on Basic: a full four-week pull nightly, an updatedSince pull every 5 minutes (one or two requests when little has changed), and webhooks for anything that must land within the minute. A full pull of 500 classes a week at 200 per page is about ten requests per four weeks of schedule, well inside the budget.
Webhooks
Subscribe to session.instructor_changed to hear about every change to who is teaching — from your own write-back (source: "PARTNER_API"), from staff in Hapana (STAFF), or from Hapana's class-cover workflow (NATIVE_COVER). session.updated carries changedFields, so a class that moved in time arrives as a startTime change.
The payload, the source values and how to verify each delivery's signature are in Webhook events. Delivery is at-least-once, so de-duplicate on the event id.
