Operaciones masivas — rollback, Flow Editor, IA y categorización

Cuatro familias de endpoints relacionadas por un mismo hilo: todas operan sobre muchos productos a la vez y todas pueden terminar en éxito parcial. Cubre el registro y rollback de operaciones masivas, el editor visual de reglas (Flow Editor), las transformaciones asistidas por IA, y la categorización de productos para un marketplace.

207 Multi-Status aparece en varios de estos endpoints

POST /products/marketplace-categories, la rama síncrona de importación CSV, POST /products/bulk/flow/execute y POST /products/bulk/ai/transform pueden responder 207 Multi-Status cuando algunos elementos de la operación tuvieron éxito y otros fallaron. Si tu cliente solo distingue "2xx = todo bien", vas a tratar un resultado parcialmente fallido como éxito total — revisa siempre el detalle por elemento de la respuesta.


Registro de operaciones masivas

Toda operación masiva que modifica productos (CSV, Flow Editor, IA) queda registrada como una "bulk operation" — con eso se construye el historial y se habilita el rollback.

Lista el historial de operaciones masivas del tenant.

pagenumber

Número de página.

limitnumber

Elementos por página.

typestring

Filtra por tipo de operación (csv-import, flow, ai-transform, etc.).

statusstring

Filtra por estado de la operación.

sortBystring

Campo de ordenamiento.

sortOrderstring

asc o desc.

200
{
  "operations": [],
  "totalCount": 0,
  "page": 0,
  "limit": 20
}

Permiso requerido: products:read y products:update (ambos, apilados)

Este endpoint exige dos permisos a la vez, no uno solo

A diferencia de la mayoría de los endpoints de lectura de esta API, GET /products/bulk/operations y GET /products/bulk/operations/{id} exigen tanto products:read como products:update simultáneamente — una API key con solo lectura no puede listar el historial de operaciones masivas. Esto es una inconsistencia de RBAC confirmada en el código, no necesariamente el diseño intencional; documentamos el comportamiento real. El endpoint de rollback (más abajo), en cambio, exige únicamente products:update.


Detalle de una operación masiva

Devuelve el detalle de una operación masiva, incluyendo si todavía se puede revertir.

idstringrequired

ID de la operación (path).

200
{
  "id": "bulkop_65f3a1b2c4d5e6f7a8b9c0f1",
  "type": "flow",
  "status": "completed",
  "canRollback": true
}
404
{ "code": "not-found", "message": "Bulk operation not found" }

Permiso requerido: products:read y products:update (ambos, apilados)

Shape parcialmente confirmado

Se confirmó que la respuesta trae la operación completa más el campo canRollback. El resto de los campos del ejemplo (id, type, status) son orientativos según el resto de la familia de bulk operations — confírmalos contra tu propia respuesta.


Revertir una operación masiva

Revierte los cambios aplicados por una operación masiva previa, cuando todavía es reversible.

idstringrequired

ID de la operación a revertir (path).

{}
200
{
  "success": true,
  "restoredCount": 298,
  "failedSkus": ["PAN-AZUL-32"]
}
400
{ "code": "rollback-failed", "message": "This operation can no longer be rolled back" }

Permiso requerido: products:update

Tip

Revisa canRollback en GET /products/bulk/operations/{id} antes de intentar el rollback — una operación puede dejar de ser reversible (por ejemplo, si otra operación posterior ya modificó los mismos SKUs).


Flow Editor (edición masiva visual/sin código)

El Flow Editor deja definir reglas de edición masiva sin escribir código (un FlowDefinition estructurado). Los tres endpoints siguen el patrón validar → previsualizar (dry-run) → ejecutar.

Validar una definición de flujo

Valida la estructura de un FlowDefinition sin ejecutarlo ni tocar ningún producto.

flowobjectrequired

Definición del flujo a validar.

{
  "flow": {
    "filter": { "status": "draft" },
    "actions": [{ "type": "set-field", "field": "status", "value": "active" }]
  }
}
200
{
  "valid": true,
  "errors": [],
  "warnings": []
}
400
{ "code": "bad-request", "message": "Missing required field: flow" }

Permiso requerido: products:update

Previsualizar un flujo (dry-run)

Simula la ejecución de un flujo sobre una muestra de productos, sin persistir cambios.

flowobjectrequired

Definición del flujo a previsualizar.

limitnumber

Máximo de productos a considerar en la simulación. Default: 100.

sampleSizenumber

Cuántos cambios de ejemplo devolver. Default: 10.

{
  "flow": {
    "filter": { "status": "draft" },
    "actions": [{ "type": "set-field", "field": "status", "value": "active" }]
  },
  "limit": 100,
  "sampleSize": 10
}
200
{
  "affectedCount": 340,
  "sampleChanges": [
    { "sku": "CAM-ROJO-M", "before": { "status": "draft" }, "after": { "status": "active" } }
  ]
}

Permiso requerido: products:update

Ejecutar un flujo

Ejecuta un FlowDefinition sobre los productos que resuelva, aplicando los cambios. Puede correr en modo dryRun también.

flowobjectrequired

Definición del flujo a ejecutar.

dryRunboolean

Si es true, simula sin persistir. Default: false.

limitnumber

Límite de productos a procesar.

batchSizenumber

Tamaño de lote de procesamiento. Default: 100.

operationNamestring

Nombre visible en el historial de bulk operations.

{
  "flow": {
    "filter": { "status": "draft" },
    "actions": [{ "type": "set-field", "field": "status", "value": "active" }]
  },
  "operationName": "Activar borradores de temporada"
}
200Éxito total.
{
  "totalProcessed": 340,
  "succeeded": 340,
  "failed": 0,
  "errors": []
}
207Éxito parcial — revisa errors[].
{
  "totalProcessed": 340,
  "succeeded": 335,
  "failed": 5,
  "errors": [
    { "sku": "PAN-AZUL-32", "message": "validation-error" }
  ]
}

Permiso requerido: products:update

Shape del resultado orientativo

Se confirmó que este endpoint puede responder 200 (éxito total) o 207 (parcial). Los nombres exactos de los campos del cuerpo (totalProcessed, succeeded, failed, errors) no se verificaron línea por línea — úsalos como guía y confírmalos contra tu propia ejecución. La operación queda registrada y es consultable/reversible vía el registro de operaciones masivas descrito arriba.

curl -X POST https://api.fenicia.io/products/bulk/flow/execute \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "flow": {
      "filter": { "status": "draft" },
      "actions": [{ "type": "set-field", "field": "status", "value": "active" }]
    },
    "operationName": "Activar borradores de temporada"
  }'

AI Mutations (transformación asistida por IA)

Aplica un prompt de lenguaje natural sobre un conjunto de productos para transformar campos específicos (por ejemplo, reescribir descripciones o generar bullets de marketplace).

Transformar productos con IA

Aplica un prompt de IA sobre un lote de productos para transformar campos específicos.

promptstringrequired

Instrucción en lenguaje natural para el modelo.

productsobject[]required

Productos a transformar, cada uno con al menos su sku.

targetFieldsstring[]

Campos que el modelo debe modificar.

modelstring

claude o gpt. Default: claude.

options.maxTokensPerRequestnumber

Default: 4096.

options.batchSizenumber

Default: 10.

options.temperaturenumber

Default: 0.3.

options.stopOnErrorboolean

Detiene el lote completo ante el primer error. Default: false.

{
  "prompt": "Reescribe la descripción con un tono más comercial, máximo 200 caracteres",
  "products": [{ "sku": "CAM-ROJO-M" }, { "sku": "PAN-AZUL-32" }],
  "targetFields": ["description"],
  "model": "claude",
  "options": { "batchSize": 10, "temperature": 0.3 }
}
200Éxito total.
{
  "totalProcessed": 25,
  "succeeded": 25,
  "failed": 0,
  "results": [
    { "sku": "CAM-ROJO-M", "success": true, "changedFields": ["description"] }
  ]
}
207Éxito parcial.
{
  "totalProcessed": 25,
  "succeeded": 22,
  "failed": 3,
  "results": [
    { "sku": "PAN-AZUL-32", "success": false, "error": "MODEL_ERROR" }
  ]
}
429
{ "code": "budget-exceeded", "message": "Daily AI token budget exceeded for this tenant" }

Permiso requerido: products:update

Shape del resultado orientativo

El wrapper de nivel superior (200/207 según éxito parcial) está confirmado; los nombres exactos de campos como results[], changedFields no se verificaron línea por línea — trátalos como orientativos.

El presupuesto de IA es diario y por tenant

Antes de que tu prompt se procese, el servidor valida contra un presupuesto diario de tokens del tenant. Si ya lo agotaste, la petición falla con 429 budget-exceeded sin siquiera intentar la transformación — consulta GET /products/bulk/ai/budget antes de lanzar un lote grande.

Consultar el presupuesto de IA del día

Devuelve el consumo de tokens de IA del tenant para el día en curso.

200
{
  "tenantId": "65f2a0b1c4d5e6f7a8b9c0aa",
  "date": "2026-07-21",
  "used": 812400,
  "limit": 2000000,
  "remaining": 1187600,
  "percentUsed": 40.62,
  "isOverLimit": false,
  "isWarning": false
}

Permiso requerido: products:read

Consultar el historial de uso de IA

Devuelve el historial de consumo de tokens de IA de los últimos días.

daysnumber

Días hacia atrás a incluir. Default: 30.

200
{
  "history": [
    { "date": "2026-07-20", "tokensUsed": 640200, "requestCount": 48 }
  ]
}

Permiso requerido: products:read

Shape de cada entrada de history[] orientativo

Se confirmó el wrapper { history: TokenUsage[] }; los campos exactos de cada TokenUsage (aquí date, tokensUsed, requestCount) no se verificaron línea por línea.


Categorización de marketplace

Asigna la categoría de un marketplace específico (MercadoLibre, T1/Sears/Sanborns, Amazon, Walmart, etc.) a un lote de SKUs. El resultado se guarda como un metafield del producto — no crea un campo nuevo en el modelo de datos.

Asigna una categoría de marketplace a un lote de SKUs para un canal específico.

skusstring[]required

SKUs a categorizar. Máximo 100 por petición.

channelIdstringrequired

ID del canal/marketplace destino.

category.idstringrequired

ID de la categoría en el marketplace. Máximo 100 caracteres.

category.namestringrequired

Nombre de la categoría. Máximo 255 caracteres.

category.pathstring[]

Ruta jerárquica de la categoría. Máximo 10 niveles.

{
  "skus": ["CAM-ROJO-M", "CAM-AZUL-L", "CAM-VERDE-S"],
  "channelId": "65f2a0b1c4d5e6f7a8b9c0ab",
  "category": {
    "id": "MLM1055",
    "name": "Camisas",
    "path": ["Ropa", "Hombre", "Camisas"]
  }
}
200Éxito total — todos los SKUs se categorizaron.
{
  "success": true,
  "total": 3,
  "categorized": 3,
  "failed": 0,
  "channelType": "mercadolibre",
  "siteId": "MLM",
  "category": { "id": "MLM1055", "name": "Camisas", "path": ["Ropa", "Hombre", "Camisas"] },
  "results": [
    { "sku": "CAM-ROJO-M", "success": true },
    { "sku": "CAM-AZUL-L", "success": true },
    { "sku": "CAM-VERDE-S", "success": true }
  ]
}
207Éxito parcial — revisa results[] para saber qué SKU falló y por qué.
{
  "success": false,
  "total": 3,
  "categorized": 2,
  "failed": 1,
  "channelType": "mercadolibre",
  "siteId": "MLM",
  "category": { "id": "MLM1055", "name": "Camisas", "path": ["Ropa", "Hombre", "Camisas"] },
  "results": [
    { "sku": "CAM-ROJO-M", "success": true },
    { "sku": "CAM-AZUL-L", "success": true },
    { "sku": "CAM-VERDE-S", "success": false, "error": "not-found" }
  ]
}
400
{ "code": "too-many-skus", "message": "skus exceeds the maximum of 100" }

Permiso requerido: products:update

Dónde queda guardado el resultado

La categoría asignada se persiste en product.metafields[], bajo namespace: 'mkt_category' y key: '{channelType}:{siteId}' — no es un campo de primer nivel del producto. El siteId depende del tipo de canal: MercadoLibre usa site_id (default MLM), T1/Sears/Sanborns usan salesChannel/marketplace (default SR), Amazon usa marketplace_id (default MX) y Walmart usa country (default MX).

curl -X POST https://api.fenicia.io/products/marketplace-categories \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "skus": ["CAM-ROJO-M", "CAM-AZUL-L", "CAM-VERDE-S"],
    "channelId": "65f2a0b1c4d5e6f7a8b9c0ab",
    "category": {
      "id": "MLM1055",
      "name": "Camisas",
      "path": ["Ropa", "Hombre", "Camisas"]
    }
  }'

Errores

CódigoStatusDescripción
not-found404No existe una operación masiva con ese id, o el SKU no existe (categorización).
rollback-failed400La operación ya no se puede revertir (expiró la ventana de rollback o ya fue revertida).
bad-request400Falta un campo requerido (flow en Flow Editor, prompt/products sin SKU en AI Mutations).
budget-exceeded429Se agotó el presupuesto diario de tokens de IA del tenant.
validation-errorCódigo genérico usado dentro de errors[]/results[] para fallas de validación por elemento.
invalid-skus400El campo skus es inválido o está vacío.
too-many-skus400skus excede el máximo de 100 por petición.
invalid-sku-values400Uno o más valores dentro de skus no son válidos.
invalid-channel-id400channelId es inválido o no corresponde a un canal del tenant.
invalid-category400El objeto category es inválido o incompleto.
invalid-category-path400category.path tiene un formato inválido.
category-id-too-long400category.id excede 100 caracteres.
category-name-too-long400category.name excede 255 caracteres.
category-path-too-deep400category.path excede 10 niveles.
auth:invalid_token401La API key es inválida o fue revocada.
auth:permission_denied403La API key no tiene el permiso requerido (products:read, products:update, o ambos según el endpoint).

Consulta el catálogo completo de errores para el resto de los códigos posibles.

Siguientes pasos