SALAF EMSDevelopers
salafems.com

Build a custom storefront

The read path, checkout, and what the API does not give you.

You want to build your own shopping experience — a mobile app, a headless frontend, a Facebook shop bot — on top of a Salaf store's catalogue and checkout. The read path and the order path both exist. This page is honest about where they stop.

You need products:read, inventory:read, channels:read, locations:read, orders:write, customers:write and payments:write.

What the API gives you

You needEndpointNotes
Catalogue listingGET /productsVariants are nested. Filter status=active.
One product pageGET /products/{id}
NavigationGET /categories, GET /brandsFlat lists; parent_id gives you the category tree.
SKU / barcode lookupGET /variants/{identifier}Accepts id, SKU or barcode.
AvailabilityGET /inventory/levelsFilter by variant_id.
AttributionGET /channelsSlugs for tagging the order's origin.
CheckoutPOST /ordersFind-or-create customer, stock reserved.
PaymentPOST /orders/{id}/paymentsRecord what your gateway settled.
Order statusGET /orders/{id}, webhooksFor an order-tracking page.

1. Render the catalogue

export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"

curl -sS "https://api.salafems.com/ext/v1/products?status=active&limit=50" \
  -H "Authorization: Bearer $SALAF_API_KEY"

A product carries its variants[] inline, so one list request gives you cards and prices. Two conventions to build around:

  • Money is a string with two decimals — "1250.00". Parse it as a decimal, never as a float, and never do arithmetic on it in JavaScript numbers if you can avoid it.
  • discount_price is the sale price when it is set and genuinely below price. Show both; sell at the lower one.

Cache aggressively. A catalogue changes far less often than it is browsed, and your rate limit is 120 reads a minute per key — which is plenty for a server serving from cache and nowhere near enough to proxy every page view.

2. Show availability honestly

export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"

curl -sS "https://api.salafems.com/ext/v1/inventory/levels?variant_id=REPLACE_WITH_VARIANT_ID" \
  -H "Authorization: Bearer $SALAF_API_KEY"

You get one row per location. For a single-warehouse store that is one row; for a multi-location store, sum available across the locations you actually ship from.

FieldUse it for
availableWhether a shopper may buy. on_hand − reserved.
on_handInternal reporting. Not a sellable number.
is_low_stockAn "only a few left" badge.

3. Take the order

Checkout is one call. It finds or creates the customer by phone, reserves stock, prices any coupon, and returns the whole order.

# The key identifies the ORDER you are placing, not this HTTP attempt.
# Generate it once, store it with your own record, and reuse it on retries.
IDEMPOTENCY_KEY=$(uuidgen)

curl -sS -X POST "https://api.salafems.com/ext/v1/orders" \
  -H "Authorization: Bearer $SALAF_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "facebook",
    "customer": { "phone": "01712345678", "full_name": "Rafiqul Islam" },
    "items": [
      { "sku": "TSHIRT-RED-M", "quantity": 2 }
    ],
    "shipping_charge": 120,
    "note": "Deliver after 6pm."
  }'

For a storefront specifically:

  • Send a channel slug so the merchant can see where their sales come from. GET /channels lists what the store has; omitted, orders land on manual.
  • Compute shipping_charge yourself. Nothing calculates it on this path, and omitting it means free delivery.
  • Use an idempotency key per checkout attempt, stored with your own cart or session record, so a double-tapped Pay button cannot produce two orders. See Writes & idempotency.
  • Do not send unit_price unless you have a real reason. The endpoint trusts you, which means a bug in your pricing becomes the merchant's revenue problem. Omit it and the catalogue price is used.

4. Record payment

If you take money through your own gateway, tell Salaf what settled:

export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"

curl -sS -X POST "https://api.salafems.com/ext/v1/orders/REPLACE_WITH_ORDER_ID/payments" \
  -H "Authorization: Bearer $SALAF_API_KEY" \
  -H "Idempotency-Key: REPLACE_WITH_A_UUID" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": 500,
  "transaction_id": "TRX9F3K2"
}'

transaction_id is unique per store, so replaying the same provider reference is a 409 rather than a double credit. The amount is capped at the order's net due. Recording payment in full moves the order to payment_status: "paid" and fires order.paid.

For cash on delivery — the common case in Bangladesh — record nothing at checkout. The merchant's staff record it when the courier settles.

5. Keep the shopper informed

An order-tracking page needs the order's current state. Either read it — GET /orders/{id} — or, better, subscribe to webhooks and push a notification when it changes:

EventTell the shopper
order.updated"Confirmed", "Out for delivery" — data.object.status
order.paidPayment received
order.fulfilledDelivered
order.cancelledCancelled, with the reason if you captured one

What the API does not give you

Being blunt, so you can design around it rather than discover it halfway:

Not availableWhat to do
No shopper authentication. There are no customer logins, sessions or password endpoints.Own the identity in your app. Map your user to a Salaf customer_id and store it yourself.
No cart. Carts are not a Salaf concept on this surface.Keep the cart in your own system; the order is created once, at checkout.
No shipping-rate calculation.Compute it and send shipping_charge.
No coupon validation endpoint.You can only find out by placing the order — an unusable code fails it. Validate the shape client-side and handle the failure at checkout.
No payment-gateway integration for you. Salaf records payments; it does not take them on this surface.Use your own gateway, then POST /orders/{id}/payments.
No customer address book over the API. Addresses are returned on a customer read, but there is no endpoint to add one.Send shipping_address on the order; it is stored verbatim with it.
No product search beyond search=. Name and SKU only.Index the catalogue yourself if you need facets or fuzzy matching.
No images upload. Product writes exclude media.Manage images in the dashboard; read thumbnail and images[].
No sandbox. Every key acts on live data.Test against a store the merchant treats as a test store, and use small amounts.

Rate limits and caching

120 reads and 30 writes per minute, per key. A storefront is read-heavy and bursty, which means:

  • Cache the catalogue at your edge or in your backend. Re-read it on a schedule, or on the product.* webhooks.
  • Do not cache availability for long. Seconds, not minutes.
  • Use a separate key for checkout writes, so a traffic spike on the read path cannot exhaust the bucket your orders depend on.