Enterprise Store Locations API
Complete reference for pushing, listing, inspecting, and removing your organisation’s canonical store master records (/api/v1/locations) by store_code.
This guide is for engineering and data teams connecting an ERP, POS, or store master database to ProfilePilot. It documents all four store master endpoints (POST /api/v1/locations, GET /api/v1/locations, GET /api/v1/locations/{store_code}, and DELETE /api/v1/locations/{store_code}), their complete request/response schemas, authentication headers, and operational guardrails.
Prerequisites, Authentication & Guardrails
- Commercial & Product Entitlement: Requires Developer API Access (
$50/monthminimum covering your first500,000API calls per UTC month) enabled under Settings → Usage & Add-ons, plus an active Locations & Reputation subscription. - API Key Creation: An organisation owner or manager creates a key in Settings → Developer API (
/dashboard/settings/developer) withlocations:readand/orlocations:writescopes. Copy the secret immediately — it is displayed once and stored only as a salted SHA-256 hash. Bothppapi_<org>_<secret>andpploc_<org>_<secret>prefixes are accepted. - Authentication Headers: Call from your backend server only (never from browser or mobile client code). Send
Authorization: Bearer <your_api_key>andContent-Type: application/json. You may optionally sendx-org-id: <your_org_uuid>— if provided, it must match the organisation that owns the key or the request is rejected with403 Forbidden. - What This API Does & Does Not Do: Each
POSTupserts your organisation’s canonical submitted store master bystore_code. It does not automatically overwrite live Google Business Profile, Facebook, or Bing listings — public listing edits remain human-reviewed inside the Locations module. If a matchingstore_codeexists on a connected Google listing, the response setslinked_google_location: truewithout exposing internal Google account IDs.
1. Create or Update Stores (POST /api/v1/locations)
Endpoint: POST https://profilepilotapp.com/api/v1/locations Required Scope: locations:write Required Product Subscription: Locations & Reputation + Developer API Access Batch Limit: Up to 100 stores per request (send { "locations": [ ... ] } or a single store JSON object with store_code).
Request Body Fields (per store object):
store_code(string, required) — Your stable, unique branch identifier (1–64characters; letters, numbers,.,_,-, starting with a letter or number). Repeating an existingstore_codeupdates that store in place.name(string, required on creation, max200chars) — Public trading name of the store. Optional on subsequent partial updates; cannot be cleared tonullor empty.external_id(string | null, optional, max128chars) — Your internal ERP/POS primary key or cost-centre code.phone(string | null, optional, max32chars) — Primary store phone number (digits, spaces, and+ ( ) . -, e.g."+27215550100").website(string | null, optional, max300chars) — Store landing page URL (http://orhttps://).description(string | null, optional, max1,000chars) — Store summary or customer description.status("open"|"temporarily_closed"|"permanently_closed", optional) — Defaults to"open"when creating a new store.address(object | null, optional) — Replaces the stored address object when provided (nullclears it):lines(string[], up to5street lines, max120chars each)locality(string | null, max80chars) — City or townregion(string | null, max80chars) — Province or statepostal_code(string | null, max16chars) — Postal / ZIP codecountry(string | null,2-letter ISO-3166-1 alpha-2 code, e.g."ZA","GB","US")latitude&longitude(number | null, optional) — Must be sent together as decimal degrees (latitude-90to90,longitude-180to180), or bothnullto clear the pin. Sending only one coordinate returns an error.hours(object | null, optional) — Keyed by lowercase weekday (monday,tuesday,wednesday,thursday,friday,saturday,sunday). Each open day is a single 24-hour window"HH:MM-HH:MM"(e.g."08:30-17:30"), and closed days are"closed"or omitted. Sendinghoursreplaces the entire weekly schedule for that store.
Response Fields (`POST /api/v1/locations`):
ok(boolean) —trueonly when every store in the payload succeeded (errors.length === 0).created(number) — Count of new stores inserted in this call.updated(number) — Count of existing stores updated in this call.results[]— Array of{ store_code, status: "created" | "updated", id }for each saved row.errors[]— Array of{ store_code, error }for any invalid row in the batch (valid rows in the same batch are still saved).
curl -sS -X POST "https://profilepilotapp.com/api/v1/locations" \
-H "Authorization: Bearer ppapi_your_secret_key" \
-H "x-org-id: YOUR_ORGANISATION_UUID" \
-H "Content-Type: application/json" \
-d '{
"locations": [
{
"store_code": "HS-CPT-01",
"external_id": "erp-4401",
"name": "Harbour Stores Cape Town",
"phone": "+27215550100",
"website": "https://example.com/stores/cape-town",
"description": "Flagship Foreshore branch with click-and-collect.",
"status": "open",
"address": {
"lines": ["Shop 4, Example Mall", "12 Wharf Street"],
"locality": "Cape Town",
"region": "Western Cape",
"postal_code": "8001",
"country": "ZA"
},
"latitude": -33.9249,
"longitude": 18.4241,
"hours": {
"monday": "09:00-17:00",
"tuesday": "09:00-17:00",
"wednesday": "09:00-17:00",
"thursday": "09:00-17:00",
"friday": "09:00-17:00",
"saturday": "09:00-13:00",
"sunday": "closed"
}
}
]
}'{
"ok": true,
"created": 1,
"updated": 0,
"results": [
{
"store_code": "HS-CPT-01",
"status": "created",
"id": "8f1c2b40-6a1e-4d3a-9c2e-1b7a0e5d44a1"
}
],
"errors": []
}2. List Submitted Stores (GET /api/v1/locations)
Endpoint: GET https://profilepilotapp.com/api/v1/locations Required Scope: locations:read Required Product Subscription: Locations & Reputation + Developer API Access
Query Parameters:
store_code(string, optional) — Filter to an exactstore_code.updated_since(string, optional, ISO-8601 timestamp e.g.2026-10-01T00:00:00Z) — Return only stores modified strictly after this timestamp.limit(number, optional,1–100, default50) — Page size.offset(number, optional,0–10000, default0) — Pagination offset.
Response Fields (`GET /api/v1/locations`):
ok(boolean),limit(number),offset(number),returned(number) — Pagination envelope.locations[]— Array of public store master objects (id,store_code,external_id,name,phone,website,address,latitude,longitude,status,hours,description,linked_google_location,created_at,updated_at).
curl -sS -X GET "https://profilepilotapp.com/api/v1/locations?updated_since=2026-10-01T00:00:00Z&limit=50&offset=0" \
-H "Authorization: Bearer ppapi_your_secret_key"{
"ok": true,
"limit": 50,
"offset": 0,
"returned": 1,
"locations": [
{
"id": "8f1c2b40-6a1e-4d3a-9c2e-1b7a0e5d44a1",
"store_code": "HS-CPT-01",
"external_id": "erp-4401",
"name": "Harbour Stores Cape Town",
"phone": "+27215550100",
"website": "https://example.com/stores/cape-town",
"address": {
"lines": ["Shop 4, Example Mall", "12 Wharf Street"],
"locality": "Cape Town",
"region": "Western Cape",
"postal_code": "8001",
"country": "ZA"
},
"latitude": -33.9249,
"longitude": 18.4241,
"status": "open",
"hours": {
"monday": "09:00-17:00",
"tuesday": "09:00-17:00",
"wednesday": "09:00-17:00",
"thursday": "09:00-17:00",
"friday": "09:00-17:00",
"saturday": "09:00-13:00",
"sunday": "closed"
},
"description": "Flagship Foreshore branch with click-and-collect.",
"linked_google_location": true,
"created_at": "2026-10-01T08:12:00.000Z",
"updated_at": "2026-10-20T09:30:11.000Z"
}
]
}3. Get Single Store by Code (GET /api/v1/locations/{store_code})
Endpoint: GET https://profilepilotapp.com/api/v1/locations/{store_code} Required Scope: locations:read Path Parameter: store_code (URL-encoded store code, e.g. HS-CPT-01). Returns 404 Not Found if the store code does not exist in your organisation’s submitted master file.
curl -sS -X GET "https://profilepilotapp.com/api/v1/locations/HS-CPT-01" \
-H "Authorization: Bearer ppapi_your_secret_key"{
"ok": true,
"location": {
"id": "8f1c2b40-6a1e-4d3a-9c2e-1b7a0e5d44a1",
"store_code": "HS-CPT-01",
"external_id": "erp-4401",
"name": "Harbour Stores Cape Town",
"phone": "+27215550100",
"website": "https://example.com/stores/cape-town",
"address": {
"lines": ["Shop 4, Example Mall", "12 Wharf Street"],
"locality": "Cape Town",
"region": "Western Cape",
"postal_code": "8001",
"country": "ZA"
},
"latitude": -33.9249,
"longitude": 18.4241,
"status": "open",
"hours": {
"monday": "09:00-17:00",
"tuesday": "09:00-17:00",
"wednesday": "09:00-17:00",
"thursday": "09:00-17:00",
"friday": "09:00-17:00",
"saturday": "09:00-13:00",
"sunday": "closed"
},
"description": "Flagship Foreshore branch with click-and-collect.",
"linked_google_location": true,
"created_at": "2026-10-01T08:12:00.000Z",
"updated_at": "2026-10-20T09:30:11.000Z"
}
}4. Delete Store from Master File (DELETE /api/v1/locations/{store_code})
Endpoint: DELETE https://profilepilotapp.com/api/v1/locations/{store_code} Required Scope: locations:write Behaviour: Removes the store record from your submitted store master file in ProfilePilot. It does not delete or mark permanently closed your live Google Business Profile listing (to mark a store closed in the master file without deleting its history, POST "status": "permanently_closed" instead).
curl -sS -X DELETE "https://profilepilotapp.com/api/v1/locations/HS-CPT-01" \
-H "Authorization: Bearer ppapi_your_secret_key"{
"ok": true,
"deleted": true,
"store_code": "HS-CPT-01"
}5. Common Pitfalls & Troubleshooting
- Why didn’t my `POST` immediately change Google Maps? By design,
/api/v1/locationsupdates your organisation’s submitted store master and links matchingstore_coderecords (linked_google_location: true). Live Google, Facebook, and Bing publishing is controlled from the Locations console so unverified ERP fields cannot accidentally trigger a Google Business Profile re-verification lock. - HTTP `200` with `"ok": false` on batch `POST`: When you send multiple stores in
locations[], valid stores are committed even if one or more rows fail validation. Always inspect bothokanderrors[]({ store_code, error }) and retry the rejected rows after fixing the field format. - `latitude and longitude must be provided together`: Never send
latitudewithoutlongitude(or vice versa). Send both as numbers (-90..90and-180..180), or send both asnullto clear coordinates. - Invalid `hours.<day>` format / split shifts: Each open weekday accepts a single 24-hour range
"HH:MM-HH:MM"(for example"08:00-17:00") or"closed". Multi-window split shifts (e.g."09:00-12:00, 14:00-17:00") are rejected — send the store’s primary daily opening span. - `403 Forbidden` responses: Check three things: (1) Developer API Access (
$50/monthadd-on) is enabled in Settings → Usage & Add-ons, (2) the organisation has an active Locations & Reputation subscription, and (3) the API key includeslocations:read(forGET) orlocations:write(forPOST/DELETE) and matchesx-org-idif that header was sent.
- Confirm Developer API Access and Locations & Reputation are active for the organisation
- Create a key with locations:read and locations:write in Settings → Developer API
- POST up to 100 stores per batch to /api/v1/locations and inspect ok + errors[]
- GET /api/v1/locations or /api/v1/locations/{store_code} to verify saved fields and linked_google_location
- Use DELETE /api/v1/locations/{store_code} to remove obsolete master records
Developer API Reference
All Developer & Reporting API Modules
locations:readlocations:writeEnterprise Store Locations API
Push and query your canonical store master records (up to 100 stores per batch) by store_code.
reports:readlocations:readLocations Metrics & Local SEO Geo-Grid API
Pull Google Business Profile impressions, website clicks, phone calls, direction requests, peak footfall days, Meta store metrics, and Geo-Grid Share of Voice.
reputation:readreports:readReputation, Reviews, Red Flags & CSAT API
Export star ratings, customer reviews, reply rates, business-hours response times, Red Flag alerts, and Customer Satisfaction cases.
whatsapp:readreports:readWhatsApp Commerce, Orders, Finance & Usage API
Export WhatsApp orders with normalized line items, fulfilment SLAs, paid GMV analytics, metered message volume, Auth OTP challenges, and loyalty balances.
email:readmarketing:readreports:readEmail Marketing, Workflows & Omnichannel Campaigns API
Pull active eligible subscriber counts, email campaign & automation engagement rates, WhatsApp marketing campaigns, and multi-platform social posts.
inbox:readsocial:readreports:readSmart Inbox SLA & Social Inbox Engagement API
Export Smart Inbox ticket volumes and business-hours SLA metrics alongside Facebook & Instagram comment sentiment and actionable response rates.
insights:readreports:readInsights Paid Media, GA4, Search Console & Attribution API
Pull paid media performance (Google Ads & Meta Ads), GA4 sessions and conversions, Google Search Console metrics, and True Attribution revenue.