Skip to content

Using Store and Facility Data

Stores are your physical or virtual locations; facilities are amenities a store can offer (parking, drive-through, kids area). Both are project-level reference data. Storelayer does not attach a store to a customer, event or cart by itself: your app sends the storeId with each cart or event, and promotions and rules compare against it.

POST /projects/{projectId}/stores

{
"id": "ist-kadikoy",
"name": "Kadıköy",
"status": "active",
"countryCode": "TR",
"location": { "city": "Istanbul", "postalCode": "34710", "lat": 40.9903, "lng": 29.0275, "formattedAddress": "Caferağa Mah., Kadıköy" },
"phone": "+902161234567",
"workingHours": { "mon": { "open": "08:00", "close": "22:00" }, "sun": null },
"facilityIds": ["fac_k2m9x7"],
"tags": ["flagship", "airport-adjacent"],
"metadata": { "posCode": "K01", "region": "anatolia" }
}

Only name is required. Send your own id (your POS or ERP store code): it is the value carts and events will carry. Without one Storelayer generates store_…. status is active (default), inactive or temporarily_closed. workingHours keys are mon…sun; null means closed. Also available: description, email, managerId, imageUrl (or upload with PUT /stores/{storeId}/image).

GET /projects/{projectId}/stores?status=active&countryCode=TR&limit=100 lists stores oldest first, as { "items": [...], "total": n }.

PUT /projects/{projectId}/stores/{storeId} changes only the top-level fields you send, but each object or array you send replaces the stored value: sending metadata, location, tags, facilityIds or workingHours overwrites it entirely. Read the store first and send the complete object.

POST /projects/{projectId}/stores/import

{ "stores": [ { "id": "ist-kadikoy", "name": "Kadıköy" }, { "id": "ank-cankaya", "name": "Çankaya" } ] }

Returns { "created": n, "skipped": n, "errors": [{ "index": 3, "id": "…", "message": "…" }] }. Stores whose id already exists are skipped, not updated, so the import is safe to re-run. To change existing stores, use PUT per store. Imports without ids create a new store on every run.

POST /projects/{projectId}/facilities with { "name": "Parking", "description": "Free for 2 hours", "icon": null } returns a facility with a generated id. Link facilities by putting their IDs in a store’s facilityIds. Storelayer does not check those links: deleting a facility leaves its ID in stores’ facilityIds, so remove it from stores yourself.

The cart sent to POST /projects/{projectId}/promotions/evaluate accepts storeId (plus salesChannel and region). Conditions read it as {{ $('cart').storeId }}:

{
"combinator": "OR",
"conditions": [
{ "leftValue": "{{ $('cart').storeId }}", "operator": "eq", "rightValue": "ist-kadikoy", "rightType": "string" },
{ "leftValue": "{{ $('cart').storeId }}", "operator": "eq", "rightValue": "ank-cankaya", "rightType": "string" }
]
}

A cart without storeId has storeId: null and fails these conditions, which is the safe default for an in-store-only offer. For “everywhere except store X”, combine neq with an exists check so online carts without a store don’t qualify by accident (or do, if that is intended).

Events carry whatever your app puts in payload, for example "payload": { "orderId": "ord_1", "amount": 4599, "storeId": "ist-kadikoy" }. Compare it directly with {{ $('event').payload.storeId }}. To use store attributes (tags, status, metadata), register an internal resource and declare it on the rule:

{
"key": "store",
"name": "Purchase store",
"type": "internal",
"config": { "toolName": "stores.get_store", "params": { "storeId": "{{ $('event').payload.storeId }}" } },
"dependsOn": ["event"]
}

Then {{ $('store').tags }} contains "airport-adjacent" awards only at tagged stores. An unknown or missing storeId resolves to an object with only _error, so field conditions fail rather than match.

Test an eligible store, an excluded store, and a request with no storeId, using promotions/evaluate (dry run) for promotions and POST /projects/{projectId}/rules/test for rule conditions. Before deleting or renaming a store id, search promotions and rules for that value: conditions compare strings and do not follow renames.