SALAF EMSDevelopers
salafems.com

Getting started

From no key to your first response, in five minutes.

The Salaf Commerce API is a REST API over your store's real data — the same products, orders, customers and stock your staff see in the dashboard. It speaks JSON, authenticates with a single header, and needs nothing installed.

This page takes you from no key to a real response. It should take about five minutes.

Before you start

You need three things, and all three are things a store owner already has or can grant in a minute:

  1. A Salaf EMS account with access to the store you want to integrate.
  2. A plan that includes API access. The api_access feature is checked on every single request, not just when the key is made — so a plan change takes effect on the next call in either direction. Without it every request answers 401 UNAUTHORIZED with the message "API access is not included in your current plan."
  3. The developer permission on your user (developer.read to see keys, developer.manage to create them). Company owners have it already.

Create an API key

Keys are created in the dashboard, never through the API.

  1. Open Settings → Developer and choose Create API key.
  2. Pick the key type:
    • Store key — reads one store. This is the right default.
    • Company key — reads every live store in the company. Only a company owner can create one.
  3. Give it a name you will recognise in six months ("NetSuite nightly sync" beats "test").
  4. Tick the scopes it needs. You can only grant scopes your own role can back — a user who cannot read orders in the dashboard cannot mint a key that reads them over the API.
  5. Optionally set an expiry date.

A key looks like this — the literal prefix salaf_sk_ followed by 40 characters:

salaf_sk_7f3kq9p2xR4mVnB8tLcYwZ1sD6hJ0gKeQaUiOpNm

Make your first request

Authenticate by putting the key in an Authorization: Bearer header. That is the whole authentication story — no signing, no token exchange, no expiry to refresh.

Ask for one product. If the key works, this returns data; if it does not, the error tells you precisely why.

Paste a key and every snippet on this page switches from the placeholder to your key — and the Try it panel under each endpoint is ready to send. It is stored in this browser only and goes nowhere except, if you press Send, straight to the API host you pick there.

export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"

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

Read the response

A list endpoint answers with the rows under data and a cursor under meta:

{
  "data": [
    {
      "id": "7c4e1d90-3b2a-4f58-9e6d-1a5c8b30f2d4",
      "store_id": "b21c5f04-9d7e-4c11-8f3a-6e0d2b915a77",
      "name": "Premium Cotton T-Shirt",
      "slug": "premium-cotton-t-shirt",
      "status": "active",
      "delivery_charge": "60.00",
      "variants": [
        {
          "id": "0f9c1b8e-5f21-4a3d-9b6c-2d7e8a41c503",
          "sku": "TSHIRT-BLK-M",
          "name": "Black / M",
          "price": "1250.00",
          "discount_price": "1100.00",
          "status": "active"
        }
      ],
      "created_at": "2026-07-14T09:12:44.000Z",
      "updated_at": "2026-08-09T17:31:02.000Z"
    }
  ],
  "meta": {
    "has_more": true,
    "next_cursor": "7c4e1d90-3b2a-4f58-9e6d-1a5c8b30f2d4"
  }
}

Four conventions hold everywhere, so learning them once is enough:

ConventionLooks likeWhy
Field names are snake_casepayment_statusStable public names, decoupled from our internal columns.
Money is a string with 2 decimals"1250.00"JSON numbers are IEEE-754 doubles and cannot hold every Decimal(12,2) exactly. 1250.10 must not read back as 1250.1.
Timestamps are ISO-8601 UTC"2026-08-09T17:31:02.000Z"Always with the Z. No local time, ever.
Lists are cursor-paginatedmeta.next_cursorPages stay stable while the store keeps taking orders. See Pagination.

Fetching a single object returns that object directly — there is no envelope around it, and no success field to check. The HTTP status is the answer.

The base URL

Running the backend locally, the same routes are on your own port with no proxy in the way:

# Default local backend port is 5000 (6003 under Docker Compose).
curl -sS "http://localhost:5000/ext/v1/products?limit=1" \
  -H "Authorization: Bearer $SALAF_API_KEY"

When something goes wrong

Errors are always the same shape: one error object with a stable machine code, a human message, and a request_id.

{
  "error": {
    "code": "FORBIDDEN_SCOPE",
    "message": "This API key is missing the `products:read` scope.",
    "request_id": "4f1c9b02-7d3e-4a85-9c16-b0e2f7a34d59"
  }
}

The three you are most likely to hit in the first five minutes:

  • 401 UNAUTHORIZED — the header is missing or malformed, the key is unknown, or your plan does not include API access.
  • 403 FORBIDDEN_SCOPE — the key is valid but was not given this scope. Scopes are fixed when a key is created, so make a new key.
  • 404 NOT_FOUND — the id does not exist in the store this key can see. A record belonging to another tenant returns 404 rather than 403, on purpose: a 403 would confirm the id exists.

Keep the request_id. It is echoed in the X-Request-Id response header and is what support needs to find your exact request in our logs. Full list on the Errors page.

What is not here yet

Being straight about the boundaries, so you can plan around them:

CapabilityStatus
Reading products, orders, customers, stock, locations, channelsAvailable
Creating orders and payments, adjusting stock, writing products and customersAvailable — see Writes & idempotency
Webhooks (outbound events)Available — see Webhooks
Managing webhook endpoints over the APIDashboard only. The webhooks:manage scope is declared but not grantable
Reading or writing returns, shipments and POS sessionsWebhook events only — there is no REST endpoint for them yet
SDKs for any languageNot planned for v1 — the four sample languages are the support level
A sandbox or test-mode keyNot planned for v1. There is no salaf_sk_test_; keys act on live data

Next steps

  • Authentication — store vs company keys, rotation without downtime, and revocation.
  • Pagination — the updated_after pattern every sync integration ends up needing.
  • Writes & idempotency — every write endpoint and the Idempotency-Key contract.
  • Webhooks — stop polling for the things you need to know about quickly.
  • Guides — four end-to-end recipes.
  • API reference — every endpoint, generated from the published OpenAPI description.