Brands and locations
A business with several places is modelled as one profile per physical location, grouped by a brand profile. A location belongs to at most one brand and may carry (stock or service) any number of others. These reads let you render that structure: a “Part of {brand}” chip on a location, a locations block on a brand page, a “Where to find us” list for a product brand.
All reads on this page are anonymous and return the platform envelope
{ data, resultCode }. Profile routes are locale-scoped — the {locale} segment is a BCP-47
code such as en-US (see Localized names); ids are UUIDs.
Profile reads
Section titled “Profile reads”Base path through the gateway: /api/profile/v1/{locale}/Profile/profiles/{profileId}.
| Method | Path | Returns |
|---|---|---|
GET | …/brand | The brand {profileId} belongs to — { profileId, handleText, displayName, locationCount } — or 404 when it has none. |
GET | …/locations | For a brand: { rollup: { locationCount, weightedRatingAverage, totalReviewCount }, locations: [ … ] }. |
GET | …/carriers | For a brand: the profiles that stock or service it, as location rows; affiliationKindCode is CARRIES_BRAND or AUTHORIZED_PROVIDER_OF. |
GET | …/carried-brands | For a location: the brands it carries, as brand summaries. |
GET | …/locations/near | For a brand: one page of its locations nearest a point or a place — see Nearest locations. |
A location row is
{ profileId, handleText, displayName, primaryCity, primarySubdivisionCode, primaryCountryCode, overallRatingAverage, reviewCount, isActive, affiliationKindCode }. overallRatingAverage and
reviewCount are the location’s own rating; rollup.weightedRatingAverage is the
review-count-weighted mean across the brand’s locations (null until one of them has a review).
Only active profiles with a confirmed link are listed — a pending request is not a location yet.
The profile detail (GET …/profiles/{profileId} and …/by-handle/{handle}) also embeds
brand (the same summary shape, or null) and, for brand profiles, brandRollup.
Brand owner. A brand can name the company profile that
owns it; one company can own several brands. The profile detail carries it in two fields, sent as
brandOwner and ownedBrands, each entry shaped { profileId, handleText, displayName }:
| Field | On | Value |
|---|---|---|
brandOwner | a brand profile | the owning company, or null when none is recorded |
ownedBrands | any other profile | the active brands it owns, sorted by name; empty when none; null on a brand |
Render them as “A brand of {owner}” on a brand and “Our brands” on the owner, each linking to the other profile by its handle.
Brand profiles are not returned by the proximity directory (…/Profile/professionals) — a brand
is an identity, not a place. Reach it by handle. Directory rows of a brand’s locations carry
brand: { profileId, handle, displayName }; a brand keeps at most two rows per list, and those rows
carry moreFromBrand — how many more of its locations matched (link them to the brand’s nearest
locations). A q that matches the brand’s own name lists all its locations, unfolded.
Nearest locations
Section titled “Nearest locations”A brand with many locations should not hand your user a list to scroll. GET …/profiles/{brandId}/locations/near
returns one page of the brand’s public locations, nearest first.
| Query | Meaning |
|---|---|
lat, lng | The origin, decimal degrees — your user’s position. Both or neither. |
near | A city or postal code (2–80 characters), used when lat/lng are absent. It is matched against the brand’s own locations (city, case- and accent-insensitive; or a postal-code prefix of an exact address); the origin is then the centre of the matched locations. No geocoding happens. |
radiusKm | Keep only locations within this distance of the origin (plus mobile locations that cover it). |
skip, take | Paging; take is 1–50, default 10. |
prefer | Up to 10 comma-separated location ids to list first, flagged preferred — for example the location your user booked last. |
Response:
{ "total": 4, "locationCount": 4, "originKind": "POINT", "nearUnmatched": false, "locations": [ { "profileId": "…", "handleText": "pawsandco.coquitlam", "displayName": "Paws & Co. Coquitlam", "primaryCity": "Coquitlam", "primarySubdivisionCode": "BC", "primaryCountryCode": "CA", "overallRatingAverage": null, "reviewCount": 0, "distanceKm": 0.6, "distanceApproximate": false, "mobile": false, "comesToOrigin": false, "preferred": false } ]}originKindisPOINT(you sentlat/lng),PLACE(anearmatched;originLabelnames the city) orNONE(no origin: locations in country, city, handle order).nearUnmatchedistruewhennearmatched no location — say so to your user rather than showing an unordered list as if it were nearby.totalcounts the locations the filter kept (for paging);locationCountall of the brand’s public locations.distanceKmfollows what each location’s address shows publicly: exact (one decimal under 10 km) for an exact address; for a city-only address it is measured to a ~10 km cell and rounded to 5 km, withdistanceApproximate: true— render it as “about”;nullwhen the location cannot be placed (no origin, or its address shows less than its city). Unplaced locations sort last.mobile— the location travels to its clients;comesToOrigin— the origin is inside its service radius. A location that comes to the origin sorts as if it were 0 km away.- Only confirmed, publicly displayed locations of active profiles are returned, to every caller. A location that takes online bookings is not marked here: check its Scheduling provider before offering “Book”.
Opening hours and holidays
Section titled “Opening hours and holidays”Added 2026-10-09. A location can follow its brand’s weekly hours, and every business sets how it opens on the public holidays of its region (closed, usual hours, or special hours). These anonymous reads let you show a business’s real hours for any date — “Closed on Canada Day”, “Open 10:00–16:00 on Boxing Day” — without working out holidays yourself.
Base path through the gateway: /api/profile/v1/{locale}/Profile/profiles/{profileId}. These reads return
PascalCase property names inside the usual { data, resultCode } envelope; parse them case-insensitively.
| Method | Path | Returns |
|---|---|---|
GET | …/working-hours | The weekly hours in force: the brand’s current week when the location follows its brand, its own otherwise. One row per block: DayOfWeekCode (0 = Monday … 6 = Sunday), BlockSequence, OpensAtLocal / ClosesAtLocal (HH:mm:ss), IsClosed, Is24Hours, IsByAppointmentOnly, Notes. |
GET | …/working-hours/settings | { Source, BrandProfileId, BrandDisplayName, BrandHandle } — Source is OWN or BRAND (follows the brand). The brand fields are null unless the location’s brand link is shown publicly. |
GET | …/opening-calendar?from=&to= | The effective hours per date. Default today .. +30 days, at most 93. |
GET | …/holidays?from=&to= | The public holidays of the business’s region with how it opens on each. Default today .. +365 days, at most 400. |
from and to are dates (yyyy-MM-dd) in the business’s own time zone. A malformed date or a range that is too long
answers 400; an unknown profile 404.
The opening calendar
Section titled “The opening calendar”GET /api/profile/v1/en-US/Profile/profiles/{profileId}/opening-calendar?from=2026-12-24&to=2026-12-27data is { TimezoneIanaName, Days: [ … ], HolidaysUnavailable }, one entry per date:
| Field | Meaning |
|---|---|
Date | yyyy-MM-dd, local. |
IsClosed | Closed all day. |
Is24Hours | Open around the clock. |
Blocks | [{ OpensAtLocal, ClosesAtLocal }] — the open hours that day (empty when closed or 24 hours). |
ReasonCode | REGULAR (the weekly hours), HOLIDAY (closed or special hours for a public holiday) or EXCEPTION (a one-off date the business set). |
HolidayCode, HolidayName, HolidayLocalName | Set on a public holiday — also when the business keeps its usual hours that day (ReasonCode is then REGULAR). |
A business with no weekly hours at all shows every regular day as open with no blocks: its hours are unknown, not
“closed”. When the holiday list cannot be read, HolidaysUnavailable is true and no day is marked HOLIDAY; show the
weekly hours and try again later.
Holidays
Section titled “Holidays”…/holidays answers { Region, Regions, BrandProfileId, BrandDisplayName, DefaultRule, Holidays, HolidaysUnavailable }:
Region—{ CountryCode, SubdivisionCode }of the business’s primary address (SubdivisionCodebare, e.g.BC), ornullfor a brand or a business without an address. A brand lists its locations’ regions inRegions.DefaultRule— what the business does on “all other holidays”:{ Mode, OpensAtLocal, ClosesAtLocal, Source }.Holidays[]—{ Date, HolidayCode, Name, LocalName, CountryCode, SubdivisionCode, Effective, OwnRule, BrandRule, Regions }.Effective.Modeis what applies:CLOSED,OPEN(usual hours) orHOURS(special hours, withOpensAtLocal/ClosesAtLocal).
Only public holidays (days off) are listed. With nothing set anywhere, a holiday’s Effective.Mode is OPEN: the
business is open its usual hours.
The public holiday calendar
Section titled “The public holiday calendar”The same holiday data, without a business, from the reference API (anonymous):
GET /api/region/v1/holidays?country=CA&subdivision=BC&from=2026-01-01&to=2026-12-31| Query | Meaning |
|---|---|
country | Required. ISO 3166-1 alpha-2 (CA). |
subdivision | Optional. BC or CA-BC. With it: national holidays plus that province’s or state’s. Without it: national holidays only. |
allSubdivisions | Optional, true = every province’s or state’s holidays of the country too (each row says which). |
from, to | Optional dates, at most 400 days apart; default today .. +365. |
Each item (camelCase here): { date, holidayCode, name, localName, countryCode, subdivisionCode, types, sourceCode }.
subdivisionCode is null for a national holiday; types lists the kinds of day (Public, Bank, School,
Authorities, Optional, Observance) — only Public days are days off for a business. The calendar is refreshed a
few times a year, so do not cache it for long, and treat holidayCode as an opaque key.
Booking is covered in the booking guide: closed holidays have no slots.
Price reads
Section titled “Price reads”Base path through the gateway: /api/price/v1.
| Method | Path | Returns |
|---|---|---|
GET | /pricelists/by-brand/{brandProfileId} | The price lists the brand owns (every status) — the common price set its locations inherit. |
GET | /pricepoints/by-brand/{brandProfileId}?subjectKey= | The active points on the brand’s active lists, optionally narrowed to one subject key. |
These are display and editor reads: they show what a location inherits, and write no
snapshot. To learn what a buyer actually pays at a location, call POST /pricepoints/resolve
(or /pricepoints/preview) with the location’s own context and brandProfileId set to the
id from …/brand; the location’s own price wins when it exists, otherwise the brand’s, and the
result carries resolvedFromBrandList: true when the brand’s list produced the price.