ElebneElebneDocs
Store API

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/template
curl 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.

ColumnRequiredDescription
nameYesProduct name (max 200 characters)
priceYesPrice in MRU (e.g., 500.00). Converted to centimes internally.
descriptionNoProduct description (max 2,000 characters)
skuNoExternal ID / SKU (max 256 characters). Used to match existing products for updates.
quantityNoStock quantity
image_urlNoProduct 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/preview

Upload 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 sku column are matched against existing products by externalId
  • Matched products show as update with a list of changed fields
  • Unmatched products show as create
  • Rows with validation errors show as error with 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/confirm

Request body

FieldTypeRequiredDescription
importIdstringYesThe 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 codeHTTPDescription
NO_FILE400No file uploaded
INVALID_FILE_TYPE400File is not a CSV
FILE_TOO_LARGE400File exceeds 5 MB
IMPORT_NOT_FOUND404/410Import ID not found or expired

Next steps

Was this page helpful?

On this page