Booking & Scheduling
UPC providers (groomers, trainers, veterinarians, sitters, kennels, transporters and more) manage availability and appointments on the UPC Scheduling engine, one scheduler for every type of service. Partners and providers can surface booking on their own websites three ways, all backed by the same Scheduling engine — pick the integration depth that fits:
| Tier | Effort | For |
|---|---|---|
| Hosted booking page | none — share a link | Providers who just want a “Book now” button |
| Embed widget (JavaScript) | one script tag | Provider websites that want booking inline without building UI |
| Partner API | full integration, free; the business keeps a payment card on file and connects your app | Custom solutions, native apps, CRMs, marketplaces, WordPress plugins, AI agents |
Hosted booking page
Section titled “Hosted booking page”Every provider gets a hosted booking page on UPC. Link to it from any website:
<a href="https://www.unitedpetclub.com/book/{providerHandle}">Book an appointment</a>Embed widget
Section titled “Embed widget”(The widget script is not available yet; link to the hosted booking page in the meantime.) The script is a smart loader: it reads your configuration, renders the entry point natively in your page (an inline panel, a “Book now” button that opens a modal, or a floating badge), and opens the booking flow itself in an isolated iframe themed to your site:
<script async src="https://cdn.unitedpetclub.com/embed/booking.js" data-provider="{providerHandle}" data-mode="button" data-service="{serviceId}" data-profile="spa-site" data-scheme="dark" data-locale="en-US"></script>Or programmatically:
UPCBooking.init({ provider: "{providerHandle}", mode: "inline", // inline | button | badge target: "#book", profile: "spa-site", // named embed profile from the provider's Dashboard scheme: "dark", // match this page's color scheme onCompleted: (booking) => analytics.track("booking", booking)});Brand theming is configured in the provider’s UPC Dashboard, not in the embed. Colors, font, logo, and corner radius live in Dashboard embed profiles — the flow fetches them server-side, so nothing brand-visual is controllable from the page (a deliberate security boundary: script injection on an embedding site cannot restyle the booking flow). A provider with multiple differently-branded websites defines one profile per site and each embed selects its profile by name. The embed itself passes only page-context hints: display mode, target, locale, preselected service/staff, and light/dark scheme.
Embedding is allowlisted. The provider registers the domains permitted to embed
their booking flow in the Dashboard; other origins are refused at the frame level
(frame-ancestors) and by the postMessage bridge. The iframe auto-resizes and emits
lifecycle events (booking:completed) via that origin-checked bridge, so you can track
conversions.
Custom CSS or scripts inside the flow are deliberately not supported: the iframe guarantees the transactional core (conflict handling, localization fallback, guest checkout, deposit payment isolation) is always UPC’s current audited code, and new features reach your site without redeploys. If you need full visual control, use the Partner API below and build your own UI.
Booked customers who don’t have a UPC account are recorded as the provider’s guest clients and can claim their history when they sign up.
Partner API
Section titled “Partner API”The Partner API lets your app act for a business that connected it — a salon’s own booking app, a CRM that keeps its calendar in sync, a marketplace that books on its behalf. It lives on the API host, not on the United Pet Club apps:
https://api.unitedpetclub.com/api/scheduling/v1The Partner API is free, for your app and for the business. Two things must be true before your app can call it:
- The business keeps a valid payment card on file (Dashboard → Finances → Billing), so that United
Pet Club can charge it for the bookings and services its apps create. Without one every call answers
403withplatformResponseCode0010010000407, and the business cannot approve your app on the consent page. A card that expires, is declined or needs re-verification counts as none. - The business connected your app. You send the business to the United Pet Club consent page; it approves the scopes you ask for and you get an authorization code, which you exchange for an access token and a refresh token. That token acts for that one business — there is no business id in the paths. See Authentication → Connecting a business.
Software that installs itself on many sites or runs as an AI agent can register itself and connect without a developer account: see WordPress plugins and AI agents.
An app’s own client_credentials token acts for no business, so the partner routes refuse it (403,
0010010000406). The business can disconnect your app at any time (Dashboard → Settings → Connected
Apps); its tokens stop working within seconds (401, 0010010000402).
| Scope | What it allows |
|---|---|
scheduling:read | The business, its services, open slots, booking options and price estimates |
appointment:read | The business’s appointments |
appointment:write | Book, reschedule and cancel appointments |
client:read | The business’s clients and their pets; client e-mail and phone on appointments |
webhook:manage | Appointment webhooks — see Webhooks |
Ask only for the scopes you need: the business sees each one on the consent page.
The business and its services
Section titled “The business and its services”GET /api/scheduling/v1/businessGET /api/scheduling/v1/services?locale=fr-CA/business answers businessProfileId, timezoneIanaName and isAcceptingBookings. /services lists
the services clients can book: serviceId, catalogItemPublicCode, name and description in the
requested locale, durationMinutes, deliveryModeCodes (AT_LOCATION, MOBILE, VIRTUAL) and
allowSpecificStaff.
Search availability
Section titled “Search availability”GET /api/scheduling/v1/services/{serviceId}/slots?from=2026-10-05&to=2026-10-11from and to are dates (yyyy-MM-dd) at most 62 days apart. The answer lists the open start times
(data[].startUtc / endUtc) with the staff free at each start (resources[]). Slots are computed live
from the business’s hours, lead time, booking horizon, existing appointments and buffers — never cache
them for more than a minute; the slot you show may be taken by the time you book it, and that is
handled below.
Days per type of service (2026-10-09). A business can offer a type of service only on some days and
times, for example grooming Monday to Wednesday and training at weekends. Slots honour that: a start
is offered only when a qualified team member is working and it falls inside the windows the business
set for that service’s type. Two services of the same business can therefore return different days for
the same range; search each service separately, and do not reuse one service’s slots for another. A
booking or reschedule at a start outside those windows answers 409 Scheduling_SchedulingPartner_AD_Slot_RJ
like any other start that is not an open slot.
Public holidays (2026-10-09). A business sets how it opens on each public holiday of its region:
closed, usual hours, or special hours. Slots honour that too: a holiday it closes for returns no
slots, and a holiday with special hours returns only the starts whose whole visit fits inside them.
A booking or reschedule on such a start answers 409 Scheduling_SchedulingPartner_AD_Slot_RJ. Nothing
changes on a holiday kept on usual hours, and the business’s regular weekly opening hours do not narrow
slots (its team’s working hours do). To show the holidays themselves — “Closed on 25 December” — read the
business profile’s opening calendar.
Mobile visits. For a service delivered MOBILE, pass the client’s location so only times when a
groomer and a van covering that address are free come back:
GET /api/scheduling/v1/services/{serviceId}/slots?from=…&to=…&latitude=49.28&longitude=-123.12&postalCode=V6B%201A1Send latitude/longitude when you have them (radius areas) and postalCode always (zone areas). The
answer gains vehicleBound (the van check ran), each slot lists the vehicles[] free at that start, and
outsideServiceArea: true with an empty list means the business runs vans but none serves this
address — show “not in this business’s area”, not “fully booked”.
Estimate first (grooming)
Section titled “Estimate first (grooming)”Grooming prices depend on the pet — size, coat, matting, how it handles — and on when the visit happens (weekends, evenings, public holidays). Read what the business asks and offers, then ask for an estimate before you book:
GET /api/scheduling/v1/booking-options?serviceId={serviceId}&locale=fr-CAPOST /api/scheduling/v1/estimate/booking-options returns the business’s booking questions (intakeQuestions[], with localized prompts
and the answer codes you send back), the named styles it offers per region, its priced extras, and the
agreements a client must accept. Do not hard-code prompts or answer labels.
The grooming questions belong to grooming services only (since 2026-10-09). When the business has not classed the service as grooming (a training session, a walk, a vet visit), a booking ignores the grooming answers on its pets (coat, clip and style choices, extras) and raises no clip or corded-coat advisory. Agreements still apply to every service.
{ "serviceIds": ["…full groom…", "…nails…"], "startUtc": "2026-10-03T14:00:00Z", "pets": [ { "name": "Biscuit", "speciesCode": "DOG", "breedTaxonomyCode": "…", "weightKg": 9, "coatConditionCode": "LIGHT_MATTING", "behaviorCode": "NERVOUS", "clipSpecs": [ { "regionCode": "BODY", "lengthMm": 3 }, { "regionCode": "HEAD", "styleCode": "TEDDY_BEAR" } ], "extras": [ { "kindCode": "MEDICATED_BATH", "note": "vet-prescribed chlorhexidine" } ] } ]}Your app describes the pets the client tells you about: species, breed (breedTaxonomyCode) or
isMixed, weightKg or sizeClassCode, the coat fields and today’s answers. At most 10 pets. The answer
carries one line per service × pet (totalMinorUnits is null until the business has a rate card), the
deposit that would apply, the questions the business asks, and advisories.
Prices are estimates. Present every grooming price as an estimate: the groomer confirms it once they have seen the pet (coat condition, size, temperament). The hosted booking page labels its summary Estimated price; your UI should do the same.
Clip advisories. Asking for a short body clip (at or below the business’s threshold, 10 mm by
default) on a double-coated, wire-coated or unknown coat raises an advisory (DO_NOT_SHAVE,
HAND_STRIP_ONLY, COAT_UNKNOWN) with the text to show and a textHash. Depending on the business’s
setting the booking then needs the client’s acknowledgement (409 Scheduling_Booking_VL_ClipAdvisory_RJ
without it) or is refused outright (409 Scheduling_Booking_VL_ClipRefused_RJ).
Corded coats. coatConditionCode accepts CORDED — a coat kept in cords (Puli, Komondor,
Bergamasco, corded Poodle). It is not matting, but it takes hours to dry, so a corded coat raises a
CORDED_COAT advisory. Depending on the business’s setting the booking then needs the acknowledgement
(409 Scheduling_Booking_VL_CordedCoat_RJ without it) or is refused
(409 Scheduling_Booking_VL_CordedRefused_RJ).
POST /api/scheduling/v1/appointmentsIdempotency-Key: 5f1c0a4e-booking-7781{ "serviceId": "…", "startUtc": "2026-10-03T14:00:00Z", "client": { "givenName": "Jane", "familyName": "Doe", "email": "jane@example.com", "phone": "+16045550100", "languageCode": "en" }, "pets": [ { "name": "Biscuit", "speciesCode": "DOG", "weightKg": 9, "coatConditionCode": "LIGHT_MATTING" } ], "notes": "Side gate is open.", "address": { "formatted": "123 Main St, Vancouver BC", "latitude": 49.28, "longitude": -123.12, "postalCode": "V6B 1A1" }, "references": [ { "url": "https://photos.example.com/biscuit-summer", "label": "last summer" } ], "acknowledgements": [ { "advisoryCode": "DO_NOT_SHAVE", "textHash": "…" } ], "agreementAcceptances": [ { "agreementId": "…", "bodyHash": "…", "signerName": "Jane Doe" } ]}- The start must be an open slot of that service. If it is not (anymore), the booking answers
409 Scheduling_SchedulingPartner_AD_Slot_RJ: search the slots again and offer the client another time — do not retry the same instant. A race the slot check cannot see answers409 Scheduling_Booking_AD_Conflict_RJ(slot clash) or409 Scheduling_Booking_AD_Capacity_RJ(no qualified staff free); handle them the same way. A start on a holiday the business closes for (or outside its holiday hours) is not an open slot either. On the hosted booking page and the embed widget the same refusal is409 Scheduling_Booking_VL_ProviderClosed_RJ(“Closed at that time”), which the booking flow shows to the client itself. - The client needs a name and an e-mail address or a phone number. It becomes (or is found again, by e-mail or phone) one of the business’s own client records.
addressis for a service that comes to the client (MOBILE); it is snapshotted onto the appointment and feeds distance-based pricing. Outside every van’s area:409 Scheduling_Booking_VL_ServiceArea_RJ; no van free:409 Scheduling_Booking_VL_Vehicle_RJ.referencesare up to five links (https) to photos of the look the client wants.acknowledgementsecho every advisory the estimate raised, with thetextHashof the text you showed.agreementAcceptancesaccept every required agreement from/booking-options, with thebodyHashyou were given and a typed name whererequiresTypedNameis true;409 Scheduling_Booking_VL_Agreement_RJmeans one is missing or the business changed its text — reload the booking options and show the new version.Idempotency-Key(up to 100 characters, recommended): repeating a request with the same key and the same body answers the booking it made, with200andIdempotent-Replayed: trueinstead of201. The same key with a different body is409 Scheduling_SchedulingPartner_AD_Idempotency_RJ. Keys are per app.
A new booking answers 201 Created with the appointment and a Location header. What state it lands in
is the business’s policy: CONFIRMED right away, REQUESTED / PENDING_PROVIDER when the business
reviews requests, or HOLD when a deposit is due — the business turns the hold into a payment request
the client pays through United Pet Club, and an unpaid hold lapses at holdExpiresAtUtc. Your app never
takes a payment. The business must be verified for online bookings: until it is, bookings answer
403 Scheduling_Booking_AD_VerificationRequired_RJ (see
Verification before public bookings). It must also
have finished its booking setup: until it has, new bookings answer
409 Scheduling_Provider_AD_NotReady_RJ (see
A business that is not ready for bookings).
Read appointments
Section titled “Read appointments”GET /api/scheduling/v1/appointments?from=2026-10-01T00:00:00Z&to=2026-10-31T00:00:00Z&state=CONFIRMED&limit=200GET /api/scheduling/v1/appointments/{appointmentId}from and to (ISO 8601) are required and at most 62 days apart; state is optional; limit is 1–500
(default 200). The list covers every appointment of the business in that window, not only the ones
your app booked — bookedByThisApp tells them apart. Each appointment carries stateCode, startUtc,
endUtc, timezoneIanaName, serviceId, deliveryModeCode, bookingOriginCode, the client
(kind GUEST or MEMBER, displayName; email and phone only when your token has client:read),
pets[], note, addressFormatted, the quote (quotedAmountMinorUnits, quotedCurrencyCode), the
deposit fields, settlementStatusCode and, once cancelled, cancellationReasonCode / cancelledAtUtc.
To stay in sync without polling, subscribe to webhooks.
Reschedule and cancel
Section titled “Reschedule and cancel”POST /api/scheduling/v1/appointments/{appointmentId}/reschedule { "startUtc": "2026-10-04T15:00:00Z" }POST /api/scheduling/v1/appointments/{appointmentId}/cancel { "reason": "CUSTOMER_REQUEST" }A new start must be an open slot of the appointment’s service, as for a booking
(409 Scheduling_SchedulingPartner_AD_Slot_RJ otherwise). reason is CUSTOMER_REQUEST (the default),
PROVIDER_REQUEST, WEATHER, EMERGENCY, ILLNESS or OTHER; a late customer cancellation may assess
a fee under the business’s policy — the call still answers 200 with the cancelled appointment, and the
fee arrives as a separate charge. A move or cancel the appointment’s state does not
allow is 409 Scheduling_Booking_UP_State_RJ.
Clients and pets
Section titled “Clients and pets”GET /api/scheduling/v1/clients?search=doe&page=1&pageSize=25GET /api/scheduling/v1/clients/{clientId}GET /api/scheduling/v1/clients/{clientId}/petsNeeds client:read. The list (pageSize 1–100; search matches a name, client number, contact, phone digits or pet name) returns the
business’s clients with their pets in brief, visit counts and lastVisitAtUtc / nextVisitAtUtc, plus
pagination. A client’s detail adds contacts[], addresses[] and full pets[] (handling notes and
alerts included). Respect optedOutAtUtc on a contact: never message a channel the client opted out
of. Merged and erased client records answer 404.
A full pet carries color (free text) and, since 2026-10-02, appearance when its colours are recorded as codes:
{ "areas": [ { "areaCode": "COAT", "areaName": "Coat", "colors": [ { "code": "BLACK", "name": "Black", "hexCode": "#1C1C1C", "accentHexCode": null } ], "patterns": [ { "code": "TAN_POINT", "name": "Tan point" } ], "patternSlots": [] } ], "markings": [ { "markingCode": "BLAZE", "name": "Blaze", "colorCode": "WHITE", "colorName": "White", "note": null } ], "summary": "Black · Tan point; Blaze (White)", "source": "PICKED", "unmatchedText": null }.
Names follow the request’s Accept-Language. source says where the codes came from: ANIMAL (the owner’s pet
profile), PICKED (chosen by the business) or PARSED (read from the typed color; unmatchedText holds the words no
code covers). appearance is null when no colour is recorded; color is always kept. The hex values above are
illustrative.
Errors on the partner routes
Section titled “Errors on the partner routes”| Status | platformResponseCode | Cause | What to do |
|---|---|---|---|
401 | 0010010000402 | The business disconnected your app, or your app was suspended. | Stop; ask the business to connect the app again. |
403 | 0010010000406 | A client_credentials token on a route that acts for a business. | Use a token from the authorization-code grant. |
403 | 0010010000407 | The business has no valid payment card on file (any more). | Tell the business; it adds a card in the Dashboard (Finances → Billing). Calls work again within about 10 seconds. |
503 | 0010010000503 | The business’s payment card or its connection could not be checked right now. | Retry after Retry-After seconds. |
400 / 404 / 409 / 503 | Scheduling_… or Clientele_… | A refusal of the booking or client service, relayed with its status and code — for example Scheduling_SchedulingPartner_AD_Slot_RJ or Scheduling_Booking_VL_Agreement_RJ. | See the sections above. |
409 | Scheduling_Provider_AD_NotReady_RJ | The business has not finished its booking setup; no new bookings for now. | Do not retry. Tell the client the business can’t take online bookings yet; see below. |
503 | Scheduling_Provider_HD_Readiness_FL | The business’s booking setup could not be checked right now. Nothing was booked. | Retry with backoff. |
Everything else follows Errors. Rate limits count per app and per connected business (see Rate limits).
Text reminders on a booking
Section titled “Text reminders on a booking”An appointment gets three business-initiated text messages (confirmation, 24-hour reminder, “on my way” / “ready for pick-up”), reset when the appointment is rescheduled; e-mail and push notifications are separate and unlimited. Since 2026-09-30 the three texts are included only when the booking is paid online through United Pet Club (a deposit is enough), or the business is on Pro or a Partner in good standing; otherwise every text uses one of the business’s text credits (WhatsApp and SMS reminders). The business sends the staff-initiated text from its Dashboard; when no included text applies and it holds no text credits the send is refused and the appointment timeline records the non-send. Nothing about the software is metered per seat, location or client — see Fees on bookings.
Mobile coverage for directories and maps
Section titled “Mobile coverage for directories and maps”Showing businesses that come to the client? The coverage reads are anonymous platform app endpoints
(https://www.unitedpetclub.com/api/scheduling/v1/…), not Partner API routes:
GET /api/scheduling/v1/mobile-coverage?profileId={id} → mobileServiceAvailable,
inStoreServiceAvailable, baseAddressHidden and areas[] (RADIUS with
centerLat/centerLng/radiusKm, or ZONES with postal prefixes). When the business hides its base
address the centre is approximate (centerPrecisionCode: APPROXIMATE) — for “near me” use
GET /mobile-coverage/contains?profileId=…&lat=…&lng=…&postalCode=… and let the platform answer
exactly. A location lists once; there is no listing per van.
Which should I use?
Section titled “Which should I use?”Start with the hosted page or widget — they are always up to date with new features. Move to the Partner API only when you need custom UI or server-side flows for a business that keeps a payment card on file and has connected your app. All three tiers share the same engine and localization, so you can mix them (e.g. widget on the marketing site, Partner API in the business’s own app).
Fees on bookings
Section titled “Fees on bookings”The booking software is free to providers. Since 2026-10-09 every seller surface names the fees a provider pays with three names, and so does this page:
| Fee | What it is | Rate |
|---|---|---|
| Commission | United Pet Club’s percentage on a sale, charged once on the booking total (a deposit followed by a balance pays one Commission) | Service bookings 1.25 %, minimum 0.50. Classes 7 % in person, 17 % online, minimum 0.50 |
| Platform Processing Fee | United Pet Club’s published card-processing rate, on card payments | Today it equals the card network list price (2.9 % + 0.30 online, 2.7 % + 0.05 in person) |
| Stripe Payout Fees | Stripe’s costs for paying the provider out, passed on at cost with no margin | 0.25 % + 0.25 per payout, plus 2.00 in each calendar month the provider gets at least one payout |
- Classes. A booking is priced as a class when the service is a group class (more than one place
per slot), or when the client buys a class enrollment. It is an online class when the
appointment’s delivery mode is
VIRTUAL(or, with no delivery mode, when the service has a video mode); otherwise it is in person. - The Pro plan’s 0 % Commission covers service bookings only, never classes. A class booked with a Pro business still carries the class Commission.
- The Platform Processing Fee is United Pet Club’s own versioned schedule, per country and currency; a new version is announced at least 30 days before it takes effect. Tips carry the Platform Processing Fee only.
- Stripe Payout Fees are priced per country (in CAD for Canada, USD for the United States). They are taken from the provider’s next transfer and listed on its fee statement; Canadian providers also pay GST/HST on them, others no tax. They concern the provider’s payouts only: a booking and its price never change because of them.
Nothing in the booking, availability or embed APIs changes because of fees, and the Partner API
routes carry no fee rates. What a partner integration sees is the fee snapshot on a
booking’s earnings line: platformFeeMinor (the Commission), processingFeeMinor (the Platform
Processing Fee), commissionRateId (the schedule row that priced the booking) and
processingRateVersionId (the processing-rate version in force when it was paid). Treat both ids as
opaque; they let a provider reconcile a payout statement against the fee schedule that applied on the
day, and a later schedule change never re-prices a booking that already carries them.
Reference prices
Section titled “Reference prices”The Platform Agreement (section 13.5) asks the business to record the true price of every booking, including bookings your app creates or reports. Each service has a reference price for the business’s country (and province or state): when the price recorded is lower, the Commission is worked out on the reference price. This changes nothing for an integration:
- Bookings and their prices are never refused, changed or re-quoted. A partner booking is priced from the provider’s own price list, as before.
- No API carries a reference price, and none is published. The provider sees it only in its own Dashboard, when it sets the fee.
- A booking pays one Commission however it is paid: deposit by card and balance in cash, or several outside payments, are priced together on the booking’s total.
- A completed booking that is never paid through United Pet Club or marked paid outside is charged the Commission on its quoted price 14 days after completion. If your integration takes payment in its own checkout, have the provider record it in United Pet Club so the booking is settled.
Bookings below their reference price can be reviewed; reporting a lower price than the one charged breaks section 13.6.
Published fee schedules
Section titled “Published fee schedules”The schedules themselves are public. Two anonymous reads on the gateway (no bearer) return them:
GET /api/commission/v1/public/fee-sheet?saleKind=SERVICE_BOOKING&country=CAGET /api/commission/v1/payout-fee-rates/current?country=CApublic/fee-sheet is the schedule of fees for one sale kind in one country (Commission, Platform
Processing Fee and usage rates, current and upcoming); it is cached for an hour. Since 2026-10-09 it
also carries a payoutFees object: the Stripe Payout Fees of that market, the same document
payout-fee-rates/current returns on its own (cached for ten minutes):
{ "data": { "id": "d0000000-…", "countryCode": "CA", "currencyCode": "CAD", "perPayoutBps": 25, "perPayoutFixedMinor": 25, "monthlyActiveAccountFeeMinor": 200, "effectiveFromUtc": "2026-10-09T00:00:00+00:00", "level": "COUNTRY", "effectiveToUtc": null }, "resultCode": "Commission_PayoutFeeRate_GT_Resource_SU"}| Field | Meaning |
|---|---|
perPayoutBps | The percentage of every payout, in basis points (25 = 0.25 %); rounded half up to the minor unit |
perPayoutFixedMinor | The fixed part of every payout, in minor units of currencyCode |
monthlyActiveAccountFeeMinor | Charged once in each calendar month the provider gets at least one payout |
level | COUNTRY (the market’s own row) or PLATFORM (the currency’s row for every other country) |
effectiveFromUtc / effectiveToUtc | The window the row is in force; effectiveToUtc is null while open-ended |
country is ISO 3166-1 alpha-2 and defaults to CA; currency defaults to the country’s settlement
currency. An invalid country answers 400 Commission_Resolver_VL_Country_RJ; a market with no
published Stripe Payout Fees answers 404 Commission_PayoutFeeRate_GT_Resource_FL. Show these as the
provider’s costs, never as something the client pays.
Bookings never go through an app store. A booking is a service delivered in person, which
Apple’s and Google’s billing rules exclude from in-app purchase, so a booking made from the native
app is paid through Stripe exactly as on the web — no store commission, no marked-up price, and the
same two ids on its earnings line. (Digital items and subscriptions bought inside the app do carry
the store’s commission as a further schedule row, added on top of the platform fee and never taken
from the professional’s share; that is a separate earnings line kind, STORE_RECEIPT, and is not a
booking.)
Payments on a booking
Section titled “Payments on a booking”A booking asks the client for money as payment requests — a deposit when the provider’s deposit
policy applies, the balance (plus any tip) when the appointment is completed, a no-show fee when one
is assessed. The appointment read (GET v1/appointments/{id}) carries them under payment:
| Field | Meaning |
|---|---|
settlementStatusCode | NONE, DEPOSIT_DUE, DEPOSIT_PAID, BALANCE_DUE, SETTLED (every request paid through the platform) or SETTLED_OUTSIDE (the provider recorded a cash / transfer payment). Independent of the lifecycle state. |
outstandingMinorUnits | The sum of requests still open, tax included. |
payments[] | One row per request: kindCode DEPOSIT / BALANCE / NO_SHOW_FEE (or OUTSIDE for a recorded outside payment), statusCode REQUESTED / PAID / CANCELLED, amountMinorUnits, currencyCode, tipMinorUnits, and — once the platform has prepared the payment — orderId, orderPaymentId, taxMinorUnits (the tax the platform computed for this request, on top of the amount; null when there is no tax basis) and totalMinorUnits (amountMinorUnits + taxMinorUnits — what the checkout must charge). |
A client pays a request by card through the standard checkout for totalMinorUnits, naming the
request’s orderId and tagging the intent with upcOrderPaymentId; a hosted booking page or embed
does this for you. Tax is computed by the platform on the quoted service total for the provider’s
location and allocated across the deposit and the balance (never on a tip); the provider collects
and remits it. Quoted amounts are always before tax, in every market: in VAT markets (EU, GB,
CH, AU) the VAT is added on top too, so taxMinorUnits is never zero there just because VAT is shown
tax-inclusive on product pages.
The provider (owner or staff — never the client) can instead record that a booking was paid
directly: POST v1/appointments/{id}/settle-outside { amountMinorUnits, currency?, method } with
method CASH / ETRANSFER / OTHER, allowed while the appointment is confirmed, in progress,
completed or a no-show and not yet settled. The Commission on the amount recorded is netted from
the provider’s next payout; a low amount is never refused (the minimum fee applies). Refusals are
governed codes: Scheduling_Settlement_UP_State_RJ (409), _VL_Owner_RJ (403),
_VL_Request_RJ (400), and Payment_CardOnFile_AD_Required_RJ (409) when the provider’s account
has neither a verified card on file nor a positive balance covering the fee on the amount — the
provider adds a card under Finances → Billing in the Dashboard (a $0 verification; nothing is
charged) and the call can be repeated. The same code is returned when a service’s video mode is
switched to PLATFORM.
A session redeemed against a package or pass the client already bought is booked with
paidByPackageId on the booking request; it raises no payment request, because the fee was paid
once on the purchase.
Providers read their own fee sheet in the Dashboard under Finances → Fees; a hosted booking page or embed never shows fees to the client, because the client pays the listed price and the provider pays the fees out of it.
Verification before public bookings (2026-09-20)
Section titled “Verification before public bookings (2026-09-20)”A business takes online bookings — the hosted page, the embed widget, Reserve with Google and
POST scheduling/v1/public/bookings — only once it is public-ready: a verified business address and
a connected payout account whose identity Stripe has verified. There is nothing to configure on the
partner side; the platform reads the provider’s verification gates on every public booking. Until the
business is verified (and from the platform’s enforcement date), public bookings answer
Scheduling_Booking_AD_VerificationRequired_RJ (403, detail “Verification required: …” with the reason
codes ADDRESS_UNVERIFIED, IDENTITY_UNVERIFIED / IDENTITY_PENDING / IDENTITY_FAILED,
PAYOUT_NOT_CONNECTED). Show the visitor the message and do not retry; the provider clears the steps from
their Dashboard checklist. If the gate itself cannot be read the booking fails closed with
Scheduling_Booking_HD_Verification_FL (503) — retry with backoff. Bookings the business records itself
through the manage endpoints are never gated, and neither is availability search. Text reminders on a
booking additionally need the provider’s payout account connected (see
Text reminders on a booking); a reminder the platform could not send shows
on the timeline as TEXT_NOT_SENT_UNVERIFIED.
A business that is not ready for bookings (2026-10-09)
Section titled “A business that is not ready for bookings (2026-10-09)”A business takes new bookings only once it has finished its booking setup in its Dashboard: payouts connected, the United Pet Club service fees accepted, and a payment card on file. The platform announces the date from which this is required; before it, nothing is refused. From it, for a business that is not ready:
| Call | Answer |
|---|---|
POST /api/scheduling/v1/appointments (book), and the hosted page, the embed and POST scheduling/v1/public/bookings | 409 Scheduling_Provider_AD_NotReady_RJ |
Slot search, /booking-options, /estimate, reading appointments | Answered as usual |
| Reschedule, cancel, and every change to an appointment that is already booked | Answered as usual: booked appointments are never blocked |
The 409 body carries data.reasons[], the steps the business still has to finish:
PAYOUT_NOT_CONNECTED, FEE_TERMS_NOT_ACCEPTED, NO_CARD_ON_FILE (and, rarely, FEE_SHEET_UNAVAILABLE
or FEE_TERMS_UNPUBLISHED when the fees for its country could not be read or are not published). They
are for your logs and for the business; do not show them to the client. Show the client that the
business can’t take online bookings right now, and do not retry: the business clears the steps from
its Dashboard, and bookings work again from then on, with nothing to change on your side.
If the setup cannot be checked at that moment, the booking fails closed with
503 Scheduling_Provider_HD_Readiness_FL and nothing is booked; retry with backoff.
This is separate from verification (a 403) and from
the payment card your app connection needs (0010010000407): without a
valid card on file, your app’s calls are refused before they reach booking.
Virtual sessions (2026-09-20)
Section titled “Virtual sessions (2026-09-20)”A service whose delivery modes include VIRTUAL carries a videoModeCode on the service read:
PLATFORM (United Pet Club creates a private video room per booking — one per class slot — that
opens 10 minutes before the start and closes 30 minutes after the end) or EXTERNAL_LINK (the
provider’s own meeting URL). Book it exactly like any other service with deliveryModeCode: "VIRTUAL".
- The appointment detail carries a
videoobject:videoModeCode,roomStatusCode(SCHEDULED | OPEN | LIVE | ENDED | …),opensAtUtc/closesAtUtc,liveParticipantCount, the platform join page URL (joinUrl) and, once the room closed, the metered actuals (billableParticipantMinutes,usageAmountMinorUnits). - Never embed a token in a link. The join page mints a short-lived grant on click:
POST scheduling/v1/appointments/{id}/video/joinfor a signed-in participant (provider = host, staff = presenter, customer = attendee),POST scheduling/v1/public/video/join { appointmentId, token }for a guest, wheretokenis the signed value in the guest’sjoinUrl(?t=). The answer isEMBEDDED(token+roomUrl: openroomUrl?t=tokenin an iframe with camera / microphone allowed) orLINK_OUT(joinUrl). Refusals:403 Scheduling_SchedulingVideo_VL_Participant_RJ,409 …_AD_Join_RJ(outside the open window),503 …_AD_Room_FL(retry). - Cost: platform video is metered at the published
VIDEO_PARTICIPANT_MINUTEusage rate (GET commission/v1/usage-rates/current?kind=VIDEO_PARTICIPANT_MINUTE&country=CA¤cy=CAD, $0.01 today) per participant-minute, staff included, and deducted from the provider’s payout monthly. That read needs a bearer (any signed-in profile; without one the gateway answers401); for the same rate with no token, read the anonymous published fee sheet, which carries the usage schedule, current and upcoming.GET scheduling/v1/services/{id}/video-estimate?participants=2&minutes=60returns the estimate for one session. The booking fee is unchanged.EXTERNAL_LINKsessions cost nothing.