Adjuntos

Los adjuntos de pedido te permiten asociar archivos a un pedido: facturas, CFDI (XML/PDF), guías de envío, evidencia de entrega y más. Los archivos se almacenan en S3 y se acceden mediante URLs prefirmadas de corta duración — nunca viajan en base64 dentro del JSON de la API.

Envelope de respuesta

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

Catálogo real de type (11 valores)

CategoríaValores
documentinvoice, ticket, cfdi-xml, cfdi-pdf, shipping-guide, customs, receipt, other
evidencepacking-photo, packing-video, condition-photo

No confundas con el catálogo antiguo

Valores como cfdi, shipping_guide o custom no existen — el real usa cfdi-xml/cfdi-pdf, shipping-guide (con guion) y other, respectivamente.

Límites por categoría

CategoríaTamaño máximoMáximo por pedido
document10 MB10
evidence200 MB30

Flujo de subida

Las subidas usan un flujo de dos pasos con URL prefirmada, de modo que los bytes del archivo viajan directamente a S3 sin pasar por los servidores de Fenicia.

1

Solicitar URL prefirmada de subida

Llama a POST /orders/{orderId}/attachments/upload-url. Recibes una uploadUrl de corta duración y un s3Key.

2

Subir el archivo a S3 con PUT

Sube los bytes del archivo directamente a la uploadUrl, usando exactamente el Content-Type que declaraste en el paso anterior.

3

Confirmar el adjunto

Llama a POST /orders/{orderId}/attachments con el mismo s3Key para registrar el adjunto contra el pedido.

Solicitar URL de subida

Genera una URL prefirmada de S3 para subir un archivo. No sube el archivo — solo prepara el destino.

orderIdstringrequired

ID del pedido

typestringrequired

Tipo de adjunto (ver catálogo arriba).

filenamestringrequired

Nombre de archivo original.

contentTypestringrequired

MIME type del archivo (ej. application/pdf).

{ "type": "invoice", "filename": "factura-1001.pdf", "contentType": "application/pdf" }
200
{
  "data": {
    "uploadUrl": "https://fenicia-order-attachments-prod.s3.us-east-2.amazonaws.com/attachments/...?X-Amz-Signature=...",
    "s3Key": "attachments/65f3a1b2c4d5e6f7a8b9c0d1/invoice-2026-04-11.pdf",
    "expiresIn": 900
  }
}

Permiso requerido: orders:update

curl -X POST https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attachments/upload-url \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"type":"invoice","filename":"factura-1001.pdf","contentType":"application/pdf"}'

Confirmar el adjunto

Registra el adjunto contra el pedido, después de haber subido el archivo a S3 con la URL prefirmada.

orderIdstringrequired

ID del pedido

s3Keystringrequired

El mismo s3Key devuelto por upload-url.

typestringrequired

Tipo de adjunto (ver catálogo arriba).

filenamestringrequired

Nombre de archivo original.

contentTypestringrequired

MIME type del archivo.

{
  "s3Key": "attachments/65f3a1b2c4d5e6f7a8b9c0d1/invoice-2026-04-11.pdf",
  "type": "invoice",
  "filename": "factura-1001.pdf",
  "contentType": "application/pdf"
}
201
{
  "data": {
    "attachment": {
      "_id": "65f3a1b2c4d5e6f7a8b9c0e5",
      "type": "invoice",
      "filename": "factura-1001.pdf",
      "contentType": "application/pdf",
      "size": 128340,
      "uploadedAt": "2026-04-11T14:30:00.000Z"
    }
  }
}

Permiso requerido: orders:update

El objeto va envuelto en attachment

El adjunto creado viaja bajo data.attachment, no directamente bajo data. Si desestructuras la respuesta, recuerda ese nivel extra de anidado.

curl -X POST https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attachments \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"s3Key":"attachments/65f3a1b2c4d5e6f7a8b9c0d1/invoice-2026-04-11.pdf","type":"invoice","filename":"factura-1001.pdf","contentType":"application/pdf"}'

Listar adjuntos de un pedido

Lista todos los adjuntos registrados en el pedido.

orderIdstringrequired

ID del pedido

200
{
  "data": {
    "attachments": [
      {
        "_id": "65f3a1b2c4d5e6f7a8b9c0e5",
        "type": "invoice",
        "filename": "factura-1001.pdf",
        "contentType": "application/pdf",
        "size": 128340,
        "uploadedAt": "2026-04-11T14:30:00.000Z"
      }
    ],
    "count": 1
  }
}

Permiso requerido: orders:read

curl https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attachments \
  -H "Authorization: Bearer fkapi_tu_api_key"

Obtener la URL de descarga de un adjunto

Devuelve una URL prefirmada para descargar un adjunto específico.

orderIdstringrequired

ID del pedido

attachmentIdstringrequired

ID del adjunto

200
{
  "data": {
    "downloadUrl": "https://fenicia-order-attachments-prod.s3.us-east-2.amazonaws.com/attachments/...?X-Amz-Signature=...",
    "expiresIn": 900
  }
}
404
{ "error": { "code": "attachments:attachment_not_found", "message": "Attachment not found" } }

Permiso requerido: orders:read

No confundir con la ruta singular /attachment

GET /orders/{orderId}/attachment (singular, sin ID) es una ruta legacy registrada en el router pero siempre responde 501 — es un stub deshabilitado durante una migración, no un endpoint funcional. Usa siempre la ruta plural con {attachmentId} documentada aquí.

DOWNLOAD_URL=$(curl -s https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attachments/65f3a1b2c4d5e6f7a8b9c0e5 \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  | jq -r '.data.downloadUrl')
 
curl -o factura.pdf "$DOWNLOAD_URL"

Eliminar un adjunto

Elimina un adjunto del pedido y su archivo en S3.

orderIdstringrequired

ID del pedido

attachmentIdstringrequired

ID del adjunto

200
{ "data": { "success": true, "message": "Attachment deleted" } }

Permiso requerido: orders:update

200 con cuerpo, no 204

Este endpoint responde 200 con { "data": { "success": true, "message": "..." } } — no es un 204 sin cuerpo. Verifica data.success en vez de solo confiar en el status code.

curl -X DELETE https://api.fenicia.io/orders/65f3a1b2c4d5e6f7a8b9c0d1/attachments/65f3a1b2c4d5e6f7a8b9c0e5 \
  -H "Authorization: Bearer fkapi_tu_api_key"

Errores

CódigoStatusDescripción
attachments:invalid_content_type400El contentType declarado no coincide con lo esperado para el type.
attachments:invalid_attachment_type400El type no está en el catálogo real (ver tabla arriba).
attachments:validation_failed400El body no cumple el schema esperado.
attachments:attachment_limit_exceeded400Se alcanzó el máximo de adjuntos por pedido para esa categoría.
attachments:file_not_found400El archivo no fue encontrado en S3 al confirmar el adjunto (¿saltaste el paso de subida?).
attachments:unauthorized403La API key no tiene el permiso requerido.
attachments:order_not_found404No existe un pedido con ese ID en tu tenant.
attachments:attachment_not_found404No existe un adjunto con ese ID sobre ese pedido.
attachments:delete_failed500Falla al eliminar el archivo en S3 o el registro del adjunto.

Tip

Los límites de tamaño y cantidad son por categoría (document vs. evidence), no un límite único global — revisa la tabla de límites arriba antes de subir archivos grandes de evidencia.

Siguientes pasos