Product import
Upload a product file, follow the operation, and fix the rejected rows.
Upload a CSV with your catalog and get an operation back right away. The file is processed in the background; poll the operation until it finishes and download the report if any rows were rejected.
Requirements
- An Admin token in
ECOMIQ_TOKEN. See Quickstart. - The
catalog:createandcatalog:updatepermissions to import, andoperation:readto follow the operation.
Build the file
Each row is a variant. Rows with the same Código producto make one product.
Código producto,Producto,SKU,Precio,Option 1 name,Option 1 value,Option 2 name,Option 2 value,Categoría
POL-001,Polo de algodón,POL-001-AZ-S,79.00,Size,S,Color,Blue,Ropa > Polos
POL-001,Polo de algodón,POL-001-AZ-M,79.00,Size,M,Color,Blue,Ropa > Polos
BOT-001,Botella,BOT-001-500,39.00,Capacity,500 ml,,,Hogar > Botellas| Column | Required | What it holds |
|---|---|---|
Código producto | To create | Your code. It creates the product or updates the one that has it. |
Producto | Yes | Product name. |
SKU | Yes | Variant SKU. |
Precio | Yes | Variant price, with a decimal point. If the column is present, it is not empty. |
Option N name, Option N value | No | Name and value of a variant option, such as Size and S. |
Atributo: <name> | No | Product characteristic, such as Atributo: Material. |
Categoría | No | Name or path, such as Ropa > Polos. If it does not exist, it is created. |
Marca | No | Brand name. If it does not exist, it is created. |
Etiquetas | No | Comma separated. |
Imágenes, Imagen variante | No | Comma-separated URLs. The variant image must also be in Imágenes. |
Peso (kg), Largo (cm), Ancho (cm), Alto (cm) | No | Package size. |
ID interno | To update by id | An Ecomiq product id. The row updates that product. |
English headers, such as Product code or Price, also work.
Each row needs Código producto or ID interno. If you import as a seller,
Categoría and Marca must already exist.
To load stock, add Stock inicial (an integer, 0 or more) and Ubicación de stock, with the inventory:update permission. The location must match exactly
one active location; without it, the default one is used. An empty quantity
keeps the current stock.
Variant options
Each pair holds the name and the value in the same row, so one file can mix
products with Talla and Color and others with Capacidad.
- The template has three pairs. Add
Opción 4 nombreandOpción 4 valorand keep numbering if you need more. - A pair with both cells empty changes nothing. When updating, each complete
pair changes that option and keeps the rest: sending only
Color=Negrokeeps theTalla. - A row is rejected if a pair has only a name or only a value, or if a name repeats in the row (case-insensitive).
- All variants of a product use the same option names, in any order.
Template and export
Request the template with POST / and
{ "format": "xlsx", "template": true }. It puts the required columns first,
with lists of your categories, brands, and shipping profiles, and help on each
column.
Without template, the export has your products with ID interno,
ID interno variante, and an ID en {canal} column for each channel with
listings. Upload it again to edit; the channel columns are read-only. Both are
operations: when they finish, download their resultUrl.
In XLSX, Productos must be the first sheet and the headers the first row with
data.
Formats
| Format | Content-Type | Maximum |
|---|---|---|
| CSV | text/ | 50 MiB |
| TSV | text/ | 50 MiB |
| XLSX | application/ | 10 MiB |
| JSONL | application/ | 50 MiB |
All formats accept up to 100,000 rows. In JSONL, each line is an object with the same column names:
{"Código producto":"POL-001","Producto":"Polo de algodón","SKU":"POL-001-AZ-S","Precio":79,"Opción 1 nombre":"Talla","Opción 1 valor":"S"}Upload the file
Send the raw file as the body, with its Content-Type.
curl -X POST https://api.ecomiq.pe/api/v1/catalog/products/imports \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: text/csv" \
-H "Idempotency-Key: catalog-2026-10-04" \
--data-binary @products.csvThe API checks the file's headers and responds 202 with the operation.
Location points to /.
{
"id": "7204558912004325401",
"status": "pending",
"resource": "products",
"format": "csv",
"createdOn": "2026-10-04T15:30:00Z"
}Keep the id to follow the operation; the products are created afterward.
Idempotency-Key is optional. If the response got lost, retry with the same key
and the same file: you get the same 202 without a new operation. The key is
valid for 24 hours.
Validate without writing
Add ?validateOnly=true to the URL. The operation checks every row and
reports the errors, but does not change the catalog.
If the file can't be read, the API responds with an error and creates no operation:
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Import.InvalidFile": [
"The import source is missing required headers: sku."
]
},
"code": "Import.InvalidFile"
}| Response | When | What to do |
|---|---|---|
400 Import.InvalidFile | Required columns are missing or the file cannot be read. | Fix the header. |
400 Import.ContentType | The Content-Type is not a supported format. | Send the type that matches the file. |
400 Import.SizeLimitExceeded | The file exceeds the maximum for its format. | Split the file. |
409 Operation.ResourceBusy | A product import is already running. | Wait for it to finish or cancel it. |
422 | You reused the Idempotency-Key with another file or other options. | Use a new key. |
429 | You exceeded the upload limit. | Wait as long as Retry-After says. |
Follow the operation
Check the operation every few seconds while status is pending or
running.
curl https://api.ecomiq.pe/api/v1/operations/7204558912004325401 \
-H "Authorization: Bearer $ECOMIQ_TOKEN"In this example the file had 4 rows and one was missing Código producto:
{
"id": "7204558912004325401",
"type": "import",
"resource": "products",
"format": "csv",
"status": "completed",
"fileName": "product-import-errors.csv",
"resultUrl": null,
"errorsUrl": "https://…/product-import-errors.csv?X-Amz-Expires=300&X-Amz-Signature=…",
"error": null,
"totalRows": 4,
"processedRows": 4,
"succeededRows": 3,
"failedRows": 1,
"startedOn": "2026-10-04T15:30:02Z",
"completedOn": "2026-10-04T15:30:05Z",
"createdOn": "2026-10-04T15:30:00Z"
}status | What it means |
|---|---|
pending | Queued. |
running | Processing. processedRows goes up. |
completed | Done. Valid rows were applied, even if others failed. |
failed | The file could not be processed. Read error. |
cancelled | It was cancelled. What was already applied stays. |
Instead of polling, you can receive the
operation.finished
webhook, which carries the id and the counters. To cancel, use
POST /.
Review the rejected rows
If failedRows is greater than zero, errorsUrl holds a CSV with each rejected
row. It's a signed link that expires after 5 minutes and needs no token;
request the operation again to get a new one.
curl -o errors.csv "$ERRORS_URL"sheet,row,column,code,message
,5,,Products.ImportExternalProductIdRequired,Source row 5 requires product_id or product_code.| Column | What it holds |
|---|---|
sheet | XLSX sheet. Empty for CSV, TSV, and JSONL. |
row | File row, counting the header as row 1. |
column | Column of the error, when known. |
code, message | What failed. |
Fix those rows and upload them in a new file.
Next steps
- For up to 100 products in JSON, use
POST /: it returnsv1/ catalog/ products/ batch 200with a result per row, without an operation. - Variants and prices to adjust prices after importing.
- Inventory to load stock.
Last updated: