REST API
With the customer API you maintain locations straight from your ERP, CRM or shop. On top, every published map has a few public endpoints.
Requirements
- The customer API and API keys are included from the Starter plan (see pricing). On other plans the API answers
403witherror: "plan_limit". - Create the key in the app under API & Webhooks → Create API key. You need the permission to manage the API (e.g. owners and admins).
- The key is shown only once. We store only a hash of it. Treat it like a password and revoke it when you no longer need it.
The same page in the app offers a ready-made Postman collection and an OpenAPI file for download.
Authentication
Base address: https://app.my-store-locator.com/api/v1/ext
Send the key in the Authorization header (or alternatively in X-Api-Key):
curl https://app.my-store-locator.com/api/v1/ext/me \
-H "Authorization: Bearer msl_live_YOUR-KEY"{ "ok": true, "key": "ERP", "scopes": ["locations:read", "locations:write"] }The API is meant for server-to-server calls. It sends no CORS headers - it does not work from a visitor's browser, and the key does not belong in website code anyway.
Scopes
When you create a key, you choose what it may do:
| Scope | Allows |
|---|---|
locations:read | read locations, categories and statuses |
locations:write | read, create, update and archive locations |
maps:read | list maps |
leads:read | read enquiries |
Without the required scope you get 403 with error: "scope".
Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /me | any key | test the connection: name and scopes of the key |
| GET | /locations | locations:read | search and page through locations |
| GET | /locations/{id} | locations:read | read one location |
| POST | /locations | locations:write | create a location |
| PATCH | /locations/{id} | locations:write | change only the fields you send |
| DELETE | /locations/{id} | locations:write | archive a location (like "Delete" in the app) |
| GET | /categories | locations:read | categories with id, name, color |
| GET | /statuses | locations:read | statuses with id, name, color, onMap |
| GET | /maps | maps:read | maps with id, name, token, published, theme and number of locations |
| GET | /leads | leads:read | enquiries, newest first |
Fetching locations
curl "https://app.my-store-locator.com/api/v1/ext/locations?city=London&limit=50" \
-H "Authorization: Bearer msl_live_YOUR-KEY"| Parameter | Effect |
|---|---|
q | text in name, city or street |
city | city (case-insensitive) |
zip | postcode, starts with |
country | country code, e.g. GB |
status | ID of a status (from /statuses) |
categoryId | ID of a category (from /categories) |
updatedSince | only locations changed since this point in time (ISO 8601) |
sort | id (default), created or updated; a leading - sorts descending, e.g. -updated |
limit, offset | paging; limit 1 to 500, default 100 |
include=photo | include the photo (can be large) |
Without sort the response is sorted by id:
{
"data": [
{
"id": 1003,
"name": "Covent Garden store",
"street": "27 Long Acre",
"zip": "WC2E 9DD",
"city": "London",
"state": "England",
"country": "GB",
"lat": 51.5104,
"lon": -0.1239,
"phone": "020 7946 0735",
"email": "covent-garden@example.com",
"website": "https://example.com/covent-garden",
"manager": "",
"description": "",
"timezone": "Europe/London",
"hours": { "mo": ["09:00", "18:00"], "sa": ["10:00", "14:00"], "su": null },
"specialDays": [{ "date": "2026-12-24", "open": "09:00", "close": "13:00", "label": "Christmas Eve" }],
"services": ["Parking"],
"tags": ["New"],
"custom": {},
"categoryId": 12,
"status": "st_active_7",
"created": "2026-10-20T08:12:00.000Z",
"updated": "2026-10-24T15:40:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}For a regular sync, remember the time of the last run and next time ask only for the changes with updatedSince.
Creating and changing a location
curl -X POST https://app.my-store-locator.com/api/v1/ext/locations \
-H "Authorization: Bearer msl_live_YOUR-KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Manchester store", "street": "1 Market St", "zip": "M1 1AA", "city": "Manchester", "country": "GB",
"hours": { "mo": ["09:00", "13:00", "14:00", "18:00"] } }'- Required:
nameandcity(orzip).countrydefaults toDE. - Coordinates: without
latandlon, our map server looks up the address. - Maps: like in the app, the new location is added to all published maps. With
"maps": [4, 7]you choose the maps yourself. - Status: without
statusthe first status of your list applies. - Response:
201with the new location.
More fields: categoryId, status, state, phone, email, website, manager, description, timezone, hours, specialDays, services, tags, custom, photo.
hours: daysmotosu, each a list of up to four timesHH:MM(open, close, and with a lunch break open and close again) ornullfor closed.specialDays: list withdate(YYYY-MM-DD),open,close, optionalopen2,close2andlabel.services,tags: lists of texts.custom: values of your custom fields, key = ID of the field. Unknown fields are ignored.photo: an https address; we download the image and store it ourselves, so the map never calls third-party servers.
PATCH /locations/{id} changes only the fields you send. DELETE /locations/{id} archives the location like "Delete" in the app and answers { "id": 1003, "deleted": true }.
Write calls go through the same checks as the app: plan limits, required fields, valid values. The account's audit log lists them as "API-Key …" with the name of the key.
Limits and errors
- 600 requests per minute and key. Above that:
429withRetry-After. - Responses are not cached (
Cache-Control: no-store).
| Status | error | Meaning |
|---|---|---|
| 400 | invalid and others | required field missing or invalid value - the message says why |
| 401 | unauthorized | key missing, wrong or revoked |
| 403 | scope | key lacks the required scope |
| 403 | plan_limit | API not in the plan, or a plan limit reached |
| 404 | not_found | location not found |
| 429 | - | too many requests |
The message is German by default. With the header X-MSL-Lang: en most messages come in English.
Public endpoints of a map
The map itself uses these endpoints. They need no key, work only for published maps of active accounts and are limited per IP address. The token is the same as in the embed code.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/public/maps/{token} | the map with settings, visible locations, categories, statuses, public custom fields and company details (120 per minute) |
| POST | /api/v1/public/maps/{token}/leads | send an enquiry from the contact form: name and email required, plus locationId, phone, type, message (Pro plan and above, 10 per minute) |
These endpoints send no CORS headers either. Every fetch of the map counts as a map view in your statistics. If you want to copy map data into your own system regularly, use the customer API instead.
Webhooks through the API
Platforms such as Zapier, Make and n8n create and remove webhooks themselves. The API key needs the scope webhooks:write.
| Method | Path | Purpose |
|---|---|---|
POST | /webhooks | create a webhook: url (public https address), events (list), optional name, format (json, slack, discord) |
GET | /webhooks | list the webhooks created with this key |
DELETE | /webhooks/{id} | remove a webhook |
Possible events: location.created, location.updated, location.deleted, map.published, lead.created, import.finished. The response to POST contains the secret for checking the signature, once. Details under Webhooks.
OpenAPI file
The complete API description is public at https://app.my-store-locator.com/api/v1/ext/openapi.json (German with ?lang=de). You can load it straight into Postman, Swagger UI or the import function of many platforms.
Try it with your own locations.
Create an account, import your locations, embed the map. The Free plan stays free - and you can test Pro for 14 days.