CSV Import
Bulk import products from CSV files. Three-step flow with preview and confirmation.
CSV Import
Import products in bulk from a CSV file. The import follows a three-step flow: download the template, upload for preview, then confirm.
Import flow
1. GET template ──> 2. POST preview ──> 3. POST confirm
(review changes) (execute import)Step 1: Get the template
Download a CSV template with the correct column headers for your shop.
GET /dev/store/products/import/templatecurl https://api.elebne.ai/api/v1/dev/store/products/import/template \
-H "Authorization: Bearer pk_test_YOUR_KEY"const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/import/template',
{
headers: { 'Authorization': 'Bearer pk_test_YOUR_KEY' },
}
);
const { data } = await response.json();
fs.writeFileSync('products-template.csv', data.csv);
console.log(`Template saved: ${data.filename}`);response = requests.get(
'https://api.elebne.ai/api/v1/dev/store/products/import/template',
headers={'Authorization': 'Bearer pk_test_YOUR_KEY'},
)
data = response.json()['data']
with open('products-template.csv', 'w') as f:
f.write(data['csv'])
print(f"Template saved: {data['filename']}")Response
{
"success": true,
"data": {
"csv": "name,price,description,sku,quantity,image_url\n",
"filename": "products-template.csv"
}
}CSV columns
Prices in MRU, not centimes
CSV prices are in MRU (e.g., 500.00 for 500 MRU). The API converts to centimes internally. This is different from the JSON API which always uses centimes.
| Column | Required | Description |
|---|---|---|
name | Yes | Product name (max 200 characters) |
price | Yes | Price in MRU (e.g., 500.00). Converted to centimes internally. |
description | No | Product description (max 2,000 characters) |
sku | No | External ID / SKU (max 256 characters). Used to match existing products for updates. |
quantity | No | Stock quantity |
image_url | No | Product image URL (http or https only) |
Example CSV
name,price,description,sku,quantity,image_url
Thiéboudienne Royale,500.00,Le plat national mauritanien,THIEB-001,20,https://example.com/thieb.jpg
Yassa Poulet,350.00,Poulet mariné aux oignons,YASSA-002,15,
Mafé Boeuf,400.00,Ragoût de boeuf à la pâte d'arachide,MAFE-003,10,Step 2: Preview the import
Upload your CSV file and review the changes before applying them. The preview shows which products will be created, updated, or skipped.
POST /dev/store/products/import/previewUpload the CSV as multipart form data (field name: file). Max file size: 5 MB. Max rows: 1,000.
curl -X POST https://api.elebne.ai/api/v1/dev/store/products/import/preview \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-F "[email protected]"const formData = new FormData();
formData.append('file', fs.createReadStream('products.csv'));
const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/import/preview',
{
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_KEY',
},
body: formData,
}
);
const { data } = await response.json();
console.log(`Import ID: ${data.importId}`);
console.log(`Creates: ${data.creates}, Updates: ${data.updates}, Errors: ${data.errors}`);with open('products.csv', 'rb') as f:
response = requests.post(
'https://api.elebne.ai/api/v1/dev/store/products/import/preview',
headers={'Authorization': 'Bearer sk_test_YOUR_KEY'},
files={'file': ('products.csv', f, 'text/csv')},
)
data = response.json()['data']
print(f"Import ID: {data['importId']}")
print(f"Creates: {data['creates']}, Updates: {data['updates']}, Errors: {data['errors']}")Response
{
"success": true,
"data": {
"importId": "imp_a1b2c3d4e5f6",
"creates": 2,
"updates": 1,
"unchanged": 0,
"errors": 0,
"rows": [
{ "action": "create", "sku": "THIEB-001", "name": "Thiéboudienne Royale" },
{ "action": "create", "sku": "YASSA-002", "name": "Yassa Poulet" },
{ "action": "update", "sku": "MAFE-003", "name": "Mafé Boeuf", "productId": "6612f...", "changes": ["price", "quantity"] }
]
}
}How matching works:
- Products with a
skucolumn are matched against existing products byexternalId - Matched products show as
updatewith a list of changed fields - Unmatched products show as
create - Rows with validation errors show as
errorwith field and message
Preview expires in 15 minutes
The import preview is stored temporarily and expires after 15 minutes. You must confirm within that window.
Step 3: Confirm the import
Execute the import using the importId from the preview step.
POST /dev/store/products/import/confirmRequest body
| Field | Type | Required | Description |
|---|---|---|---|
importId | string | Yes | The import ID from the preview response |
curl -X POST https://api.elebne.ai/api/v1/dev/store/products/import/confirm \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "importId": "imp_a1b2c3d4e5f6" }'const response = await fetch(
'https://api.elebne.ai/api/v1/dev/store/products/import/confirm',
{
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ importId: 'imp_a1b2c3d4e5f6' }),
}
);
const { data } = await response.json();
console.log(`Created: ${data.created}, Updated: ${data.updated}, Failed: ${data.failed}`);response = requests.post(
'https://api.elebne.ai/api/v1/dev/store/products/import/confirm',
headers={
'Authorization': 'Bearer sk_test_YOUR_KEY',
'Content-Type': 'application/json',
},
json={'importId': 'imp_a1b2c3d4e5f6'},
)
data = response.json()['data']
print(f"Created: {data['created']}, Updated: {data['updated']}, Failed: {data['failed']}")Response
{
"success": true,
"data": {
"created": 2,
"updated": 1,
"failed": 0,
"errors": []
}
}Error responses
| Error code | HTTP | Description |
|---|---|---|
NO_FILE | 400 | No file uploaded |
INVALID_FILE_TYPE | 400 | File is not a CSV |
FILE_TOO_LARGE | 400 | File exceeds 5 MB |
IMPORT_NOT_FOUND | 404/410 | Import ID not found or expired |
Next steps
- Products -- manage imported products individually
- Inventory -- update stock levels after import
- Webhook Events -- get notified on stock changes
Was this page helpful?