Carritos abandonados

Un carrito abandonado es un proceso de compra que un cliente inició en alguno de tus canales conectados pero nunca convirtió en pedido. Fenicia los agrega automáticamente para que puedas analizarlos, intentar recuperarlos o cerrarlos como no recuperables.

Ruta canónica vs. alias legacy

La ruta canónica de este dominio es /orders/abandoned-carts/*. Existe un alias /abandoned-carts/* (sin el prefijo /orders) que apunta al mismo lambda por compatibilidad, pero está deprecado desde el 1 de junio de 2026 y cada llamada genera un warning en los logs del servidor. Usa siempre /orders/abandoned-carts/* en integraciones nuevas.

El envelope de este dominio es distinto al resto de Órdenes

Todos los demás endpoints de Órdenes devuelven listas paginadas como { data: [...], meta: { pagination: {...} } }. El listado de carritos abandonados no sigue ese patrón. Su respuesta viene doble-anidada:

{
  "data": {
    "data": [{ "_id": "65f3a1b2c4d5e6f7a8b9c0d1", "totalAmount": 1250.5 }],
    "pagination": { "page": 0, "limit": 20, "total": 87, "totalPages": 5, "hasMore": true }
  }
}

Es decir, pagination es hermano de data.data, no de data a secas, y no existe un objeto meta. Esta es una inconsistencia real de la API (no un error de esta documentación) — si escribes un cliente genérico para Órdenes que asume el shape {data, meta:{pagination}}, este endpoint lo va a romper. Léelo con response.data.data y response.data.pagination.

Permisos y códigos de error: dos vocabularios distintos

Este dominio usa dos convenciones de texto diferentes que no debes confundir:

  • Los permisos (scopes de la API key) se escriben con guion: abandoned-carts:read, abandoned-carts:sync, abandoned-carts:recover, abandoned-carts:update, abandoned-carts:send-reminder.
  • Los códigos de error del catálogo se escriben con guion bajo: abandoned_carts:not_found, abandoned_carts:already_recovered, abandoned_carts:already_closed, abandoned_carts:recovery_failed.

Son dos catálogos independientes (el de permisos vive en PERMISSIONS.ABANDONED_CARTS.*, el de errores en el catálogo de errores de la librería). No es un error tipográfico de esta página: literalmente así están definidos en la API.

URL base

https://api.fenicia.io

Todos los endpoints requieren Authorization: Bearer fkapi_....


Listar carritos abandonados

Lista paginada de carritos abandonados con filtros. Requiere abandoned-carts:read.

pagenumber

Número de página (base 0).

limitnumber

Elementos por página.

statusstring

Filtra por estado del carrito.

statusGroupstring

Filtra por grupo visual de estado.

recoveryStatusstring

Filtra por estado del proceso de recuperación.

channelIdstring

Filtra por un canal específico.

channelIdsstring

Filtra por varios canales. Alias plural de channelId.

originstring

Filtra por origen/tipo de canal.

startDatestring

Fecha ISO de inicio del rango de abandono (inclusiva).

endDatestring

Fecha ISO de fin del rango de abandono (inclusiva).

minAmountnumber

Monto mínimo del carrito.

maxAmountnumber

Monto máximo del carrito.

hasBeenContactedboolean

Filtra carritos a los que ya se les envió (o no) un intento de recuperación.

sortBystring

Campo de ordenamiento.

sortOrderstring

Dirección de ordenamiento (asc | desc).

200
{
  "data": {
    "data": [
      {
        "id": "65f3a1b2c4d5e6f7a8b9c0e1",
        "channelId": "chn_shopify_01",
        "channelType": "shopify",
        "status": "open",
        "recoveryStatus": "not_contacted",
        "totalAmount": 1250.00,
        "currency": "MXN",
        "hasBeenContacted": false,
        "abandonedAt": "2026-04-10T09:15:00.000Z"
      }
    ],
    "pagination": {
      "page": 0,
      "limit": 20,
      "total": 87,
      "totalPages": 5,
      "hasMore": true
    }
  }
}
curl "https://api.fenicia.io/orders/abandoned-carts?limit=20&status=open&hasBeenContacted=false" \
  -H "Authorization: Bearer fkapi_tu_api_key"

Buscar carritos abandonados

Búsqueda de carritos abandonados por término libre. Requiere abandoned-carts:read.

qstring

Término de búsqueda.

pagenumber

Número de página (base 0).

limitnumber

Elementos por página.

200
{
  "data": {
    "data": [
      {
        "id": "65f3a1b2c4d5e6f7a8b9c0e1",
        "channelId": "chn_shopify_01",
        "channelType": "shopify",
        "status": "open",
        "recoveryStatus": "not_contacted",
        "totalAmount": 1250.00,
        "currency": "MXN",
        "hasBeenContacted": false,
        "abandonedAt": "2026-04-10T09:15:00.000Z"
      }
    ],
    "pagination": {
      "page": 0,
      "limit": 20,
      "total": 3,
      "totalPages": 1,
      "hasMore": false
    }
  }
}

Mismo envelope doble-anidado que el listado

Esta búsqueda usa la misma forma de respuesta doble-anidada descrita arriba: response.data.data es el arreglo, response.data.pagination es la paginación — no hay meta.


Contar carritos por grupo de estado

Conteo de carritos abandonados agrupados por estado visual. Requiere abandoned-carts:read.

200
{
  "data": {
    "no_action": 42,
    "with_action": 18,
    "recovered": 15,
    "not_recovered": 12,
    "all": 87
  }
}

Grupos reales: no_action, with_action, recovered, not_recovered, all

no_action son los carritos open sin ningún intento de recuperación; with_action son los contacted; not_recovered suma expired + closed; all es el total. Estos 5 nombres no coinciden con el statusGroup que aceptas como filtro en el listado — son la agrupación fija de este endpoint de conteo.


Contar carritos abandonados

Devuelve el conteo total de carritos abandonados que cumplen los filtros. Requiere abandoned-carts:read.

statusstring

Filtra por estado del carrito.

statusGroupstring

Filtra por grupo visual de estado.

recoveryStatusstring

Filtra por estado del proceso de recuperación.

channelIdstring

Filtra por canal.

originstring

Filtra por origen/tipo de canal.

startDatestring

Fecha ISO de inicio (inclusiva).

endDatestring

Fecha ISO de fin (inclusiva).

200
{ "data": { "count": 87 } }

Tip

A diferencia de lo que podrías esperar por otros endpoints de conteo simples, este acepta los mismos filtros que el listado — úsalo para mostrar un contador que coincida exactamente con una vista filtrada, sin traer todos los registros.


Estadísticas de carritos abandonados

Estadísticas agregadas de carritos abandonados dentro de un rango. Requiere abandoned-carts:read.

channelIdstring

Filtra por canal.

originstring

Filtra por origen/tipo de canal.

startDatestring

Fecha ISO de inicio (inclusiva).

endDatestring

Fecha ISO de fin (inclusiva).

200
{
  "data": {
    "totalCarts": 87,
    "totalValue": 128450.50,
    "recoveryRate": 0.18
  }
}
curl "https://api.fenicia.io/orders/abandoned-carts/stats?startDate=2026-04-01&endDate=2026-04-30" \
  -H "Authorization: Bearer fkapi_tu_api_key"

Obtener un carrito abandonado

Obtiene el detalle completo de un carrito abandonado, incluyendo sus enlaces HATEOAS. Requiere abandoned-carts:read.

idstringrequired

ID del carrito abandonado.

200
{
  "data": {
    "id": "65f3a1b2c4d5e6f7a8b9c0e1",
    "channelId": "chn_shopify_01",
    "channelType": "shopify",
    "status": "open",
    "recoveryStatus": "not_contacted",
    "totalAmount": 1250.00,
    "currency": "MXN",
    "abandonedAt": "2026-04-10T09:15:00.000Z",
    "_links": {
      "recover": "/orders/abandoned-carts/65f3a1b2c4d5e6f7a8b9c0e1/recover",
      "close": "/orders/abandoned-carts/65f3a1b2c4d5e6f7a8b9c0e1/close"
    }
  }
}
404
{ "error": { "code": "abandoned_carts:not_found", "message": "Abandoned cart not found" } }

Sincronizar carritos abandonados

Dispara una sincronización de carritos abandonados desde los canales conectados. Requiere abandoned-carts:sync.

channelIdstring

Limita la sincronización a un solo canal. Si se omite, el handler la reporta como general (ver advertencia abajo).

{ "channelId": "chn_shopify_01" }
202
{
  "data": {
    "message": "Abandoned carts sync initiated",
    "status": "processing",
    "channelId": "chn_shopify_01"
  }
}

Endpoint placeholder: no dispara sincronización real todavía

A diferencia de POST /orders/sync, el handler de este endpoint es un placeholder: acepta la petición y responde 202, pero las integraciones de canal aún no están conectadas a esta ruta — no dispara ninguna sincronización real de carritos abandonados en el canal. channelId es opcional; si lo omites, la clave channelId simplemente no aparece en la respuesta.


Recuperar un carrito

Marca el carrito como recuperado o dispara la acción de recuperación. Requiere abandoned-carts:recover.

idstringrequired

ID del carrito abandonado.

orderIdstring

ID interno del pedido en el que se convirtió el carrito, si ya existe.

externalOrderIdstring

ID del pedido en el canal de origen (Shopify, Amazon, etc.), si ya existe.

{ "orderId": "65f3a1b2c4d5e6f7a8b9c0d1", "externalOrderId": "SHOPIFY-10042" }
200
{ "data": { "id": "65f3a1b2c4d5e6f7a8b9c0e1", "status": "recovered" } }
409
{ "error": { "code": "abandoned_carts:already_recovered", "message": "Abandoned cart already recovered" } }
curl -X POST https://api.fenicia.io/orders/abandoned-carts/65f3a1b2c4d5e6f7a8b9c0e1/recover \
  -H "Authorization: Bearer fkapi_tu_api_key"

Cerrar un carrito

Cierra el carrito como no recuperable. Requiere abandoned-carts:update.

idstringrequired

ID del carrito abandonado.

{}
200
{ "data": { "id": "65f3a1b2c4d5e6f7a8b9c0e1", "status": "closed" } }
409
{ "error": { "code": "abandoned_carts:already_closed", "message": "Abandoned cart already closed" } }

No requiere cuerpo

El handler no lee ningún campo del body — envía {} o ningún cuerpo, es indistinto.

curl -X POST https://api.fenicia.io/orders/abandoned-carts/65f3a1b2c4d5e6f7a8b9c0e1/close \
  -H "Authorization: Bearer fkapi_tu_api_key"

Registrar una acción de recuperación

Registra una acción de recuperación sobre el carrito (por ejemplo, un recordatorio enviado). Requiere abandoned-carts:send-reminder.

idstringrequired

ID del carrito abandonado.

typestringrequired

Canal de la acción: email | sms | whatsapp | notification | manual.

statusstringrequired

Resultado de la acción: scheduled | sent | delivered | failed | clicked | converted.

timestampstring

Fecha ISO de la acción. Default: ahora.

templateIdstring

ID de la plantilla de mensaje usada.

recoveryUrlstring

URL de recuperación enviada al cliente (debe ser una URL válida).

messagestring

Contenido del mensaje enviado.

userIdstring

ID del usuario de tu equipo que disparó la acción.

{
  "type": "whatsapp",
  "status": "sent",
  "templateId": "recordatorio-carrito-v1",
  "recoveryUrl": "https://tienda.example.com/cart/recover/65f3a1b2c4d5e6f7a8b9c0e1"
}
200
{
  "data": {
    "id": "65f3a1b2c4d5e6f7a8b9c0e1",
    "status": "contacted",
    "recoveryStatus": "whatsapp_sent",
    "lastContactedAt": "2026-04-10T10:00:00.000Z"
  }
}
404
{ "error": { "code": "abandoned_carts:not_found", "message": "Abandoned cart not found" } }

recoveryStatus se deriva de type + status

recoveryStatus no lo envías tú: la API lo calcula a partir de tu type/status. Solo cuando status es sent o delivered pasa a email_sent / sms_sent / whatsapp_sent según el type; en cualquier otro caso queda pending. El carrito además pasa a status: "contacted" en cuanto registras la primera acción.


Actualizar el estado de un carrito

Actualiza directamente el estado de un carrito abandonado. Requiere abandoned-carts:update.

idstringrequired

ID del carrito abandonado.

statusstringrequired

Nuevo estado: open | contacted | recovered | expired | closed.

{ "status": "expired" }
200
{
  "data": {
    "id": "65f3a1b2c4d5e6f7a8b9c0e1",
    "status": "expired",
    "updatedAt": "2026-04-10T10:05:00.000Z"
  }
}
400
{ "error": { "code": "validation:invalid_input", "message": "Invalid status. Must be one of: open, contacted, recovered, expired, closed" } }
404
{ "error": { "code": "abandoned_carts:not_found", "message": "Abandoned cart not found" } }

Usa recover/close para las transiciones con reglas de negocio

Este endpoint escribe el status directamente, sin las reglas que aplican POST .../recover (registra recoveredAt) o POST .../close. Prefiere esos endpoints dedicados cuando el caso de uso coincida; usa este solo para casos que no cubren (por ejemplo, marcar expired manualmente).


Errores

CódigoStatusDescripción
abandoned_carts:not_found404No existe un carrito abandonado con ese ID en tu tenant.
abandoned_carts:already_recovered409El carrito ya fue marcado como recuperado.
abandoned_carts:already_closed409El carrito ya fue cerrado.
abandoned_carts:recovery_failed500Falló la acción de recuperación (por ejemplo, el envío del recordatorio).
validation:invalid_input400El cuerpo no cumple el schema — falta type/status en una acción de recuperación, o status no es un valor válido al actualizar el estado.
validation:invalid_pagination400page o limit tienen un valor fuera de rango.
auth:invalid_token401La API key es inválida o fue revocada.
auth:permission_denied403La API key no tiene el permiso requerido (con guion, ej. abandoned-carts:recover).

Aislamiento por tenant

Solo puedes consultar y operar carritos abandonados de tu propio tenant. Intentar acceder a un carrito de otro tenant devuelve 404, nunca información cruzada.

Siguientes pasos