Crear un pedido
Este endpoint te permite crear un pedido manualmente desde tu sistema. Úsalo cuando necesites capturar ventas que no provienen de un canal conectado, por ejemplo:
- Ventas telefónicas — un cliente llama y tomas su pedido en caliente.
- Ventas en mostrador sin POS conectado.
- Integraciones personalizadas con ERPs, marketplaces propios o sistemas legacy.
- Migraciones históricas de otra plataforma (usa
orderCreatedpara preservar la fecha original).
Si necesitas cargar varios pedidos a la vez, usa POST /orders/import en lugar de llamar este endpoint en un ciclo.
Pedidos desde canales conectados
Si el pedido ya existe en un canal conectado (Shopify, Amazon, MercadoLibre…), no lo crees manualmente. Fenicia lo sincroniza automáticamente. Crear pedidos duplicados genera inconsistencias de inventario.
Endpoint
Crea un nuevo pedido en el tenant autenticado. El cuerpo del request debe ir envuelto en la propiedad order.
orderobjectrequiredEnvelope obligatorio. Todo el cuerpo del request va anidado dentro de esta propiedad — el schema raíz es estricto y no acepta propiedades fuera de order.
{
"order": {
"externalId": "FEN-10042",
"handle": "fen-10042",
"locationId": "65f2a1b2c4d5e6f7a8b9c0aa",
"origin": "manual",
"channelId": "65f2a1b2c4d5e6f7a8b9c0bb",
"orderStatus": "pending",
"paymentStatus": "paid",
"currency": "MXN",
"totalAmount": 1986.26,
"orderCreated": "2026-04-11T14:23:11.000Z",
"items": [
{ "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
{ "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
],
"customerInfo": { "name": "María González" },
"shippingAddress": {
"street": "Av. Reforma 123",
"city": "Ciudad de México",
"state": "CDMX",
"zipCode": "06600",
"country": "MX"
},
"notes": "Cliente pidió empaque de regalo"
}
}{
"data": {
"_id": "65f3a1b2c4d5e6f7a8b9c0d1",
"externalId": "FEN-10042",
"handle": "fen-10042",
"orderStatus": "pending",
"paymentStatus": "paid",
"currency": "MXN",
"totalAmount": 1986.26,
"items": [
{ "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
{ "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
],
"customerInfo": { "name": "María González" },
"orderCreated": "2026-04-11T14:23:11.000Z",
"_links": {
"self": "/orders/65f3a1b2c4d5e6f7a8b9c0d1",
"accept": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/accept",
"reject": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/reject"
}
}
}
{ "error": { "code": "validation:invalid_input", "message": "order object is required" } }
Permiso requerido: orders:create
El envelope order es obligatorio
El cuerpo del request no es el objeto del pedido directamente — debe ir anidado dentro de una propiedad order: { "order": { ... } }. El validador del schema es estricto (.strict()): cualquier propiedad desconocida en la raíz del JSON (fuera de order) hace que el request se rechace con validation:invalid_input.
Cuerpo del request — campos de order
Campos requeridos (10)
Estos 10 campos deben incluirse siempre dentro de order. Si falta alguno, el request se rechaza.
| Campo | Tipo | Descripción |
|---|---|---|
externalId | string | Identificador del pedido en tu sistema de origen. |
handle | string | Handle o folio legible del pedido. |
locationId | string | ID de la ubicación (bodega/tienda) que despacha el pedido. |
origin | string | Origen de la venta (por ejemplo manual). |
channelId | string | ID del canal de venta al que pertenece el pedido. |
orderStatus | string | Estado inicial del pedido. Debe ser un estado válido de la máquina de estados. |
paymentStatus | string | Estado del pago al momento de crear el pedido. |
currency | string | Código de moneda ISO 4217, exactamente 3 letras (ej. MXN, USD). |
totalAmount | number | Monto total del pedido. |
orderCreated | string (ISO 8601) | Fecha de creación original del pedido. Útil al migrar pedidos históricos, donde no coincide con la fecha del request. |
Campos opcionales
| Campo | Tipo | Descripción |
|---|---|---|
items | array | Líneas del pedido. Ver forma exacta abajo. |
customerInfo | object | Datos del cliente. Ver forma exacta abajo. |
customerId | string | ID de un cliente existente en tu CRM. |
notes | string | Notas internas visibles solo para tu equipo. |
tags | array | Etiquetas para clasificar el pedido. |
discounts | array | Descuentos aplicados al pedido. |
subtotal | number | Subtotal antes de impuestos y envío. |
tax | number | Impuesto total del pedido. |
shipping | number | Costo de envío cobrado al cliente. |
payment | object | Información del pago (método, referencia, etc.). |
paymentSummary | object | Resumen del pago. |
deliveryInfo | object | Información de entrega (método, paquetería). |
billingAddress | object | Dirección de facturación. |
shippingAddress | object | Dirección de envío. |
metadata | object | Pares clave-valor arbitrarios para tu integración. |
isCounterSale | boolean | Indica si es una venta de mostrador. |
customerInfo y contenido adicional
customerInfo acepta propiedades adicionales a las documentadas (no las rechaza), pero solo name está confirmado como validado. No asumas que otros campos dentro de customerInfo se validan o se usan.
Forma de items[]
Cada línea del pedido tiene esta forma:
| Campo | Tipo | Descripción |
|---|---|---|
sku | string | SKU del producto. |
title | string | Nombre/título de la línea. |
quantity | number | Cantidad vendida. |
totalAmount | number | Monto total de la línea (no precio unitario). |
Forma de customerInfo
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí (si envías customerInfo) | Nombre del cliente. |
Ejemplo completo
curl -X POST https://api.fenicia.io/orders \
-H "Authorization: Bearer fkapi_tu_api_key" \
-H "Content-Type: application/json" \
-d '{
"order": {
"externalId": "FEN-10042",
"handle": "fen-10042",
"locationId": "65f2a1b2c4d5e6f7a8b9c0aa",
"origin": "manual",
"channelId": "65f2a1b2c4d5e6f7a8b9c0bb",
"orderStatus": "pending",
"paymentStatus": "paid",
"currency": "MXN",
"totalAmount": 1986.26,
"orderCreated": "2026-04-11T14:23:11.000Z",
"items": [
{ "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
{ "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
],
"customerInfo": { "name": "María González" },
"shippingAddress": {
"street": "Av. Reforma 123",
"city": "Ciudad de México",
"state": "CDMX",
"zipCode": "06600",
"country": "MX"
},
"notes": "Cliente pidió empaque de regalo"
}
}'Respuesta de ejemplo
{
"data": {
"_id": "65f3a1b2c4d5e6f7a8b9c0d1",
"externalId": "FEN-10042",
"handle": "fen-10042",
"orderStatus": "pending",
"paymentStatus": "paid",
"currency": "MXN",
"totalAmount": 1986.26,
"items": [
{ "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
{ "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
],
"customerInfo": { "name": "María González" },
"orderCreated": "2026-04-11T14:23:11.000Z",
"_links": {
"self": "/orders/65f3a1b2c4d5e6f7a8b9c0d1",
"accept": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/accept",
"reject": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/reject"
}
}
}Tip
Guarda el _id devuelto. Lo necesitarás para consultar, actualizar o transicionar el pedido en las siguientes llamadas.
Importar pedidos en lote
Para cargar varios pedidos en una sola llamada (migraciones, backfills), usa el endpoint de importación.
Importa un lote de pedidos en el tenant autenticado
ordersarrayrequiredArreglo de objetos de pedido. Cada uno requiere, como mínimo, locationId.
{
"orders": [
{ "externalId": "LEGACY-001", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." },
{ "externalId": "LEGACY-002", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." }
]
}{ "data": { "imported": 2 } }
{ "error": { "code": "validation:invalid_input", "message": "orders must be an array" } }
Permiso requerido: orders:import
Validación por pedido
La validación de cada pedido del lote la aplica la librería de dominio, no un schema de transporte por-item documentado aquí. Prueba primero con un lote pequeño en un tenant de desarrollo antes de correr una importación masiva.
Errores
| Código | Status | Descripción |
|---|---|---|
validation:invalid_input | 400 | El cuerpo no cumple el schema — falta un campo requerido, un tipo no coincide, o hay una propiedad desconocida en la raíz (fuera de order). |
auth:invalid_token | 401 | La API key es inválida o fue revocada. |
auth:permission_denied | 403 | La API key no tiene el scope orders:create (o orders:import para el lote). |
internal:server_error | 500 | Error interno al procesar el pedido. |
Consulta el catálogo completo de errores para el resto de los códigos del dominio.