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 orderCreated para 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.

orderobjectrequired

Envelope 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"
  }
}
201
{
  "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"
    }
  }
}
400
{ "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.

CampoTipoDescripción
externalIdstringIdentificador del pedido en tu sistema de origen.
handlestringHandle o folio legible del pedido.
locationIdstringID de la ubicación (bodega/tienda) que despacha el pedido.
originstringOrigen de la venta (por ejemplo manual).
channelIdstringID del canal de venta al que pertenece el pedido.
orderStatusstringEstado inicial del pedido. Debe ser un estado válido de la máquina de estados.
paymentStatusstringEstado del pago al momento de crear el pedido.
currencystringCódigo de moneda ISO 4217, exactamente 3 letras (ej. MXN, USD).
totalAmountnumberMonto total del pedido.
orderCreatedstring (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

CampoTipoDescripción
itemsarrayLíneas del pedido. Ver forma exacta abajo.
customerInfoobjectDatos del cliente. Ver forma exacta abajo.
customerIdstringID de un cliente existente en tu CRM.
notesstringNotas internas visibles solo para tu equipo.
tagsarrayEtiquetas para clasificar el pedido.
discountsarrayDescuentos aplicados al pedido.
subtotalnumberSubtotal antes de impuestos y envío.
taxnumberImpuesto total del pedido.
shippingnumberCosto de envío cobrado al cliente.
paymentobjectInformación del pago (método, referencia, etc.).
paymentSummaryobjectResumen del pago.
deliveryInfoobjectInformación de entrega (método, paquetería).
billingAddressobjectDirección de facturación.
shippingAddressobjectDirección de envío.
metadataobjectPares clave-valor arbitrarios para tu integración.
isCounterSalebooleanIndica 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:

CampoTipoDescripción
skustringSKU del producto.
titlestringNombre/título de la línea.
quantitynumberCantidad vendida.
totalAmountnumberMonto total de la línea (no precio unitario).

Forma de customerInfo

CampoTipoRequeridoDescripción
namestringSí (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

ordersarrayrequired

Arreglo de objetos de pedido. Cada uno requiere, como mínimo, locationId.

{
  "orders": [
    { "externalId": "LEGACY-001", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." },
    { "externalId": "LEGACY-002", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." }
  ]
}
200
{ "data": { "imported": 2 } }
400
{ "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ódigoStatusDescripción
validation:invalid_input400El 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_token401La API key es inválida o fue revocada.
auth:permission_denied403La API key no tiene el scope orders:create (o orders:import para el lote).
internal:server_error500Error interno al procesar el pedido.

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

Siguientes pasos