Products
Products with their variants nested — variants are the commercial unit, since price, SKU and stock all hang off them.
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 products
/ext/v1/productsproducts:readVariants are nested in each product — they are the commercial unit. Use updated_after to sync only what changed.
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. |
status | string | Filter to one of the values below.draftactivearchived |
category_id | string | Only products in this category. |
updated_after | date-time | Only products changed since this instant — the sync filter. Pair it with the default -created_at order and page with the cursor. |
search | string | Match on product name or variant SKU. |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS "https://api.salafems.com/ext/v1/products?limit=25&updated_after=2026-08-01T00%3A00%3A00Z" \
-H "Authorization: Bearer $SALAF_API_KEY"const url = new URL("https://api.salafems.com/ext/v1/products");
Object.entries({
limit: "25",
updated_after: "2026-08-01T00:00:00Z",
}).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/products",
params={
"limit": "25",
"updated_after": "2026-08-01T00:00:00Z",
},
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/products?limit=25&updated_after=2026-08-01T00%3A00%3A00Z');
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/productsStored 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 products.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",
"name": "Classic Cotton T-Shirt",
"slug": "classic-cotton-t-shirt",
"short_description": null,
"description": null,
"thumbnail": null,
"status": "active",
"delivery_charge": "1250.00",
"rating_average": "4.50",
"rating_count": 12,
"brand": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Acme",
"slug": "acme"
},
"categories": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "T-Shirts",
"slug": "t-shirts",
"is_primary": true
}
],
"images": [
{
"url": "https://cdn.example.com/p/1.webp",
"position": 0
}
],
"variants": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"product_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"sku": "TSHIRT-RED-M",
"barcode": "0123456789012",
"name": "Red / M",
"price": "1250.00",
"discount_price": "1250.00",
"weight": "0.25",
"dimensions": null,
"low_stock_threshold": 5,
"status": "active",
"image": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"url": "https://cdn.example.com/p/1.webp"
},
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"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 product
/ext/v1/productsproducts:writeAt least one variant (a simple product has exactly one). Slugs auto-generate from the name and auto-suffix when taken; a duplicate SKU is a 409. Media, attribute wiring and initial stock are dashboard concerns — stock enters through POST /v1/inventory/adjustments.
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. |
namerequired | string | — |
slug | string | URL-safe slug. Auto-generated from the name when omitted; auto-suffixed when taken. |
brand_id | uuid | — |
short_description | string | — |
description | string | — |
status | stringdefault draft | —draftactivearchived |
delivery_charge | number | Per-product delivery-charge override: 0 = free delivery, N = flat charge; omit for the standard shipping rules. |
categories | ProductCategoryLinkDto[] | — |
variantsrequired | ProductVariantWriteDto[] | — |
categories[] fields
| Name | Type | Description |
|---|---|---|
idrequired | uuid | — |
is_primary | booleandefault false | — |
variants[] fields
| Name | Type | Description |
|---|---|---|
id | uuid | Update only: patches this existing variant. Omit to create a new one. |
skurequired | string | — |
barcode | string | — |
namerequired | string | — |
pricerequired | number | — |
discount_price | number | Sale price — must be strictly below price. |
cost_price | number | What the unit costs the store. Write-only (never serialized, Q6); defaults to 0 when omitted. |
weight | number | — |
low_stock_threshold | numberdefault 0 | — |
status | stringdefault active | —activeinactive |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS -X POST "https://api.salafems.com/ext/v1/products" \
-H "Authorization: Bearer $SALAF_API_KEY" \
-H "Idempotency-Key: e7a1…-your-uuid" \
-H "Content-Type: application/json" \
-d '{
"name": "Classic Cotton T-Shirt",
"status": "active",
"variants": [
{
"sku": "TSHIRT-RED-M",
"name": "Red / M",
"price": 1250,
"cost_price": 850
}
]
}'const url = new URL("https://api.salafems.com/ext/v1/products");
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({
"name": "Classic Cotton T-Shirt",
"status": "active",
"variants": [
{
"sku": "TSHIRT-RED-M",
"name": "Red / M",
"price": 1250,
"cost_price": 850
}
]
}),
});
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 = {
"name": "Classic Cotton T-Shirt",
"status": "active",
"variants": [
{
"sku": "TSHIRT-RED-M",
"name": "Red / M",
"price": 1250,
"cost_price": 850,
},
],
}
response = requests.post(
"https://api.salafems.com/ext/v1/products",
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([
'name' => 'Classic Cotton T-Shirt',
'status' => 'active',
'variants' => [
[
'sku' => 'TSHIRT-RED-M',
'name' => 'Red / M',
'price' => 1250,
'cost_price' => 850,
],
],
]);
$ch = curl_init('https://api.salafems.com/ext/v1/products');
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/productsStored 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 product, variants nested.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 variant SKU is already in use in this store.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",
"name": "Classic Cotton T-Shirt",
"slug": "classic-cotton-t-shirt",
"short_description": null,
"description": null,
"thumbnail": null,
"status": "active",
"delivery_charge": "1250.00",
"rating_average": "4.50",
"rating_count": 12,
"brand": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Acme",
"slug": "acme"
},
"categories": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "T-Shirts",
"slug": "t-shirts",
"is_primary": true
}
],
"images": [
{
"url": "https://cdn.example.com/p/1.webp",
"position": 0
}
],
"variants": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"product_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"sku": "TSHIRT-RED-M",
"barcode": "0123456789012",
"name": "Red / M",
"price": "1250.00",
"discount_price": "1250.00",
"weight": "0.25",
"dimensions": null,
"low_stock_threshold": 5,
"status": "active",
"image": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"url": "https://cdn.example.com/p/1.webp"
},
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Get a product by id
/ext/v1/products/{id}products:readThe full product object, variants nested.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The product 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/products/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SALAF_API_KEY"const url = new URL("https://api.salafems.com/ext/v1/products/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/products/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/products/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/products/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 product.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.404Product 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",
"name": "Classic Cotton T-Shirt",
"slug": "classic-cotton-t-shirt",
"short_description": null,
"description": null,
"thumbnail": null,
"status": "active",
"delivery_charge": "1250.00",
"rating_average": "4.50",
"rating_count": 12,
"brand": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Acme",
"slug": "acme"
},
"categories": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "T-Shirts",
"slug": "t-shirts",
"is_primary": true
}
],
"images": [
{
"url": "https://cdn.example.com/p/1.webp",
"position": 0
}
],
"variants": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"product_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"sku": "TSHIRT-RED-M",
"barcode": "0123456789012",
"name": "Red / M",
"price": "1250.00",
"discount_price": "1250.00",
"weight": "0.25",
"dimensions": null,
"low_stock_threshold": 5,
"status": "active",
"image": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"url": "https://cdn.example.com/p/1.webp"
},
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Update a product
/ext/v1/products/{id}products:writePartial update of the product fields. categories (when present) replaces the link set; variants (when present) reconciles the FULL variant set — patch by id, create without one, delete the rest unless stock history forbids it. Same semantics as the dashboard.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The product 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. |
name | string | — |
slug | string | — |
brand_id | uuid | Set null to detach the brand. |
short_description | string | — |
description | string | — |
status | string | —draftactivearchived |
delivery_charge | object | Set null to return to the standard shipping rules. |
categories | ProductCategoryLinkDto[] | When present, REPLACES the product's category links. |
variants | ProductVariantWriteDto[] | When present, reconciles the FULL variant set in one transaction — entries with an id are patched, entries without one are created, and existing variants missing from the list are removed (refused when they carry stock history). Same semantics as the dashboard. |
categories[] fields
| Name | Type | Description |
|---|---|---|
idrequired | uuid | — |
is_primary | booleandefault false | — |
variants[] fields
| Name | Type | Description |
|---|---|---|
id | uuid | Update only: patches this existing variant. Omit to create a new one. |
skurequired | string | — |
barcode | string | — |
namerequired | string | — |
pricerequired | number | — |
discount_price | number | Sale price — must be strictly below price. |
cost_price | number | What the unit costs the store. Write-only (never serialized, Q6); defaults to 0 when omitted. |
weight | number | — |
low_stock_threshold | numberdefault 0 | — |
status | stringdefault active | —activeinactive |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS -X PATCH "https://api.salafems.com/ext/v1/products/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SALAF_API_KEY" \
-H "Idempotency-Key: e7a1…-your-uuid" \
-H "Content-Type: application/json" \
-d '{
"status": "active"
}'const url = new URL("https://api.salafems.com/ext/v1/products/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({
"status": "active"
}),
});
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 = {
"status": "active",
}
response = requests.patch(
"https://api.salafems.com/ext/v1/products/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([
'status' => 'active',
]);
$ch = curl_init('https://api.salafems.com/ext/v1/products/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/products/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 product, variants nested.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.404Product not found.409A variant SKU is already in use in this store.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",
"name": "Classic Cotton T-Shirt",
"slug": "classic-cotton-t-shirt",
"short_description": null,
"description": null,
"thumbnail": null,
"status": "active",
"delivery_charge": "1250.00",
"rating_average": "4.50",
"rating_count": 12,
"brand": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Acme",
"slug": "acme"
},
"categories": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "T-Shirts",
"slug": "t-shirts",
"is_primary": true
}
],
"images": [
{
"url": "https://cdn.example.com/p/1.webp",
"position": 0
}
],
"variants": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"product_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"sku": "TSHIRT-RED-M",
"barcode": "0123456789012",
"name": "Red / M",
"price": "1250.00",
"discount_price": "1250.00",
"weight": "0.25",
"dimensions": null,
"low_stock_threshold": 5,
"status": "active",
"image": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"url": "https://cdn.example.com/p/1.webp"
},
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Archive a product
/ext/v1/products/{id}/archiveproducts:writeNon-destructive: variants, images and inventory survive with their status parked, and the dashboard can restore. Archived products drop out of GET /v1/products.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The product 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. |
Request
export SALAF_API_KEY="salaf_sk_YOUR_API_KEY"
curl -sS -X POST "https://api.salafems.com/ext/v1/products/REPLACE_WITH_ID/archive" \
-H "Authorization: Bearer $SALAF_API_KEY" \
-H "Idempotency-Key: e7a1…-your-uuid" \
-H "Content-Type: application/json" \
-d '{}'const url = new URL("https://api.salafems.com/ext/v1/products/REPLACE_WITH_ID/archive");
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({}),
});
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 = {}
response = requests.post(
"https://api.salafems.com/ext/v1/products/REPLACE_WITH_ID/archive",
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((object) []);
$ch = curl_init('https://api.salafems.com/ext/v1/products/REPLACE_WITH_ID/archive');
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/products/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001/archiveStored 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
201Archival confirmed.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.404Product 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",
"archived": true
}Get a variant by id, SKU or barcode
/ext/v1/variants/{identifier}products:readAnswers "what is this thing in my hand?" for a scanned barcode or a SKU from a spreadsheet.
Path parameters
| Name | Type | Description |
|---|---|---|
identifierrequired | string | A variant id, SKU or barcode. All three resolve here. |
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/variants/SKU-001" \
-H "Authorization: Bearer $SALAF_API_KEY"const url = new URL("https://api.salafems.com/ext/v1/variants/SKU-001");
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/variants/SKU-001",
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/variants/SKU-001');
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/variants/TSHIRT-RED-MStored 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 variant.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.404Variant 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",
"product_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"sku": "TSHIRT-RED-M",
"barcode": "0123456789012",
"name": "Red / M",
"price": "1250.00",
"discount_price": "1250.00",
"weight": "0.25",
"dimensions": null,
"low_stock_threshold": 5,
"status": "active",
"image": {
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"url": "https://cdn.example.com/p/1.webp"
},
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}