Entrega y Envíos

Estos endpoints cubren todo lo que ocurre después de que un pedido fue procesado: registrar la entrega, confirmarla, subir evidencia (fotos/documentos), adjuntar información de un envío externo y gestionar su costo. Para la parte de preparación y envío en sí, consulta Preparación y Envío.

Envelope de respuesta

Toda respuesta exitosa de recurso único sigue { "data": {...} }. Los errores siempre son { "error": { "code", "message", "details?" } } con códigos namespace:snake_case.

Marcar como entregado

Marca el pedido como entregado. La fecha de entrega es opcional — si la omites, se usa la hora del servidor.

orderIdstringrequired

ID del pedido

deliveredAtstring

Fecha/hora ISO 8601 de la entrega. Si se omite, se usa el momento en que llega la petición.

{
  "deliveredAt": "2026-07-20T18:30:00.000Z"
}
200
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "orderStatus": "delivered",
    "_links": {
      "returns": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/returns"
    }
  }
}

Permiso requerido: orders:update

curl -X POST https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/mark-delivered \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"deliveredAt":"2026-07-20T18:30:00.000Z"}'

Confirmar entrega

Confirma formalmente la entrega de un pedido, con notas opcionales.

orderIdstringrequired

ID del pedido

notesstring

Notas asociadas a la confirmación de entrega.

{
  "notes": "Recibido por el cliente en recepción"
}
200
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "orderStatus": "delivered",
    "_links": {}
  }
}

Permiso requerido: orders:fulfill

curl -X PUT https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/confirm-delivery \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"notes":"Recibido por el cliente en recepción"}'

Subir evidencia de entrega

Registra evidencia de entrega (fotos, documentos) asociada al pedido.

orderIdstringrequired

ID del pedido

evidenceUrlstringrequired

URL de la evidencia (foto o documento). Debe ser una URL válida.

evidenceTypestring

Tipo de evidencia (texto libre, ej. 'photo', 'signature').

notesstring

Notas asociadas a la evidencia.

{
  "evidenceUrl": "https://fenicia-order-attachments-prod.s3.us-east-2.amazonaws.com/evidence/delivery-photo.jpg",
  "evidenceType": "photo",
  "notes": "Entregado en la puerta principal"
}
200
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "orderStatus": "delivered",
    "_links": {}
  }
}

Permiso requerido: orders:update

No sube el archivo — solo registra su URL

Este endpoint NO es un flujo de subida en dos pasos como el de adjuntos de pedido (URL prefirmada de S3 + confirmación). evidenceUrl es la URL ya existente de la evidencia (por ejemplo, una foto que subiste tú mismo a tu propio storage o vía el flujo de adjuntos) — este endpoint solo la asocia al pedido, no aloja el archivo.

Adjuntar guía de envío externa

Adjunta al pedido los datos de una guía de envío generada fuera de Fenicia (por ejemplo, comprada directamente con la paquetería).

orderIdstringrequired

ID del pedido

trackingNumberstringrequired

Número de rastreo de la guía.

carrierstringrequired

Código de la paquetería.

trackingUrlstring

URL pública de rastreo.

{
  "trackingNumber": "1Z999AA10123456784",
  "carrier": "fedex"
}
200
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "orderStatus": "fulfilled",
    "_links": {}
  }
}

Permiso requerido: orders:update

curl -X POST https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/upload-guide \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"trackingNumber":"1Z999AA10123456784","carrier":"fedex"}'

Adjuntar información de envío

Adjunta al pedido la información completa de un envío: paquetería, costo, guía, fechas estimadas, origen/destino y dimensiones del paquete.

orderIdstringrequired

ID del pedido

trackingNumberstringrequired

Número de rastreo.

carrierstringrequired

Código de paquetería.

shipmentIdstring

ID del shipment, si ya existe uno a actualizar.

waybillIdstring

ID de la guía/waybill.

carrierNamestring

Nombre visible de la paquetería.

serviceNamestring

Nombre del servicio de envío (ej. 'Express', 'Terrestre').

providerstring

Proveedor que originó el envío (ej. agregador de paqueterías).

providerShipmentIdstring

ID del envío en el sistema del proveedor.

costobject

Costo del envío: { providerCost, feniciaFee, totalCost, currency }.

trackingUrlstring

URL pública de rastreo.

labelUrlstring

URL de la guía en PDF.

labelS3Keystring

Key de S3 donde vive la guía, si Fenicia la almacenó.

deliveryDaysnumber

Días estimados de tránsito.

estimatedDeliveryDatestring

Fecha estimada de entrega (ISO 8601).

purchasedAtstring

Fecha en que se compró el envío (ISO 8601).

printingFormatstring

'standard' o 'thermal'.

originobject

Dirección de origen del envío.

destinationobject

Dirección de destino del envío.

packageobject

Dimensiones del paquete: { weight, weightUnit, length, width, height, dimensionUnit }.

{
  "trackingNumber": "1Z999AA10123456784",
  "carrier": "fedex",
  "cost": { "providerCost": 180.00, "feniciaFee": 15.00, "totalCost": 195.00, "currency": "MXN" }
}
200
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "orderStatus": "fulfilled",
    "fulfillmentMode": "fenicia-shippments",
    "_links": {}
  }
}

Permiso requerido: orders:update

Forma de cost (opcional)

CampoTipoDescripción
providerCostnumberCosto cobrado por el proveedor de envío.
feniciaFeenumberComisión de Fenicia sobre el envío.
totalCostnumberCosto total (providerCost + feniciaFee, salvo ajustes).
currencystringMoneda de 3 letras (ej. MXN).

Forma de package (opcional)

CampoTipoDescripción
weightnumberPeso del paquete.
weightUnitstringUnidad de peso (ej. kg).
length / width / heightnumberDimensiones.
dimensionUnitstringUnidad de dimensiones (ej. cm).

carrier === 'other'

Si carrier es "other", revisa si tu integración requiere un nombre descriptivo — usa carrierName para eso.

curl -X POST https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attach-shipment \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingNumber": "1Z999AA10123456784",
    "carrier": "fedex",
    "cost": { "providerCost": 180.00, "feniciaFee": 15.00, "totalCost": 195.00, "currency": "MXN" }
  }'

Costo de un shipment

Actualiza o define el costo de un shipment específico dentro del pedido.

orderIdstringrequired

ID del pedido

shipmentIdstringrequired

ID del shipment dentro del pedido

amountnumberrequired

Monto pagado por el envío (≥ 0).

currencystringrequired

Moneda ISO 4217 de 3 letras (ej. MXN).

{
  "amount": 195.00,
  "currency": "MXN"
}
200
{
  "data": {
    "orderId": "65f3a1b2c4d5e6f7a8b9c0d1",
    "shipmentId": "65f3a1b2c4d5e6f7a8b9c0e2",
    "shippingCost": { "amount": 195.00, "currency": "MXN" }
  }
}

Permiso requerido: orders:update

Este endpoint es solo el costo — no reemplaza attach-shipment

PUT .../shipping-cost únicamente captura o actualiza el monto pagado por un shipment que ya existe en el pedido (identificado por shipmentId); no crea el shipment ni acepta los demás campos de attach-shipment (guía, paquetería, fechas). Cada operación emite un evento de dominio que lambda-finance-events refleja como un Expense en @fenicia/finance-service.

Elimina el costo previamente asignado a un shipment del pedido.

orderIdstringrequired

ID del pedido

shipmentIdstringrequired

ID del shipment dentro del pedido

200
{
  "data": {
    "orderId": "65f3a1b2c4d5e6f7a8b9c0d1",
    "shipmentId": "65f3a1b2c4d5e6f7a8b9c0e2",
    "removed": true
  }
}

Permiso requerido: orders:update

Idempotente: removed indica si había costo previo

Si el shipment ya no tenía costo asignado, la llamada igual responde 200 con removed: false — no es un error, es idempotencia intencional (los consumidores de eventos pueden entregar la misma remoción dos veces). Un shipmentId que no existe en el pedido sí devuelve orders:shipment_not_found (404).

curl -X DELETE https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/shipments/65f3a1b2c4d5e6f7a8b9c0e2/shipping-cost \
  -H "Authorization: Bearer fkapi_tu_api_key"

Descargar guías de envío de un pedido

Devuelve URLs presignadas de S3 para descargar las guías de envío del pedido. Cada PDF se persiste como adjunto del pedido (la primera vez) o se sirve desde caché S3 en llamadas posteriores.

orderIdstringrequired

ID del pedido

200
{
  "data": {
    "orderId": "65f3a1b2c4d5e6f7a8b9c0d1",
    "totalLabels": 1,
    "labels": [
      {
        "identifier": "T1-2026-04-27-00042",
        "downloadUrl": "https://fenicia-order-attachments-prod.s3.us-east-2.amazonaws.com/attachments/...?X-Amz-Signature=...",
        "filename": "t1-T1-2026-04-27-00042_shipping_label.pdf",
        "contentType": "application/pdf",
        "size": 51234,
        "expiresIn": 900,
        "source": "live",
        "carrier": "T1-via-Estafeta",
        "attachmentId": "65f3a1b2c4d5e6f7a8b9c0e5"
      }
    ],
    "processingTimeMs": 432
  }
}

Permiso requerido: orders:read

El PDF nunca viaja en base64

Se persiste como adjunto del pedido en S3 y la API responde con una URL presignada (TTL ~15 min). Descarga el PDF directamente desde esa URL — no necesitas tu API key para ese segundo paso. Llamadas repetidas para el mismo pedido reutilizan el adjunto en caché.

LABEL_URL=$(curl -s https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/download-shipping-label \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  | jq -r '.data.labels[0].downloadUrl')
 
curl -o guia.pdf "$LABEL_URL"

Tip

Para impresión en lote de alto volumen, usa POST /orders/fulfillment/labels (ver Preparación y Envío) con varios IDs de pedido a la vez.

Errores

CódigoStatusDescripción
orders:not_found404No existe un pedido con ese ID en tu tenant.
orders:cannot_confirm_delivery422El pedido no está en un estado desde el que se pueda confirmar entrega.
orders:shipment_not_found404No existe un shipment con ese ID dentro del pedido.
orders:shipping_info_required400Falta información de envío requerida para la operación.
orders:shipping_label_not_available422No hay guía disponible para generar/descargar en el estado actual.
validation:invalid_input / validation:missing_field400El body no cumple el schema esperado o falta un campo requerido.
auth:permission_denied403La API key no tiene el permiso requerido.
auth:invalid_token401La API key es inválida o fue revocada.

Siguientes pasos