Skip to content

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.

Base path through the gateway: /api/profile/v1/{locale}/Profile/profiles/{profileId}.

MethodPathReturns
GET…/brandThe brand {profileId} belongs to — { profileId, handleText, displayName, locationCount } — or 404 when it has none.
GET…/locationsFor a brand: { rollup: { locationCount, weightedRatingAverage, totalReviewCount }, locations: [ … ] }.
GET…/carriersFor a brand: the profiles that stock or service it, as location rows; affiliationKindCode is CARRIES_BRAND or AUTHORIZED_PROVIDER_OF.
GET…/carried-brandsFor a location: the brands it carries, as brand summaries.
GET…/locations/nearFor 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 }:

FieldOnValue
brandOwnera brand profilethe owning company, or null when none is recorded
ownedBrandsany other profilethe 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.

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.

QueryMeaning
lat, lngThe origin, decimal degrees — your user’s position. Both or neither.
nearA 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.
radiusKmKeep only locations within this distance of the origin (plus mobile locations that cover it).
skip, takePaging; take is 1–50, default 10.
preferUp 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 }
]
}
  • originKind is POINT (you sent lat/lng), PLACE (a near matched; originLabel names the city) or NONE (no origin: locations in country, city, handle order). nearUnmatched is true when near matched no location — say so to your user rather than showing an unordered list as if it were nearby.
  • total counts the locations the filter kept (for paging); locationCount all of the brand’s public locations.
  • distanceKm follows 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, with distanceApproximate: true — render it as “about”; null when 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”.

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.

MethodPathReturns
GET…/working-hoursThe 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.

GET /api/profile/v1/en-US/Profile/profiles/{profileId}/opening-calendar?from=2026-12-24&to=2026-12-27

data is { TimezoneIanaName, Days: [ … ], HolidaysUnavailable }, one entry per date:

FieldMeaning
Dateyyyy-MM-dd, local.
IsClosedClosed all day.
Is24HoursOpen around the clock.
Blocks[{ OpensAtLocal, ClosesAtLocal }] — the open hours that day (empty when closed or 24 hours).
ReasonCodeREGULAR (the weekly hours), HOLIDAY (closed or special hours for a public holiday) or EXCEPTION (a one-off date the business set).
HolidayCode, HolidayName, HolidayLocalNameSet 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 answers { Region, Regions, BrandProfileId, BrandDisplayName, DefaultRule, Holidays, HolidaysUnavailable }:

  • Region — { CountryCode, SubdivisionCode } of the business’s primary address (SubdivisionCode bare, e.g. BC), or null for a brand or a business without an address. A brand lists its locations’ regions in Regions.
  • 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.Mode is what applies: CLOSED, OPEN (usual hours) or HOURS (special hours, with OpensAtLocal / 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 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
QueryMeaning
countryRequired. ISO 3166-1 alpha-2 (CA).
subdivisionOptional. BC or CA-BC. With it: national holidays plus that province’s or state’s. Without it: national holidays only.
allSubdivisionsOptional, true = every province’s or state’s holidays of the country too (each row says which).
from, toOptional 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.

Base path through the gateway: /api/price/v1.

MethodPathReturns
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.