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 403 with error: "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:

ScopeAllows
locations:readread locations, categories and statuses
locations:writeread, create, update and archive locations
maps:readlist maps
leads:readread enquiries

Without the required scope you get 403 with error: "scope".

Endpoints

MethodPathScopePurpose
GET/meany keytest the connection: name and scopes of the key
GET/locationslocations:readsearch and page through locations
GET/locations/{id}locations:readread one location
POST/locationslocations:writecreate a location
PATCH/locations/{id}locations:writechange only the fields you send
DELETE/locations/{id}locations:writearchive a location (like "Delete" in the app)
GET/categorieslocations:readcategories with id, name, color
GET/statuseslocations:readstatuses with id, name, color, onMap
GET/mapsmaps:readmaps with id, name, token, published, theme and number of locations
GET/leadsleads:readenquiries, 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"
ParameterEffect
qtext in name, city or street
citycity (case-insensitive)
zippostcode, starts with
countrycountry code, e.g. GB
statusID of a status (from /statuses)
categoryIdID of a category (from /categories)
updatedSinceonly locations changed since this point in time (ISO 8601)
sortid (default), created or updated; a leading - sorts descending, e.g. -updated
limit, offsetpaging; limit 1 to 500, default 100
include=photoinclude 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: name and city (or zip). country defaults to DE.
  • Coordinates: without lat and lon, 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 status the first status of your list applies.
  • Response: 201 with the new location.

More fields: categoryId, status, state, phone, email, website, manager, description, timezone, hours, specialDays, services, tags, custom, photo.

  • hours: days mo to su, each a list of up to four times HH:MM (open, close, and with a lunch break open and close again) or null for closed.
  • specialDays: list with date (YYYY-MM-DD), open, close, optional open2, close2 and label.
  • 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: 429 with Retry-After.
  • Responses are not cached (Cache-Control: no-store).
StatuserrorMeaning
400invalid and othersrequired field missing or invalid value - the message says why
401unauthorizedkey missing, wrong or revoked
403scopekey lacks the required scope
403plan_limitAPI not in the plan, or a plan limit reached
404not_foundlocation 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.

MethodPathPurpose
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}/leadssend 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.

MethodPathPurpose
POST/webhookscreate a webhook: url (public https address), events (list), optional name, format (json, slack, discord)
GET/webhookslist 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.

Try the demoTry it now, no sign-up