Products
Create, read, update, and delete products in your shop catalog. Manage variants, options, images, and pricing.
Products
Full CRUD for your product catalog. Products support variants (size, color), options (extras), multiple images, and external ID mapping for POS/ERP sync.
Idempotency required
All write endpoints (create, update, delete) require an X-Idempotency-Key header. Use a unique key per request (e.g. your SKU or UUID). Safe to retry on network errors.
Create a product
POST /dev/store/productsDefault status is DISABLED
Products created via API default to DISABLED unless you explicitly set "status": "ACTIVE". This prevents incomplete products from appearing in your shop.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Product name |
price | integer | Yes | Price in centimes. 50000 = 500.00 MRU |
description | string | No | Product description |
externalId | string | No | SKU or external system ID (max 256 chars). Used for inventory sync and CSV matching. |
categoryId | string | No | Shop category ID |
quantity | integer | No | Stock quantity. null = unlimited stock. |
status | string | No | ACTIVE or DISABLED. Default: DISABLED. |
isFlexiblePrice | boolean | No | Allow customer to choose price |
costBasis | integer | No | Cost basis in centimes (for MARGIN revenue model) |
images | array | No | [{ url, sortOrder? }] -- image URLs with optional sort order |
variants | array | No | Product variants (see below) |
options | array | No | Product options / extras (see below) |
preparationTime | object | No | { value, unit } where unit is minutes, hours, or days |
Variant object
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Variant type: COLOR, SIZE, or custom |
label | string | Yes | Display label, e.g. "Rouge", "XL" |
value | string | No | Value code, e.g. #FF0000 |
priceAdjustment | integer | No | Price delta in centimes. Can be negative. |
quantity | integer | No | Per-variant stock. null = unlimited. |
Option object
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Option name, e.g. "Sauce piquante" |
priceAdjustment | integer | Yes | Additional price in centimes |
curl -X POST https://api.elebne.ai/api/v1/dev/store/products \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: create-thieb-001" \
-d '{
"name": "Thiéboudienne Royale",
"price": 50000,
"description": "Le plat national mauritanien, poisson frais et légumes",
"quantity": 20,
"externalId": "THIEB-001",
"status": "ACTIVE",
"variants": [
{ "type": "SIZE", "label": "Normal", "priceAdjustment": 0, "quantity": 15 },
{ "type": "SIZE", "label": "Grand", "priceAdjustment": 10000, "quantity": 5 }
],
"options": [
{ "name": "Sauce piquante", "priceAdjustment": 500 }
],
"preparationTime": { "value": 30, "unit": "minutes" }
}'const response = await fetch('https://api.elebne.ai/api/v1/dev/store/products', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'create-thieb-001',
},
body: JSON.stringify({
name: 'Thiéboudienne Royale',
price: 50000,
description: 'Le plat national mauritanien, poisson frais et légumes',
quantity: 20,
externalId: 'THIEB-001',
status: 'ACTIVE',
variants: [
{ type: 'SIZE', label: 'Normal', priceAdjustment: 0, quantity: 15 },
{ type: 'SIZE', label: 'Grand', priceAdjustment: 10000, quantity: 5 },
],
options: [
{ name: 'Sauce piquante', priceAdjustment: 500 },
],
preparationTime: { value: 30, unit: 'minutes' },
}),
});
const { data } = await response.json();
console.log(data.id); // "6612f..."
console.log(data.status); // "ACTIVE"import requests
response = requests.post(
'https://api.elebne.ai/api/v1/dev/store/products',
headers={
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'create-thieb-001',
},
json={
'name': 'Thiéboudienne Royale',
'price': 50000,
'description': 'Le plat national mauritanien, poisson frais et légumes',
'quantity': 20,
'externalId': 'THIEB-001',
'status': 'ACTIVE',
'variants': [
{'type': 'SIZE', 'label': 'Normal', 'priceAdjustment': 0, 'quantity': 15},
{'type': 'SIZE', 'label': 'Grand', 'priceAdjustment': 10000, 'quantity': 5},
],
'options': [
{'name': 'Sauce piquante', 'priceAdjustment': 500},
],
'preparationTime': {'value': 30, 'unit': 'minutes'},
},
)
data = response.json()['data']
print(data['id']) # "6612f..."
print(data['status']) # "ACTIVE"Get a product
Retrieve a single product by its MongoDB ID or externalId (SKU). The endpoint resolves both.
GET /dev/store/products/{id}# By MongoDB ID
curl https://api.elebne.ai/api/v1/dev/store/products/6612f1a2b3c4d5e6f7890123 \
-H "Authorization: Bearer pk_test_YOUR_KEY"
# By external ID (SKU)
curl https://api.elebne.ai/api/v1/dev/store/products/THIEB-001 \
-H "Authorization: Bearer pk_test_YOUR_KEY"const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
{
headers: { 'Authorization': 'Bearer pk_test_YOUR_KEY' },
}
);
const { data } = await response.json();
console.log(data.name); // "Thiéboudienne Royale"
console.log(data.price); // 50000
console.log(data.quantity); // 20response = requests.get(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
headers={'Authorization': 'Bearer pk_test_YOUR_KEY'},
)
data = response.json()['data']
print(data['name']) # "Thiéboudienne Royale"
print(data['price']) # 50000
print(data['quantity']) # 20List products
Paginated list with search, status filter, and sorting.
GET /dev/store/products?search=thieb&status=ACTIVE&sortBy=newest&page=1| Parameter | Type | Default | Description |
|---|---|---|---|
search | string | -- | Full-text search on product name |
status | string | all | ACTIVE, DISABLED, or ALL |
sortBy | string | -- | Sort order: newest, oldest, price_asc, price_desc |
categoryId | string | -- | Filter by shop category |
page | integer | 1 | Page number |
curl "https://api.elebne.ai/api/v1/dev/store/products?status=ACTIVE&sortBy=newest&page=1" \
-H "Authorization: Bearer pk_test_YOUR_KEY"const params = new URLSearchParams({
status: 'ACTIVE',
sortBy: 'newest',
page: '1',
});
const response = await fetch(
`https://api.elebne.ai/api/v1/dev/store/products?${params}`,
{
headers: { 'Authorization': 'Bearer pk_test_YOUR_KEY' },
}
);
const { items, total, hasMore } = await response.json();
console.log(`${total} products found`);response = requests.get(
'https://api.elebne.ai/api/v1/dev/store/products',
headers={'Authorization': 'Bearer pk_test_YOUR_KEY'},
params={'status': 'ACTIVE', 'sortBy': 'newest', 'page': 1},
)
result = response.json()
print(f"{result['total']} products found")Update a product
Full replace with PUT or partial update with PATCH. Both accept the same body -- PATCH only changes the fields you send.
PUT /dev/store/products/{id}
PATCH /dev/store/products/{id}curl -X PATCH https://api.elebne.ai/api/v1/dev/store/products/THIEB-001 \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: update-thieb-price" \
-d '{
"price": 55000,
"description": "Thiéboudienne premium avec poisson frais du jour"
}'const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
{
method: 'PATCH',
headers: {
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'update-thieb-price',
},
body: JSON.stringify({
price: 55000,
description: 'Thiéboudienne premium avec poisson frais du jour',
}),
}
);
const { data } = await response.json();
console.log(data.price); // 55000response = requests.patch(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
headers={
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'update-thieb-price',
},
json={
'price': 55000,
'description': 'Thiéboudienne premium avec poisson frais du jour',
},
)
data = response.json()['data']
print(data['price']) # 55000Delete a product
Soft-deletes the product. It will no longer appear in your shop or API responses.
DELETE /dev/store/products/{id}curl -X DELETE https://api.elebne.ai/api/v1/dev/store/products/THIEB-001 \
-H "Authorization: Bearer sk_test_YOUR_KEY"const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
{
method: 'DELETE',
headers: { 'Authorization': 'Bearer sk_test_YOUR_KEY' },
}
);
const { data } = await response.json();
console.log(data.deleted); // trueresponse = requests.delete(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001',
headers={'Authorization': 'Bearer sk_test_YOUR_KEY'},
)
data = response.json()['data']
print(data['deleted']) # TrueUpload a product image
Upload an image file (JPEG, PNG, or WebP, max 5 MB) as multipart form data.
POST /dev/store/products/{id}/imagescurl -X POST https://api.elebne.ai/api/v1/dev/store/products/THIEB-001/images \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-F "[email protected]"const formData = new FormData();
formData.append('file', fs.createReadStream('thieboudienne.jpg'));
const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001/images',
{
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_KEY',
},
body: formData,
}
);
const { data } = await response.json();
console.log(data.url); // "https://..."
console.log(data.imageId); // "6612f..."with open('thieboudienne.jpg', 'rb') as f:
response = requests.post(
'https://api.elebne.ai/api/v1/dev/store/products/THIEB-001/images',
headers={'Authorization': 'Bearer sk_test_YOUR_KEY'},
files={'file': ('thieboudienne.jpg', f, 'image/jpeg')},
)
data = response.json()['data']
print(data['url']) # "https://..."
print(data['imageId']) # "6612f..."Error responses
| Error code | HTTP | Description |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 400 | Missing X-Idempotency-Key header |
NO_FILE | 400 | No file uploaded for image endpoint |
INVALID_FILE_TYPE | 400 | File type not JPEG, PNG, or WebP |
FILE_TOO_LARGE | 400 | File exceeds 5 MB |
PRODUCT_NOT_FOUND | 404 | Product not found by ID or externalId |
SHOP_NOT_FOUND | 404 | Shop not found for this business |
Next steps
- Inventory -- update stock levels for individual products or in bulk
- CSV Import -- bulk import products from a spreadsheet
- Webhook Events -- get notified on stock changes
Was this page helpful?