Customers
Customer records. Addresses are returned on the detail read only.
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.
List customers
/ext/v1/customerscustomers:readAddresses are on the detail read only — a page of 100 customers should not carry every address any of them ever saved.
Query parameters
| Name | Type | Description |
|---|---|---|
limit | numberdefault 25max 100 | Page size. |
starting_after | string | Return the page AFTER this object id (the previous page's next_cursor). |
ending_before | string | Return the page BEFORE this object id. |
sort | stringdefault -created_at | Newest first by default. Only created_at is sortable — a cursor over a mutable key (like updated_at) cannot page reliably. Use updated_after to sync changes.created_at-created_at |
store_id | string | Restrict to one store. Required breadth control for company-scoped keys; on a store-scoped key it must match the key's own store. |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS "https://api.salafems.com/ext/v1/customers?limit=25" \
-H "Authorization: Bearer $SALAF_API_KEY"const url = new URL("https://api.salafems.com/ext/v1/customers");
Object.entries({
limit: "25",
}).forEach(([key, value]) => url.searchParams.set(key, value));
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.SALAF_API_KEY}`,
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const { data, meta } = await response.json();import os
import requests
response = requests.get(
"https://api.salafems.com/ext/v1/customers",
params={
"limit": "25",
},
headers={
"Authorization": f"Bearer {os.environ['SALAF_API_KEY']}",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
page = response.json()
rows, meta = page["data"], page["meta"]<?php
$key = getenv('SALAF_API_KEY');
$ch = curl_init('https://api.salafems.com/ext/v1/customers?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}
$rows = $response['data'];
$meta = $response['meta'];https://api.salafems.com/ext/v1/customersStored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Query parameters
Blank fields are left out of the request.
Paste a key above to enable Send.
Responses
200A cursor page of customers.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"data": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"full_name": "Rafiqul Islam",
"email": null,
"phone": "01712345678",
"status": "active",
"type": "manual",
"gender": null,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"meta": {
"has_more": true,
"next_cursor": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001"
}
}Create a customer
/ext/v1/customerscustomers:writePhones are canonicalized (+880… spellings collapse to 01…) and unique per store, as is email — a duplicate is a 409. API-created customers are always type manual.
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
full_namerequired | string | — |
phone | string | Bangladeshi mobile, any accepted spelling — stored canonical (01712345678). Unique per store. |
email | string | — |
status | stringdefault active | —activeinactiveblocked |
gender | string | —malefemale |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS -X POST "https://api.salafems.com/ext/v1/customers" \
-H "Authorization: Bearer $SALAF_API_KEY" \
-H "Idempotency-Key: e7a1…-your-uuid" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Rafiqul Islam",
"phone": "01712345678"
}'const url = new URL("https://api.salafems.com/ext/v1/customers");
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SALAF_API_KEY}`,
"Idempotency-Key": "e7a1…-your-uuid",
"Content-Type": "application/json",
},
body: JSON.stringify({
"full_name": "Rafiqul Islam",
"phone": "01712345678"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"full_name": "Rafiqul Islam",
"phone": "01712345678",
}
response = requests.post(
"https://api.salafems.com/ext/v1/customers",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SALAF_API_KEY']}",
"Idempotency-Key": "e7a1…-your-uuid",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SALAF_API_KEY');
$payload = json_encode([
'full_name' => 'Rafiqul Islam',
'phone' => '01712345678',
]);
$ch = curl_init('https://api.salafems.com/ext/v1/customers');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: e7a1…-your-uuid', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.salafems.com/ext/v1/customersStored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
201The created customer, addresses included.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.409A customer with this phone or email already exists.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"full_name": "Rafiqul Islam",
"email": null,
"phone": "01712345678",
"status": "active",
"type": "manual",
"gender": null,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z",
"addresses": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"label": "Home",
"recipient_name": "Rafiqul Islam",
"recipient_phone": "01712345678",
"address_line": "House 12, Road 5",
"city": "Dhaka",
"area": "Dhanmondi",
"postal_code": "1209",
"is_default": true,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
]
}Get a customer by id, with their addresses
/ext/v1/customers/{id}customers:readThe customer plus every saved address, default address first.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The customer id. |
Query parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Restrict the lookup to one store. Breadth control for company-scoped keys; on a store-scoped key it must match the key's own store. |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS "https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SALAF_API_KEY"const url = new URL("https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.SALAF_API_KEY}`,
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
response = requests.get(
"https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID",
headers={
"Authorization": f"Bearer {os.environ['SALAF_API_KEY']}",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SALAF_API_KEY');
$ch = curl_init('https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.salafems.com/ext/v1/customers/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001Stored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
Query parameters
Blank fields are left out of the request.
Paste a key above to enable Send.
Responses
200The customer.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Customer not found.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"full_name": "Rafiqul Islam",
"email": null,
"phone": "01712345678",
"status": "active",
"type": "manual",
"gender": null,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z",
"addresses": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"label": "Home",
"recipient_name": "Rafiqul Islam",
"recipient_phone": "01712345678",
"address_line": "House 12, Road 5",
"city": "Dhaka",
"area": "Dhanmondi",
"postal_code": "1209",
"is_default": true,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
]
}Update a customer
/ext/v1/customers/{id}customers:writeSame rules as creation (canonical phone, per-store uniqueness). The seeded walk-in system record cannot be edited through any surface, this one included.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The customer id. |
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
full_name | string | — |
phone | string | — |
email | string | — |
status | string | —activeinactiveblocked |
gender | string | —malefemale |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS -X PATCH "https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SALAF_API_KEY" \
-H "Idempotency-Key: e7a1…-your-uuid" \
-H "Content-Type: application/json" \
-d '{
"email": "rafiq@example.com"
}'const url = new URL("https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID");
const response = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SALAF_API_KEY}`,
"Idempotency-Key": "e7a1…-your-uuid",
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "rafiq@example.com"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"email": "rafiq@example.com",
}
response = requests.patch(
"https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SALAF_API_KEY']}",
"Idempotency-Key": "e7a1…-your-uuid",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SALAF_API_KEY');
$payload = json_encode([
'email' => 'rafiq@example.com',
]);
$ch = curl_init('https://api.salafems.com/ext/v1/customers/REPLACE_WITH_ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: e7a1…-your-uuid', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.salafems.com/ext/v1/customers/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001Stored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
200The updated customer, addresses included.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Customer not found.409A customer with this phone or email already exists.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"full_name": "Rafiqul Islam",
"email": null,
"phone": "01712345678",
"status": "active",
"type": "manual",
"gender": null,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z",
"addresses": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"label": "Home",
"recipient_name": "Rafiqul Islam",
"recipient_phone": "01712345678",
"address_line": "House 12, Road 5",
"city": "Dhaka",
"area": "Dhanmondi",
"postal_code": "1209",
"is_default": true,
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
]
}