La API importa el mismo catálogo que el portal (referencias, códigos de tienda, los precios de una tarifa, las respuestas del ERP a los pedidos, las cargas de las furgonetas, el lineal esperado, las visitas, tus ventas, el calendario de promociones y tus márgenes) en cualquier formato que el portal lea, como importaciones: se lee el archivo, se deducen sus columnas y la importación lo aplica en segundo plano, con un resultado para cada fila. Cada importación queda en el historial y en la página de actividad, con la clave que la envió. Las columnas de los tipos del catálogo están en formatos CSV del módulo de campo, y las de los demás, en su artículo: lineal esperado, visitas, ventas, calendario y márgenes.
Enviar un archivo
Envía el archivo como cuerpo de POST /api/portal/v1/imports/{kind}/files: CSV u otro texto delimitado (UTF-8, UTF-16 o Windows-1252), un libro de Excel (.xlsx, su primera hoja visible) o JSON (una lista de objetos), de hasta 4,5 MB y 5.000 filas (25.000 los de códigos de tienda). Ponle nombre en una cabecera Content-Disposition (attachment; filename="catalogue.xlsx"), nunca en la dirección: su extensión dice el formato y la importación lo conserva. El archivo de una tarifa indica su tarifa en priceListId.
El archivo puede llegar en un ZIP o un gzip, que abrimos en memoria para leer el que lleva dentro (archivos ZIP, gzip y con contraseña). Un ZIP con varios archivos que leemos necesita el que hay que importar en una cabecera X-Archive-Entry (su ruta dentro del ZIP, su nombre o un patrón con * y ?, y se toma el más reciente que coincida); sin ella, la respuesta es 422 archiveChoice, con los archivos en entries. Un ZIP con contraseña la necesita en una cabecera X-Archive-Password: se usa una vez y no se guarda, registra ni devuelve en ningún sitio; sin ella, 422 archivePassword, y con otra, archivePasswordWrong. Las dos cabeceras van en UTF-8 con codificación de porcentaje, como la escribe encodeURIComponent, y nunca en la dirección.
La respuesta es la importación y la propuesta de sus columnas: para cada campo, la columna elegida, por qué y si otra columna encaja casi igual de bien (doubtful). La importación empieza en el acto si el archivo lleva nuestra propia cabecera, si la cuenta guardó una correspondencia para exactamente esas columnas, con profileId (una correspondencia guardada, que se rechaza si las columnas no son aquellas para las que se guardó) o con mapping, las columnas que tú eliges, como en mapping=code:0,name:1,ean:4 (las columnas se cuentan desde la 0). Si no, la importación espera 24 horas a que se confirmen sus columnas; después caduca y su archivo se borra. Un archivo que ningún lector entiende responde 422 con su motivo, y no se guarda nada.
Enviar un archivo grande
Un archivo de más de 4,5 MB, hasta 20 MB, va directo al almacenamiento privado. POST /api/portal/v1/imports/uploads con su tipo, su nombre y su tamaño responde la importación, que espera su archivo (uploading), y una dirección: envía allí el archivo con un solo PUT, el contentType que da la respuesta y no más bytes de los declarados. Después, POST /api/portal/v1/imports/uploads/{id}/finish lo lee como se lee un archivo enviado en el cuerpo y responde la importación: esperando a que se confirmen sus columnas, en marcha, o fallida con el motivo de los lectores en error. Antes de que llegue el archivo, la finalización responde 422 con fileMissing. Un ZIP con varios archivos o con contraseña responde 422 (archiveChoice, archivePassword o archivePasswordWrong) y su importación sigue esperando: vuelve a llamar a la finalización con entry o password en su cuerpo, como mucho 10 finalizaciones por subida. Una subida cuyo archivo no llega nunca caduca con su importación.
Confirmar las columnas
Una importación en estado mapping espera a POST /api/portal/v1/imports/jobs/{id}/confirm con la columna de cada campo, y null para un campo que el archivo no trae:
{ "mapping": { "code": 0, "name": 1, "ean": 4, "case_size": null } }
Cada campo obligatorio necesita una columna y ninguna columna llena dos campos. La importación queda en cola y se ejecuta con tu clave. Una clave nunca guarda la correspondencia de la cuenta: la guarda un miembro, en la página de la importación en el portal, donde un miembro también puede confirmarla; una integración que siempre envía las mismas columnas envía mapping con cada archivo. Una importación que ya no espera responde 409. Cuando una clave se revoca, o pierde catalogue:write, se cancelan los archivos que envió y aún esperan a que se confirmen sus columnas.
Enviar filas
Las filas que ya están en nuestros campos van en JSON a POST /api/portal/v1/imports/{kind}/rows, hasta 5.000:
{ "rows": [{ "code": "AX-9", "name": "Agua X", "case_size": 6, "ean": "8412345678905" }] }
Las claves de cada fila son las columnas del tipo tal como las nombra la cabecera del CSV, y sus valores, texto o números; un valor vacío o null conserva lo que tiene MercaMap y - lo borra, como en el CSV. Una clave que el tipo no tiene, o una fila sin una obligatoria, responde 400 indicando la fila, y no se guarda nada. La importación empieza en el acto, y el resultado de cada fila se numera por su lugar en la lista. Las ventas (sales) piden dataRights=true (Ventas y lineal).
Seguir una importación
GET /api/portal/v1/imports/jobs/{id} responde la importación: su estado (mapping, queued, running, done, failed, cancelled o expired), el nombre, el tamaño y la cabecera de su archivo, cada campo con su columna y, cuando termina, sus recuentos; una importación fallida dice por qué en error. GET /api/portal/v1/imports/jobs/{id}/results pagina en qué quedó cada fila (added, updated, removed o refused, con el motivo), siguiendo next como cursor; los resultados se conservan 90 días después de que termine la importación. En lugar de preguntar, una dirección puede recibir import.ended (webhooks), que lleva el identificador, el estado y los recuentos de la importación, nunca una celda.
Una importación se lee solo con tu cuenta y su identificador: la de otra cuenta responde 404.
Permisos, límites y reintentos
Enviar y confirmar necesitan catalogue:write; leer las importaciones, sus resultados y la propuesta de columnas necesita catalogue:read (claves y permisos). Cada archivo o conjunto de filas cuenta como una de las 50 importaciones diarias de la cuenta y una de las 30 escrituras por minuto de la clave; un archivo dentro de un ZIP o un gzip cuenta además, rechazado o no, como uno de los 200 archivos comprimidos diarios de la cuenta. El mismo archivo o las mismas filas enviados otra vez mientras su importación no ha terminado responden esa importación con duplicate: true, sin coste para las importaciones del día, así que una petición agotada puede enviarse de nuevo. POST /api/portal/v1/imports/{kind} sigue importando nuestro propio CSV dentro de la petición, como antes.
Asistentes de IA
Un asistente de IA conectado por MCP puede leer una importación, sus resultados y guess_import_columns, una prueba que lee un archivo y responde las columnas que propone sin guardar nada (200 al día por cuenta). Nunca envía ni confirma una importación: eso queda para un miembro en el portal o para una clave (conectar un asistente de IA).