Documentación API

API Pública de Sourced v1

La API de Sourced permite que los sistemas ERP (como Calipso, SAP, JD Edwards) se integren directamente con la plataforma de compras de Sourced. Úsala para:

  • Enviar requisiciones de compra desde tu ERP a Sourced
  • Obtener órdenes de compra de vuelta en tu ERP una vez adjudicadas
  • Verificar qué requisiciones ya fueron sincronizadas
  • Monitorear el estado de tus requisiciones

La API sigue las convenciones REST, utiliza JSON para los cuerpos de solicitud y respuesta, y se autentica mediante API keys.

Autenticación

Todos los endpoints (excepto health check) requieren una API key enviada en el header X-API-Key.

Las API keys se crean en el panel de administración de Sourced en Configuración → API Keys. Cada key se muestra solo una vez al crearla. Guardala de forma segura.

Ejemplo: Solicitud autenticada
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/requisitions
Seguridad: Tratá tu API key como una contraseña. No la expongas en código del lado del cliente, repositorios públicos o logs. Si se compromete, revocala inmediatamente desde el panel de administración y creá una nueva.

URL Base

AmbienteURL Base
Producciónhttps://api.gosourced.ai
Desarrollohttps://api-dev.gosourced.ai

Todos los endpoints tienen el prefijo /api/v1.

Límites de tasa

Todos los endpoints están limitados a 60 solicitudes por minuto por endpoint. Exceder este límite devuelve una respuesta 429 Too Many Requests.

Para operaciones masivas, usá el endpoint de importación por lotes (POST /requisitions) que acepta hasta 100 requisiciones por solicitud.

Errores

La API utiliza códigos de estado HTTP estándar. Los errores devuelven un cuerpo JSON con un campo detail.

Formato de respuesta de error
{
  "detail": "Invalid or revoked API key."
}
EstadoSignificado
200Éxito
400Solicitud inválida - error de validación o cuerpo malformado
401No autorizado - API key faltante o inválida
404No encontrado - el recurso no existe o no es accesible
422Entidad no procesable - el cuerpo de la solicitud no pasó la validación del esquema
429Demasiadas solicitudes - límite de tasa excedido
500Error interno del servidor - error inesperado de nuestro lado

Ejemplos de errores

401 - API key inválida
{
  "detail": "Invalid or revoked API key."
}
422 - Error de validación (ej: campo requerido faltante)
{
  "detail": [
    {
      "loc": ["body", "requisitions", 0, "items", 0, "quantity"],
      "msg": "Input should be greater than 0",
      "type": "greater_than"
    }
  ]
}
200 - Éxito parcial (algunas requisiciones fallaron)
{
  "success": false,
  "created_count": 2,
  "updated_count": 0,
  "skipped_count": 0,
  "error_count": 1,
  "errors": [
    {
      "index": 2,
      "external_id": "REQ-2026-0099",
      "error": "Failed to process requisition: duplicate external_line_id within items"
    }
  ],
  "requisition_ids": [1234, 1235]
}

Nota: el endpoint POST /requisitions devuelve 200 incluso con fallos parciales. Siempre verificá el campo success y el array errors para detectar problemas por requisición.

Health Check

GET/api/v1/health

Verificar que la API esté accesible. No requiere autenticación.

Solicitud
curl https://api.gosourced.ai/api/v1/health
Respuesta - 200 OK
{
  "status": "ok",
  "api": "v1"
}

Subir un archivo (Presigned)

POST/api/v1/files/presignAPI Key requerida

Obtené una URL prefirmada para subir un archivo (un manifiesto de requisición o un adjunto) directo a S3. Los bytes no pasan por la API, así que las especificaciones técnicas pesadas de licitaciones suben sin chocar los límites de tamaño.

Solo canal de transporte: los archivos quedan en el almacenamiento bajo el prefijo de tu organización, sin parseo ni procesamiento. La URL prefirmada vence a los 10 minutos y acepta archivos de hasta 500 MB.

Flujo de dos pasos (por archivo)

  1. Llamá a este endpoint con el nombre del archivo para recibir un upload_url y los campos del formulario.
  2. Hacé un POST del archivo como multipart/form-data a upload_url, incluyendo primero todos los campos de fields y luego un campo file con los bytes.

Cuerpo del request

CampoTipoRequeridoDescripción
filenamestringrequeridoNombre original del archivo (ej. 'SOL_106177_LINE_001_SEQ010_adjunto.docx'). Se eliminan los componentes de ruta.
content_typestringopcionalTipo MIME. Si se omite, se acepta cualquier tipo.
1. Solicitud
curl -X POST https://api.gosourced.ai/api/v1/files/presign \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "SOL_106177_LINE_001_SEQ010_adjunto.docx",
    "content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  }'
Respuesta - 200 OK
{
  "upload_url": "https://s3.amazonaws.com/your-bucket",
  "fields": {
    "key": "erp-inbound/42/SOL_106177_LINE_001_SEQ010_adjunto.docx",
    "Content-Type": "application/vnd.openxmlformats-...",
    "policy": "eyJleHBpcmF0aW9uIjoi...",
    "x-amz-signature": "a1b2c3..."
  },
  "key": "erp-inbound/42/SOL_106177_LINE_001_SEQ010_adjunto.docx",
  "max_file_size": 524288000,
  "expires_in": 600
}

Campos de la respuesta

  • upload_url - la URL de S3 a la que se hace el POST del archivo.
  • fields - campos del formulario que deben incluirse en el POST multipart, antes del campo file.
  • key - la clave bajo la que se guardará el archivo.
  • max_file_size - tamaño máximo permitido en bytes (lo aplica S3).
  • expires_in - segundos hasta que vence la URL prefirmada.
2. Subida a S3 (multipart/form-data)
# Use upload_url + every key in "fields", then the file (last).
curl -X POST "$UPLOAD_URL" \
  -F "key=erp-inbound/42/SOL_106177_LINE_001_SEQ010_adjunto.docx" \
  -F "Content-Type=application/vnd.openxmlformats-..." \
  -F "policy=eyJleHBpcmF0aW9uIjoi..." \
  -F "x-amz-signature=a1b2c3..." \
  -F "file=@SOL_106177_LINE_001_SEQ010_adjunto.docx"
# HTTP 204 No Content on success

Importar requisiciones

POST/api/v1/requisitionsAPI Key requerida

Importar una o más requisiciones de compra desde tu ERP. Soporta importación por lotes de hasta 100 requisiciones por solicitud. Usa external_id para deduplicación.

Cuerpo de la solicitud

El cuerpo debe contener un array requisitions con 1 a 100 objetos de requisición.

Objeto Requisición

CampoTipoRequeridoDescripción
external_idstringrequeridoID único en tu ERP (clave de deduplicación). Máx 250 caracteres.
itemsarrayrequeridoItems de línea (1-200 items). Ver esquema de Item abajo.
requester_namestringopcionalNombre de la persona solicitante. Máx 200 caracteres.
requester_emailstringopcionalEmail del solicitante. Máx 200 caracteres.
assigned_buyerstringopcionalComprador asignado en el ERP de origen (texto libre). Se muestra y filtra en el triage. Máx 200 caracteres.
descriptionstringopcionalTítulo o resumen de la requisición. Máx 500 caracteres.
commentsstringopcionalInstrucciones adicionales para compradores (HTML soportado). Máx 15.000 caracteres.
delivery_addressstringopcionalDirección de entrega en texto libre. Máx 500 caracteres.
delivery_address_codestringopcionalCódigo que coincide con una dirección configurada en Sourced. Máx 50 caracteres.
desired_delivery_lead_time_daysintegeropcionalPlazo de entrega deseado en días desde la confirmación de la OC. Se aplica como default a todos los items.
created_datedatetimeopcionalFecha de creación de la requisición en tu sistema (ISO 8601). Se muestra como "Creación" en el triage. No es una fecha de entrega: la necesidad va por item en desired_delivery_date.
offer_deadlinedatetimeopcionalFecha límite para cotizaciones de proveedores (ISO 8601).
total_estimated_valuefloatopcionalValor total estimado. Se calcula automáticamente desde los items si se omite.
currencystringopcionalCódigo de moneda (ej: "ARS", "USD"). Por defecto "ARS". Máx 10 caracteres.
department_codestringopcionalCódigo de departamento/centro de costo (se compara con departamentos de Sourced). Máx 50 caracteres.
priority_levelstringopcionalPrioridad: "LOW", "MEDIUM", "HIGH" o "CRITICAL".
attachmentsarrayopcionalArchivos adjuntos (máx 20). Ver esquema de Adjunto abajo.
raw_dataobjectopcionalJSON arbitrario de tu ERP, almacenado para trazabilidad.

Objeto Item

CampoTipoRequeridoDescripción
descriptionstringrequeridoVisible para el proveedorNombre o descripción del item. Máx 1.000 caracteres.
quantityfloatrequeridoVisible para el proveedorCantidad requerida (debe ser > 0).
external_line_idstringopcionalID de línea en tu ERP (ej: "REQ-001-L10"). Se devuelve en las OC para trazabilidad. Máx 100 caracteres.
unit_of_measurestringopcionalVisible para el proveedorCódigo de UOM (ej: "KG", "EA", "LT", "M", "UN"). Se compara con el catálogo de tu org. Máx 50 caracteres.
target_pricefloatopcionalPrecio objetivo/presupuesto por unidad.
estimated_pricefloatopcionalPrecio total estimado para esta línea.
currencystringopcionalMoneda para precios (ej: "ARS", "USD"). Máx 10 caracteres.
categorystringopcionalCategoría desde tu ERP. Máx 200 caracteres.
material_codestringopcionalVisible para el proveedorCódigo de material/parte en tu ERP (ej: código de material SAP). Máx 100 caracteres.
specificationsobjectopcionalEspecificaciones técnicas. Ver esquema de Especificaciones abajo.
desired_delivery_datedatetimeopcionalFecha de entrega deseada para este item (ISO 8601, ej: "2026-05-15T00:00:00Z").
desired_delivery_lead_time_daysintegeropcionalPlazo de entrega deseado en días para esta línea. Sobreescribe el valor a nivel de cabecera.
detailstringopcionalVisible para el proveedorDato técnico o nota de esta línea. Máx 1.000 caracteres. Se comparte con el proveedor: se incluye en el mail de solicitud de cotización bajo «Especificaciones», salvo que mandes specifications.technical_specs, que tiene prioridad. No lo uses para notas internas de compras.
raw_dataobjectopcionalJSON arbitrario para este item.

Objeto Especificaciones

CampoTipoRequeridoDescripción
manufacturer_codestringopcionalVisible para el proveedorNúmero de parte del fabricante (ej: "6ES7214-1AG40-0XB0"). Máx 100 caracteres.
manufacturer_namestringopcionalVisible para el proveedorNombre del fabricante (ej: "Siemens"). Máx 200 caracteres.
manufacturer_descriptionstringopcionalDescripción del fabricante. Máx 500 caracteres.
buyer_codestringopcionalVisible para el proveedorCódigo interno en tu sistema (ej: "MAT-001234"). Máx 100 caracteres.
buyer_code_descriptionstringopcionalDescripción del código interno. Máx 500 caracteres.
buyer_code_systemstringopcionalNombre del sistema de origen (ej: "SAP", "Calipso"). Máx 50 caracteres.
technical_specsstringopcionalVisible para el proveedorEspecificaciones técnicas en texto libre (ej: "220V, 50Hz, IP55"). Máx 2.000 caracteres. Se comparte con el proveedor en el mail de solicitud de cotización. Tiene prioridad sobre el detail del ítem.
requirementsstringopcionalRequisitos adicionales (ej: "Certificación ISO 9001 requerida"). Máx 2.000 caracteres. Uso interno: no se incluye en el mail al proveedor.

Objeto Adjunto

CampoTipoRequeridoDescripción
urlstringrequeridoURL públicamente accesible para descargar el archivo. Máx 2.000 caracteres.
filenamestringrequeridoNombre de archivo original (ej: "plano_motor.pdf"). Máx 255 caracteres.
descriptionstringopcionalDescripción del adjunto. Máx 500 caracteres.
file_typestringopcionalTipo MIME (se detecta automáticamente si no se proporciona). Máx 100 caracteres.

Lógica de deduplicación

Cada requisición se identifica por su external_id (dentro del alcance de tu organización):

  • Nueva: Si no se encuentra una requisición existente, se crea una nueva con estado PENDING.
  • Actualizar: Si se encuentra una requisición existente con estado PENDING, se actualiza in-place (los items se reemplazan completamente).
  • Superseder: Si se encuentra una requisición existente con estado SUPERSEDED, se reactiva (vuelve a PENDING) con los nuevos datos.
  • Lanzada: Si la requisición ya fue lanzada como Solicitud de Compra (LAUNCHED), la importación se rechaza. La PR activa no puede ser sobreescrita.
  • Descartada: Si la requisición fue descartada previamente (DISCARDED), se reactiva (vuelve a PENDING) con los nuevos datos. Útil cuando el ERP la cancela y luego la reenvía.
Solicitud - Ejemplo mínimo
curl -X POST https://api.gosourced.ai/api/v1/requisitions \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "requisitions": [
      {
        "external_id": "REQ-2026-0042",
        "items": [
          {
            "description": "Bearing SKF 6205",
            "quantity": 10
          }
        ]
      }
    ]
  }'

Respuesta

ERPImportResult

CampoTipoRequeridoDescripción
successbooleanrequeridotrue si error_count es 0.
created_countintegerrequeridoCantidad de nuevas requisiciones creadas.
updated_countintegerrequeridoCantidad de requisiciones PENDING existentes actualizadas.
skipped_countintegerrequeridoCantidad de requisiciones omitidas (actualmente siempre 0).
error_countintegerrequeridoCantidad de requisiciones que fallaron al importar.
errorsarrayrequeridoArray de {index, external_id, error} para cada requisición fallida.
requisition_idsarrayrequeridoIDs internos de Sourced de las requisiciones creadas/actualizadas.
Respuesta - 200 OK
{
  "success": true,
  "created_count": 1,
  "updated_count": 0,
  "skipped_count": 0,
  "error_count": 0,
  "errors": [],
  "requisition_ids": [1234]
}

Listar requisiciones

GET/api/v1/requisitionsAPI Key requerida

Obtener una lista paginada de tus requisiciones importadas. Solo devuelve requisiciones creadas a través de la API.

Parámetros de consulta

CampoTipoRequeridoDescripción
pageintegeropcionalNúmero de página (por defecto: 1).
page_sizeintegeropcionalItems por página, 1-100 (por defecto: 20).
statusstringopcionalFiltrar por estado: "PENDING", "LAUNCHED", "DISCARDED", "SUPERSEDED".
Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  "https://api.gosourced.ai/api/v1/requisitions?page=1&page_size=20&status=PENDING"
Respuesta - 200 OK
{
  "data": [
    {
      "id": 1234,
      "external_id": "REQ-2026-0042",
      "status": "PENDING",
      "requester_name": "Juan Pérez",
      "description": "Repuestos línea producción",
      "items": [...],
      "imported_at": "2026-03-07T14:30:00Z"
    }
  ],
  "total": 45,
  "page": 1,
  "page_size": 20,
  "has_more": true
}

Detalle de requisición

GET/api/v1/requisitions/{requisition_id}API Key requerida

Obtener una requisición individual por su ID interno de Sourced, incluyendo todos los items.

Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/requisitions/1234

Devuelve el objeto completo de la requisición. Devuelve 404 si no se encuentra o no pertenece a tu organización.

Verificar requisiciones existentes

POST/api/v1/requisitions/check-existingAPI Key requerida

Verificar qué external_ids ya existen en Sourced antes de importar. Útil para evitar llamadas innecesarias a la API.

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
external_idsarrayrequeridoLista de strings external_id a verificar (1-100 items).
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/requisitions/check-existing \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_ids": ["REQ-2026-0042", "REQ-2026-0043", "REQ-2026-0044"]
  }'
Respuesta - 200 OK
{
  "existing": ["REQ-2026-0042"],
  "not_found": ["REQ-2026-0043", "REQ-2026-0044"]
}

Cancelar requisición

POST/api/v1/requisitions/cancelAPI Key requerida

Cancelar una requisición PENDING enviando el external_id en el cuerpo del request.

Solo se pueden cancelar requisiciones con estado PENDING. Si la requisición ya fue lanzada como Solicitud de Compra, debe cancelarse dentro de Sourced.

CampoTipoRequeridoDescripción
external_idstringrequeridoID de la requisición en tu sistema externo (la misma que usaste al importar)
reasonstringopcionalMotivo de la cancelación (se almacena para auditoría)
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/requisitions/cancel \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "REQ-2026-0042",
    "reason": "Cancelado desde ERP"
  }'
Respuesta - 200 OK
{
  "status": "discarded",
  "external_id": "REQ-2026-0042"
}

Respuestas posibles:

  • discarded - La requisición fue descartada exitosamente.
  • already_discarded - La requisición ya estaba descartada (idempotente).
  • already_superseded - La requisición ya fue reemplazada por una versión más nueva.
  • La requisición ya fue lanzada - devuelve 409 con detalles.
  • Requisición no encontrada - devuelve 404.

Listar órdenes de compra

GET/api/v1/purchase-ordersAPI Key requerida

Obtener órdenes de compra de tu organización. Soporta sincronización incremental a través del parámetro 'since'.

Parámetros de consulta

CampoTipoRequeridoDescripción
pageintegeropcionalNúmero de página (por defecto: 1).
page_sizeintegeropcionalItems por página, 1-100 (por defecto: 20).
statusstringopcionalFiltrar por estado: "DRAFT", "CREATED", "SENT", "CONFIRMED", "REJECTED", "CANCELLED".
sincedatetimeopcionalTimestamp ISO 8601. Devuelve solo OC creadas o actualizadas después de esta fecha.
requisition_external_idstringopcionalFiltrar por el external_id de la requisición original en tu ERP. Devuelve las OC que se generaron a partir de esa requisición.
Solicitud - Sincronización incremental
curl -H "X-API-Key: sk_live_abc123..." \
  "https://api.gosourced.ai/api/v1/purchase-orders?since=2026-03-01T00:00:00Z&status=CONFIRMED"
Respuesta - 200 OK
{
  "data": [
    {
      "id": 567,
      "code": "PO-2026-0089",
      "status": "CONFIRMED",
      "award_type": "FULL",
      "supplier_name": "Distribuidora Industrial SA",
      "supplier_id": 42,
      "supplier_external_id": "PROV-CAL-00123",
      "supplier_tax_id": "30712345678",
      "supplier_email": "ventas@distribuidora.com",
      "total_price": 285000.00,
      "currency": "ARS",
      "delivery_address": "Av. Corrientes 1234, CABA",
      "delivery_address_code": "001",
      "observations": "Entregar en horario de mañana. Coordinar con depósito.",
      "delivery_lead_time_days": 15,
      "expected_delivery_date": "2026-03-20T16:00:00Z",
      "payment_terms_code": "NET30",
      "awarded_at": "2026-03-05T16:00:00Z",
      "awarded_by_name": "Juan Pérez",
      "awarded_by_email": "juan.perez@empresa.com",
      "created_at": "2026-03-05T16:00:00Z",
      "updated_at": "2026-03-06T10:30:00Z",
      "purchase_request_id": 1234,
      "requisition_external_id": "REQ-2026-00142",
      "exchange_rate_data": {
        "date": "2026-03-05",
        "usdToArs": 1450.0,
        "arsToUsd": 0.00069
      },
      "total_nominal_savings": 15000.00,
      "total_real_savings": 8500.00,
      "savings_currency": "ARS",
      "items": [
        {
          "description": "Bearing SKF 6205",
          "quantity": 10,
          "unit_price": 28500.00,
          "total_price": 285000.00,
          "unit_of_measure": "UN",
          "currency": "ARS",
          "delivery_lead_time_days": 15,
          "expected_delivery_date": "2026-03-20T16:00:00Z",
          "external_line_id": "REQ-2026-0042-L10",
          "buyer_code": "MAT-001234",
          "buyer_code_system": "SAP",
          "manufacturer_code": "6205-2RS",
          "technical_specs": "220V, 50Hz, IP55"
        }
      ]
    }
  ],
  "total": 12,
  "page": 1,
  "page_size": 20,
  "has_more": false
}

Detalle de orden de compra

GET/api/v1/purchase-orders/{po_id}API Key requerida

Obtener una orden de compra individual por su ID interno de Sourced, incluyendo todos los items con trazabilidad hacia tu ERP via external_line_id.

Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/purchase-orders/567
¿Buscás OCs por el ID de requisición de tu sistema?

Este endpoint requiere el ID interno de Sourced (po_id). Si necesitás buscar órdenes de compra usando el ID de requisición de tu ERP, usá el endpoint GET /purchase-orders con el query parameter requisition_external_id:

GET /api/v1/purchase-orders?requisition_external_id=YOUR-REQ-ID

También podés combinarlo con otros filtros: status para filtrar por estado de la OC, since para sincronización incremental, y page / page_size para paginación.

Objeto Orden de Compra

CampoTipoRequeridoDescripción
idintegerrequeridoID interno de OC en Sourced.
codestringrequeridoCódigo de OC legible.
statusstringrequeridoEstado de la OC: "DRAFT", "CREATED", "SENT", "CONFIRMED", "REJECTED", "CANCELLED".
award_typestringopcionalTipo de adjudicación: FULL (proveedor único) o PARTIAL (adjudicación dividida entre múltiples proveedores).
supplier_namestringrequeridoNombre del proveedor.
supplier_idintegeropcionalID interno del proveedor en Sourced.
total_pricefloatopcionalValor total de la OC.
currencystringopcionalCódigo de moneda.
delivery_addressstringopcionalDirección de entrega.
delivery_address_codestringopcionalCódigo de la dirección de entrega de la organización vinculada (ej. "001"). Usalo para mapear el destino contra tu ERP. Null para direcciones de texto libre sin registro vinculado.
observationsstringopcionalComentarios u observaciones en texto libre que el comprador cargó en el paso de revisión pre-adjudicación. Usa las condiciones especiales cotizadas por el proveedor si el comprador no dejó nota. Null cuando no hay ninguna.
delivery_lead_time_daysintegeropcionalPlazo de entrega en días, según lo cotizado por el proveedor a nivel cabecera. Cuando el proveedor cotiza distintos plazos por línea, los items pueden tener valores diferentes - usá items[].delivery_lead_time_days para el valor por línea.
expected_delivery_datedatetimeopcionalFecha de entrega calculada (awarded_at + delivery_lead_time_days). Null si falta cualquiera de los dos. Para fechas por línea usá items[].expected_delivery_date.
payment_terms_codestringopcionalCódigo de condiciones de pago.
awarded_atdatetimeopcionalCuándo se adjudicó la OC (ISO 8601).
awarded_by_namestringopcionalNombre del usuario que adjudicó la OC.
awarded_by_emailstringopcionalEmail del usuario que adjudicó la OC.
created_atdatetimeopcionalTimestamp de creación (ISO 8601).
updated_atdatetimeopcionalTimestamp de última actualización (ISO 8601).
purchase_request_idintegeropcionalID de la solicitud de compra originante.
requisition_external_idstringopcionalID del documento origen en tu ERP (ej. número de requisición en Calipso/SAP/JDE). Se resuelve desde la ERPRequisition de origen cuando existe; si no, cae al external_id de la PR. Puede ser una lista separada por comas cuando una sola PR agrupa varias requisiciones origen.
exchange_rate_dataobjectopcionalTipos de cambio al momento del award. Formato: {"date": "2026-03-05", "usdToArs": 1450.0, "arsToUsd": 0.00069}. Null si no hay datos disponibles.
total_nominal_savingsfloatopcionalAhorro nominal total vs precios históricos (sin ajuste por inflación).
total_real_savingsfloatopcionalAhorro real total vs precios históricos (ajustado por inflación).
savings_currencystringopcionalMoneda de los montos de ahorro.
itemsarrayrequeridoItems de línea de la OC. Ver esquema de Item de OC abajo.

Objeto Item de OC

CampoTipoRequeridoDescripción
descriptionstringrequeridoDescripción del item.
quantityfloatopcionalCantidad ordenada.
unit_pricefloatopcionalPrecio por unidad.
total_pricefloatopcionalPrecio total de la línea (cantidad x precio unitario).
unit_of_measurestringopcionalUnidad de medida.
currencystringopcionalCódigo de moneda.
delivery_lead_time_daysintegeropcionalPlazo de entrega en días para esta línea, según lo cotizado por el proveedor. Items distintos de una misma OC pueden tener plazos diferentes.
expected_delivery_datedatetimeopcionalFecha de entrega calculada para esta línea (PO.awarded_at + delivery_lead_time_days del item). Null si falta cualquiera de los dos.
external_line_idstringopcionalID de línea original de tu ERP - usalo para vincular items de la OC con tus líneas de requisición.
buyer_codestringopcionalCódigo interno en tu sistema, tal como lo enviaste al importar la requisición (ej: "MAT-001234").
buyer_code_systemstringopcionalSistema de origen del código interno (ej: "SAP", "JDE", "Calipso").
buyer_code_descriptionstringopcionalDescripción del código interno.
manufacturer_codestringopcionalNúmero de parte del fabricante.
manufacturer_namestringopcionalNombre del fabricante (ej: "Siemens").
manufacturer_descriptionstringopcionalDescripción del fabricante para la parte.
technical_specsstringopcionalEspecificaciones técnicas en texto libre (ej: "220V, 50Hz, IP55").
requirementsstringopcionalRequisitos adicionales (ej: "Certificación ISO 9001 requerida").
Sobre fechas y plazos de entrega
  • El plazo de entrega existe a dos niveles: delivery_lead_time_days a nivel cabecera (refleja el plazo general que cotizó el proveedor) y items[].delivery_lead_time_days a nivel línea (preciso por item). Cuando el proveedor cotiza distintos plazos por línea, conviene usar el valor por línea.
  • expected_delivery_date es un campo calculado: awarded_at + delivery_lead_time_days. Lo computa Sourced; no es un dato que el proveedor envíe directamente.
  • Si delivery_lead_time_days viene null, también lo hará expected_delivery_date. Esto suele significar que el proveedor no incluyó el plazo en su cotización.

Descargar legajo de la OC (expediente)

GET/api/v1/purchase-orders/{po_id}/legajoAPI Key requerida

Obtiene un link temporal para descargar el legajo - un ZIP con la traza completa de la adjudicación: PDF/XLSX de comparativa, historial de emails y los adjuntos de todos los proveedores participantes.

Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/purchase-orders/567/legajo

Objeto de respuesta

CampoTipoRequeridoDescripción
download_urlstringrequeridoURL presignada temporal para descargar el ZIP del legajo.
filenamestringrequeridoNombre sugerido para el archivo ZIP.
expires_inintegerrequeridoSegundos hasta que la URL de descarga expira.
size_bytesintegerrequeridoTamaño del ZIP del legajo en bytes.
Cómo funciona el legajo
  • La respuesta es una URL presignada temporal (válida ~10 minutos). Descarga el ZIP directamente desde ahí - esa URL no requiere API key.
  • El legajo corresponde a la solicitud (PR) detrás de la OC. En una adjudicación dividida (varias OC de una misma solicitud), las OC hermanas devuelven el mismo expediente.

Comparativa de cotizaciones de la OC

GET/api/v1/purchase-orders/{po_id}/quotationsAPI Key requerida

Obtiene la comparativa completa de cotizaciones que originó esta adjudicación: todas las líneas cotizadas por cada proveedor para la solicitud (PR) detrás de la OC, adjudicadas y no adjudicadas - no solo las que terminaron en esta OC.

Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/purchase-orders/567/quotations
Respuesta - 200 OK
{
  "purchase_order_id": 567,
  "purchase_order_code": "PO-4F2A91C3",
  "requisition_external_id": "REQ-2026-0042",
  "exchange_rate": {
    "date": "2026-07-12",
    "base": "USD",
    "rates": { "ARS": 1450.5, "EUR": 0.92 }
  },
  "currencies": ["ARS", "USD"],
  "items": [
    {
      "material_code": "MAT-000123",
      "external_line_id": "10",
      "description": "Guantes de nitrilo talle L",
      "quantity": 500.0,
      "quotes": [
        {
          "quotation_id": 8123,
          "supplier_id": 42,
          "supplier_name": "Proveedor A S.A.",
          "supplier_tax_id": "30516242775",
          "supplier_erp_code": "PROV-001",
          "unit_price": 11.25,
          "original_price": 12.5,
          "discount": { "type": "percentage", "value": 10, "source": "manual" },
          "currency": "ARS",
          "quantity": 500,
          "awarded": true,
          "awarded_po_code": "PO-4F2A91C3",
          "lead_time_days": 7,
          "payment_term_code": "NET30",
          "response_status": "PARTIAL",
          "received_at": "2026-07-10T14:32:00+00:00"
        },
        {
          "quotation_id": 8124,
          "supplier_id": 57,
          "supplier_name": "Proveedor B S.R.L.",
          "supplier_tax_id": "30709876541",
          "supplier_erp_code": null,
          "unit_price": 0.0095,
          "original_price": 0.0095,
          "discount": null,
          "currency": "USD",
          "quantity": 500,
          "awarded": false,
          "awarded_po_code": null,
          "lead_time_days": 15,
          "payment_term_code": null,
          "response_status": "INCOMPLETE",
          "received_at": "2026-07-11T09:05:00+00:00"
        }
      ]
    }
  ]
}

Objeto de respuesta

CampoTipoRequeridoDescripción
purchase_order_idintegerrequeridoID de la OC consultada.
purchase_order_codestringrequeridoCódigo de la OC consultada (p. ej. PO-4F2A91C3).
requisition_external_idstringopcionalID externo de la requisición de origen (sistema ERP), si existe.
exchange_rateobjectopcionalTipos de cambio guardados en la OC al momento de la adjudicación, para convertir las cotizaciones a una moneda común: { date, base: "USD", rates: { ARS: 1450.5, EUR: 0.92 } } (unidades de cada moneda por 1 USD). null si la OC no guardó una foto (licitación, catálogo u OC anterior a esta función).
currenciesarrayrequeridoMonedas distintas que aparecen en las cotizaciones, ordenadas (p. ej. ["ARS", "USD"]).
itemsarrayrequeridoLíneas de la solicitud detrás de la OC, cada una con sus cotizaciones.

Objeto ítem (items[])

CampoTipoRequeridoDescripción
material_codestringopcionalCódigo de material del comprador, si la línea tiene uno.
external_line_idstringopcionalID de línea del sistema de origen (ERP), si existe.
descriptionstringrequeridoDescripción del ítem solicitado.
quantityfloatopcionalCantidad solicitada.
quotesarrayrequeridoCotizaciones recibidas para esta línea, una por proveedor que cotizó con precio.

Objeto cotización (items[].quotes[])

CampoTipoRequeridoDescripción
quotation_idintegerrequeridoID de la cotización. Un proveedor que cotizó más de una vez (rondas) aparece una vez por cotización.
supplier_idintegeropcionalID interno del proveedor en Sourced. Identidad estable entre líneas (el nombre puede repetirse).
supplier_namestringopcionalNombre del proveedor que cotizó.
supplier_tax_idstringopcionalIdentificador fiscal del proveedor (CUIT en Argentina, CNPJ en Brasil, RUT en Chile/Uruguay).
supplier_erp_codestringopcionalCódigo ERP del proveedor en tu organización, si está configurado.
unit_pricefloatopcionalPrecio unitario FINAL, neto de descuento, en la moneda original del proveedor. Es el mismo número que ve el comprador en la comparativa de adjudicación y en el Excel del legajo, y el precio al que se adjudicó la OC.
original_pricefloatopcionalPrecio unitario antes del descuento. Igual a unit_price si no hubo descuento.
discountobjectopcionalDescuento aplicado: { type: "percentage" | "fixed", value, source }. source: supplier_quoted (lo ofreció el proveedor), negotiation, manual (lo cargó el comprador) o global. null si no hubo descuento.
currencystringopcionalMoneda de la cotización (código ISO, p. ej. ARS, USD). Si el proveedor no indicó moneda en ninguna parte, se informa "USD".
quantityfloatopcionalCantidad cotizada por el proveedor.
awardedbooleanrequeridotrue si esta línea se adjudicó a este proveedor en alguna OC activa de la solicitud.
awarded_po_codestringopcionalCódigo de la OC donde se adjudicó la línea (puede ser una OC hermana en adjudicación dividida). null si no fue adjudicada.
lead_time_daysintegeropcionalPlazo de entrega ofrecido, en días (entero). Un plazo no numérico se informa como null.
payment_term_codestringopcionalCódigo de condición de pago ofrecida por el proveedor.
response_statusstringopcionalClasificación del análisis AI de la respuesta del proveedor. Valores como INCOMPLETE, PARTIAL, NEEDS_HUMAN_REVIEW, EXPLICIT_REJECTION.
received_atdatetimeopcionalFecha y hora de recepción de la cotización (ISO 8601).
Cómo leer la comparativa
  • La comparativa corresponde a la solicitud (PR) detrás de la OC: incluye las cotizaciones de todos los proveedores que participaron, aunque no hayan ganado. Una línea sin precio cotizado no genera entrada en quotes; una OC de catálogo (sin proceso de cotización) devuelve quotes vacíos.
  • awarded es a nivel solicitud: en una adjudicación dividida, una línea puede haberse adjudicado en una OC hermana distinta de la consultada - awarded_po_code siempre indica el código de la OC ganadora de esa línea.
  • Los precios son los mismos que el comprador vio en la comparativa de adjudicación y los que figuran en el Excel del legajo: unit_price ya tiene aplicado cualquier descuento negociado (original_price y discount muestran el bruto y el descuento). Una cotización importada que el usuario todavía no confirmó no aparece.
  • Los precios se devuelven en la moneda cotizada por cada proveedor - no se aplica conversión. exchange_rate trae los tipos de cambio guardados en la OC al adjudicar (null si no hay) para que puedas convertir todo a una moneda común.
  • Todas las claves están siempre presentes; si un dato falta, el valor es null (el shape de la respuesta nunca cambia).

Actualizar estado de orden de compra

POST/api/v1/purchase-orders/{po_id}/statusAPI Key requerida

Actualizar el estado de una orden de compra para reflejar su progreso en tu sistema externo (ERP).

Transiciones de estado permitidas:

DesdeHaciaSignificado
DRAFTCREATEDLa OC fue creada en tu ERP.
CREATEDCONFIRMEDLa OC fue aprobada completamente en tu ERP.
DRAFTCONFIRMEDAtajo cuando no necesitás el paso intermedio.
DRAFTREJECTEDLa OC fue rechazada en tu ERP. La PR se reabre en Sourced.
CREATEDREJECTEDLa OC fue rechazada en tu ERP después de haber sido creada. La PR se reabre en Sourced.
DRAFT / CREATED / SENT / CONFIRMEDCANCELLEDLa compra se cayó. La OC se cancela y la PR también - no se reabre nada. Usá REJECTED si la necesidad sigue vigente y querés re-adjudicar.

REJECTED y CANCELLED funcionan también cuando la OC fue una adjudicación parcial, siempre que sea la única OC activa de su PR. Si la PR tiene varias OCs activas (split a varios proveedores), el endpoint devuelve 409 y la reversión debe gestionarse dentro de Sourced.

CampoTipoRequeridoDescripción
statusstringrequeridoEstado destino: CREATED (OC creada en tu ERP), CONFIRMED (OC aprobada en tu ERP), REJECTED (OC rechazada, reabre la PR) o CANCELLED (compra caída, cancela la OC y la PR).
external_idstringopcionalNúmero o código de la OC en tu ERP (ej: OC-CAL-00045678). Se guarda para trazabilidad.
notesstringopcionalNotas opcionales sobre el cambio de estado.
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/purchase-orders/567/status \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "status": "CREATED",
    "external_id": "OC-CAL-00045678",
    "notes": "Creada en Calipso"
  }'
Respuesta - 200 OK
{
  "status": "created",
  "po_id": 567,
  "code": "PO-A1B2C3D4"
}

Editar una OC

PATCH/api/v1/purchase-orders/{po_id}API Key requerida

Actualiza los campos editables de una orden de compra. Solo se tocan los campos presentes en el body: número de OC en tu ERP, condición de pago, plazo de entrega, observaciones y estado (misma máquina de transiciones que POST /status).

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
external_idstringopcionalNúmero o código de la OC en tu ERP. Sobrescribe el valor guardado.
payment_terms_codestringopcionalCódigo de condición de pago del catálogo (p. ej. NET30). Insensible a mayúsculas; se valida contra los códigos activos - un código desconocido devuelve 400 con la lista permitida.
delivery_lead_time_daysintegeropcionalPlazo de entrega en días desde la confirmación de la OC. expected_delivery_date se deriva de este valor.
observationsstringopcionalTexto libre que se muestra en la OC. Una cadena vacía la limpia.
statusstringopcionalEstado destino: CREATED, CONFIRMED, REJECTED o CANCELLED - mismas transiciones que POST /status. Enviar el estado actual es un no-op. REJECTED/CANCELLED devuelven el contrato de esos flujos (pr_reopened / pr_code) más updated_fields.
notesstringopcionalNotas sobre el cambio de estado. Solo válido junto con status.
Solicitud
curl -X PATCH https://api.gosourced.ai/api/v1/purchase-orders/567 \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "OC-CAL-00045678",
    "payment_terms_code": "NET30",
    "status": "CONFIRMED"
  }'
Respuesta - 200 OK
{
  "status": "updated",
  "po_id": 567,
  "code": "PO-A1B2C3D4",
  "updated_fields": ["external_id", "payment_terms_code", "status"]
}
Semántica del PATCH
  • Partial update real: solo se aplican los campos presentes en el body. Enviar null explícito es un error (400) - para dejar un campo como está, se omite.
  • Toda la validación corre antes de aplicar nada: si la respuesta es 400 o 409, ningún campo del body se modificó.
  • El cambio de estado usa la misma máquina de transiciones que POST /purchase-orders/{po_id}/status (que sigue disponible). Una transición inválida devuelve 409. Editar una OC CANCELLED o REJECTED devuelve 409.
  • Cambiar la condición de pago o el plazo de entrega no regenera el PDF que ya recibió el proveedor: el dato se actualiza en Sourced, pero el documento enviado no cambia.
  • El contenido de la adjudicación (precios, ítems, moneda, proveedor) no es editable: para corregirlo, rechaza la OC (status REJECTED) y vuelve a adjudicar.

Listar proveedores

GET/api/v1/suppliersAPI Key requerida

Listar los proveedores de tu organización. Usa ?has_erp_code=false para encontrar proveedores sin mapear al ERP. Usa ?detail=full para obtener contactos, categorías y cobertura.

Parámetros de consulta

CampoTipoRequeridoDescripción
pageintegeropcionalNúmero de página (default: 1).
page_sizeintegeropcionalResultados por página (1-200, default: 50).
searchstringopcionalBuscar por nombre, nombre personalizado, email, CUIT/CNPJ o código ERP.
erp_codestringopcionalFiltrar por código ERP exacto.
has_erp_codebooleanopcionaltrue = solo mapeados al ERP, false = solo sin mapear.
detailstringopcionalUsar 'full' para incluir contactos, categorías y cobertura.
Solicitud
# Light (default)
curl -H "X-API-Key: sk_live_abc123..." \
  "https://api.gosourced.ai/api/v1/suppliers?has_erp_code=false"

# Full detail
curl -H "X-API-Key: sk_live_abc123..." \
  "https://api.gosourced.ai/api/v1/suppliers?search=30712345678&detail=full"
Respuesta - 200 OK
// Light response
{
  "data": [
    {
      "id": 42,
      "name": "Distribuidora Industrial SA",
      "email": "ventas@distribuidora.com",
      "tax_id": "30712345678",
      "country_code": "AR",
      "city": "Buenos Aires",
      "is_active": true,
      "is_preferred": true,
      "erp_code": "PROV-CAL-00123",
      "erp_type": "SAP_B1"
    }
  ],
  "total": 85,
  "page": 1,
  "page_size": 50,
  "has_more": true
}

// Full response (?detail=full)
{
  "data": [
    {
      "id": 42,
      "name": "Distribuidora Industrial SA",
      "email": "ventas@distribuidora.com",
      "tax_id": "30712345678",
      "country_code": "AR",
      "city": "Buenos Aires",
      "is_active": true,
      "is_preferred": true,
      "erp_code": "PROV-CAL-00123",
      "erp_type": "SAP_B1",
      "address": "Av. Corrientes 1234",
      "state_code": "CABA",
      "website": "https://distribuidora.com",
      "description": "Distribuidor de insumos industriales",
      "tax_regime": null,
      "employee_count": "50+",
      "years_in_business": "5+",
      "performance_score": 4.2,
      "reliability_score": 4.5,
      "quality_score": 4.0,
      "contacts": [
        {
          "name": "Juan Pérez",
          "email": "juan@distribuidora.com",
          "phone": "+54 11 5555-1234",
          "role": "SALES",
          "is_primary": true
        }
      ],
      "categories": [
        { "id": 15, "code": "IND-001", "name": "Insumos Industriales", "level": "LEVEL_1" }
      ],
      "coverage": [
        { "country": "Argentina", "province": null, "is_nationwide": true }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 50,
  "has_more": false
}

Crear proveedor

POST/api/v1/suppliersAPI Key requerida

Crear un proveedor para tu organización. Al menos uno de tax_id o erp_code es obligatorio. Se requiere al menos un contacto con rol 'sales'. Si ya existe un proveedor global con el mismo CUIT, se reutiliza y se vincula a tu organización.

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
namestringrequeridoNombre del proveedor para tu organización.
tax_idstringopcionalCUIT, CNPJ, RUT, etc. Al menos uno de tax_id o erp_code es obligatorio.
erp_codestringopcionalCódigo del proveedor en tu ERP. Al menos uno de tax_id o erp_code es obligatorio.
erp_typestringopcionalTipo de ERP (SAP_B1, JDE, ORACLE_CLOUD, etc.).
country_codestringopcionalCódigo de país ISO 3166-1 (ej: AR, BR, US).
citystringopcionalCiudad del proveedor.
addressstringopcionalDirección completa.
state_codestringopcionalEstado/provincia (ej: CABA, SP).
websitestringopcionalSitio web del proveedor.
contactsarrayrequeridoLista de contactos. Al menos uno con rol 'sales' es obligatorio.
contacts[].namestringrequeridoNombre del contacto.
contacts[].emailstringrequeridoEmail del contacto.
contacts[].phonestringopcionalTeléfono (opcional).
contacts[].rolestringrequeridoRol: SALES o LOGISTICS.
contacts[].is_primarybooleanopcionaltrue si es el contacto principal (default: false).
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/suppliers \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Distribuidora Industrial SA",
    "tax_id": "30712345678",
    "erp_code": "PROV-CAL-00123",
    "erp_type": "SAP_B1",
    "country_code": "AR",
    "city": "Buenos Aires",
    "address": "Av. Corrientes 1234",
    "contacts": [
      {
        "name": "Juan Pérez",
        "email": "juan@distribuidora.com",
        "phone": "+54 11 5555-1234",
        "role": "SALES",
        "is_primary": true
      },
      {
        "name": "María García",
        "email": "maria@distribuidora.com",
        "role": "LOGISTICS"
      }
    ]
  }'
Respuesta - 200 OK
// 201 Created
{
  "id": 42,
  "name": "Distribuidora Industrial SA",
  "email": null,
  "tax_id": "30712345678",
  "country_code": "AR",
  "city": "Buenos Aires",
  "is_active": true,
  "is_preferred": false,
  "erp_code": "PROV-CAL-00123",
  "erp_type": "SAP_B1",
  "address": "Av. Corrientes 1234",
  "state_code": null,
  "website": null,
  "description": null,
  "tax_regime": null,
  "employee_count": null,
  "years_in_business": null,
  "performance_score": null,
  "reliability_score": null,
  "quality_score": null,
  "contacts": [
    {
      "name": "Juan Pérez",
      "email": "juan@distribuidora.com",
      "phone": "+54 11 5555-1234",
      "role": "SALES",
      "is_primary": true
    },
    {
      "name": "María García",
      "email": "maria@distribuidora.com",
      "phone": null,
      "role": "LOGISTICS",
      "is_primary": false
    }
  ],
  "categories": [],
  "coverage": []
}

Crear o actualizar seguimientos de entrega

POST/api/v1/delivery-trackingsAPI Key requerida

Upsert por lotes de 1 a 200 líneas de OC para seguir su entrega. La clave natural es (po_code, line_position): reenviar la misma línea actualiza el seguimiento existente en vez de duplicarlo.

Límite de tasa: 30 solicitudes por minuto (los demás endpoints de este grupo usan el límite general de 60 por minuto).

supplier_erp_code es el identificador recomendado para matchear al proveedor. supplier_tax_id es un respaldo. supplier_name es solo texto para mostrar: nunca se usa para matchear.
Una línea cuyo proveedor no se pudo resolver queda en SUPPLIER_NOT_FOUND: no se le manda ningún mail. Una vez dado de alta el proveedor con POST /suppliers, reenviá la misma línea (mismo po_code y line_position) para que se re-vincule.

Cuerpo de la solicitud

El cuerpo debe contener un array lines con 1 a 200 líneas. Cada línea lleva la OC, la posición, la fecha comprometida, el proveedor (código ERP o CUIT) y el detalle del ítem (descripción, cantidad pedida y cantidad pendiente): sin eso no hay nada que seguir ni qué mostrarle al proveedor. Un campo obligatorio ausente rechaza el batch completo con 422.

Objeto de línea (lines[])

CampoTipoRequeridoDescripción
po_codestringrequeridoNúmero de OC en el sistema del cliente. Máximo 100 caracteres.
line_positionintegerrequeridoPosición de la línea dentro de la OC (10, 20, 30…). Entero ≥ 0.
original_delivery_datedaterequeridoFecha de entrega comprometida (YYYY-MM-DD)
supplier_erp_codestringUno de los dosCódigo del proveedor en el ERP del cliente. Identificador recomendado. Obligatorio si no se envía supplier_tax_id. Máximo 100 caracteres.
supplier_tax_idstringUno de los dosCUIT/RUT del proveedor. Obligatorio si no se envía supplier_erp_code. Máximo 50 caracteres.
supplier_namestringopcionalSolo texto para mostrar; nunca se usa para matchear al proveedor. Máximo 255 caracteres.
material_codestringopcionalCódigo del material o ítem en el sistema del cliente. Máximo 100 caracteres.
descriptionstringrequeridoDescripción del ítem. Es lo que el proveedor ve en el mail de seguimiento. Máximo 500 caracteres.
quantity_orderedfloatrequeridoCantidad pedida. ≥ 0. Es lo que el proveedor ve en el mail de seguimiento.
quantity_pendingfloatrequeridoCantidad pendiente de entrega. ≥ 0. Es lo que el mail le reclama al proveedor: con entregas parciales, mandá lo que falta, no lo pedido.
unit_of_measurestringopcionalUnidad de medida. Máximo 20 caracteres.
po_datedateopcionalFecha de la OC (YYYY-MM-DD).
buyer_emailstringopcionalCC de escalación y destinatario de los avisos de atraso. Si no se envía, la notificación in-app de atraso va al primer usuario con rol logistics de la organización (uno solo, no a todos).
is_urgentbooleanopcionalMarca la línea como urgente.

Hay que enviar al menos supplier_erp_code o supplier_tax_id. Para supplier_tax_id, un valor vacío o compuesto solo por separadores (por ejemplo "-" o " - ") cuenta como no enviado, y el match ignora guiones y espacios en ambos lados (30-71234567-8 matchea 30712345678). supplier_erp_code solo se recorta (trim): un "-" literal SÍ cuenta como enviado y se usa tal cual para buscar — si no matchea ningún proveedor, la línea queda en SUPPLIER_NOT_FOUND (no es un error).

Reglas del upsert

  • Línea nueva: se crea en PENDING_SCHEDULE si el proveedor resolvió, o en SUPPLIER_NOT_FOUND si no. Nace activa (sin pausar).
  • Línea existente, abierta, creada por esta misma API: se actualizan sus datos y cantidades. Un cambio de original_delivery_date o de las cantidades no reinicia el ciclo de seguimiento.
  • Línea existente ya cerrada (DELIVERED o CANCELLED): se reactiva con un ciclo de seguimiento nuevo (vuelve a mandar mails de confirmación); el resultado es reactivated y cuenta dentro de updated_count.
  • Corregir el proveedor de una línea existente: si llega con un supplier_erp_code o supplier_tax_id que resuelve a OTRO proveedor, el seguimiento se re-vincula a esa empresa y el ciclo arranca de nuevo (los mails previos eran con la empresa equivocada). Una línea que no resuelve ningún proveedor nunca desvincula el proveedor ya asignado.
  • Sin cambios: el resultado es unchanged; no se escribe ningún evento ni se toca updated_at.
  • Línea administrada por otra fuente (CSV o sincronización ERP): se rechaza con el error dlvTrackingManagedByOtherSource. Los seguimientos cargados por CSV o por la sincronización ERP no se pueden modificar por esta API.
  • Cantidad pendiente 0: la línea ya se recibió entera. Sobre un seguimiento abierto se cierra como DELIVERED (evento delivered con reason quantity_pending_zero; la fecha real queda como el día del envío — si la conocés, usá mark_delivered con actual_delivery_date). Sobre uno ya cerrado es unchanged, no lo reactiva. Una línea NUEVA con pendiente 0 devuelve el error dlvNothingPending.
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/delivery-trackings \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "lines": [
      {
        "po_code": "OC-2026-0455",
        "line_position": 10,
        "original_delivery_date": "2026-10-15",
        "supplier_erp_code": "PROV-CAL-00123",
        "material_code": "MAT-001234",
        "description": "Rodamiento SKF 6205",
        "quantity_ordered": 50,
        "quantity_pending": 50,
        "unit_of_measure": "UN",
        "buyer_email": "compras@empresa.com"
      },
      {
        "po_code": "OC-2026-0455",
        "line_position": 20,
        "original_delivery_date": "2026-10-15",
        "supplier_tax_id": "30-71234567-8",
        "description": "Filtro de aceite",
        "quantity_ordered": 12,
        "quantity_pending": 12,
        "unit_of_measure": "UN"
      }
    ]
  }'

Respuesta

Respuesta del upsert

CampoTipoRequeridoDescripción
created_countintegerrequeridoCantidad de líneas creadas.
updated_countintegerrequeridoCantidad de líneas actualizadas (incluye las reactivadas).
unchanged_countintegerrequeridoCantidad de líneas sin cambios.
error_countintegerrequeridoCantidad de líneas con error.
resultsarrayrequeridoResultado de cada línea del batch, en el mismo orden en que se enviaron.

Objeto de resultado por línea (results[])

CampoTipoRequeridoDescripción
po_codestringrequeridoNúmero de OC, tal como se envió.
line_positionintegerrequeridoPosición de línea, tal como se envió.
idintegeropcionalId interno del seguimiento. Ausente cuando la línea terminó en error.
resultstringrequeridocreated | updated | unchanged | reactivated | error.
statusstringopcionalEstado del seguimiento después del upsert. Ausente en las líneas con error.
codestringopcionalCódigo del error (dlv*). Solo presente cuando result es error.
messagestringopcionalDetalle del error en inglés, pensado para logs. No traducir ni mostrar al usuario final: el code es la clave i18n.
Respuesta - 200 OK
// 200 OK
{
  "created_count": 1,
  "updated_count": 0,
  "unchanged_count": 0,
  "error_count": 1,
  "results": [
    {
      "po_code": "OC-2026-0455",
      "line_position": 10,
      "id": 8821,
      "result": "created",
      "status": "PENDING_SCHEDULE"
    },
    {
      "po_code": "OC-2026-0455",
      "line_position": 20,
      "result": "error",
      "code": "dlvSupplierReferenceAmbiguous",
      "message": "supplier_tax_id '30-71234567-8' matches more than one supplier"
    }
  ]
}

El HTTP siempre es 200, incluso si hay líneas con error: revisá error_count y el code de cada línea en results para saber qué falló.

Listar seguimientos de entrega

GET/api/v1/delivery-trackingsAPI Key requerida

Obtener los seguimientos de entrega de tu organización, de todas las fuentes (API, CSV y sincronización ERP). Soporta sincronización incremental con since.

Parámetros de consulta

CampoTipoRequeridoDescripción
pageintegeropcionalNúmero de página (por defecto: 1).
page_sizeintegeropcionalItems por página, 1-200 (por defecto: 50).
statusstringopcionalFiltrar por estado (ver la tabla de estados más abajo). Incluye STAND_BY.
po_codestringopcionalFiltrar por el po_code exacto.
supplier_erp_codestringopcionalFiltrar por el código ERP del proveedor.
sincedatetimeopcionalTimestamp ISO 8601. Devuelve solo seguimientos creados o modificados desde esa fecha, en orden ascendente por fecha de modificación. Sin zona horaria se interpreta como UTC: mandá siempre un offset explícito (o "Z") para evitar ambigüedad.
open_onlybooleanopcionalExcluye los seguimientos DELIVERED y CANCELLED.
Con since, el orden es por fecha de modificación ascendente (no por fecha de creación descendente, que es el orden por defecto): guardá el updated_at del último registro recibido y usalo como el próximo since. Para no perder filas escritas por una transacción larga (un CSV grande, un sync del ERP), el servidor corre el since 10 minutos hacia atrás: vas a volver a recibir registros de esos minutos, así que deduplicá por id (el upsert es idempotente de tu lado). Para un seguimiento que nunca se modificó, updated_at es igual a created_at (nunca null): el cursor siempre tiene un valor usable, sin que tengas que resolver vos el fallback.

Estados

EstadoDescripción
PENDING_SCHEDULEPendiente de programar. Cargada; todavía no se pidió confirmación al proveedor.
READY_TO_SENDLista para enviar. Lista para mandar el pedido de confirmación de entrega.
SENT_PENDING_RESPONSEEnviada, esperando respuesta. Se pidió confirmación; el proveedor aún no respondió.
NO_RESPONSESin respuesta. No respondió tras los recordatorios.
CONFIRMED_ON_TIMEConfirmada en fecha. El proveedor confirmó la fecha original.
CONFIRMED_DELAYEDConfirmada con demora. Confirmó, pero con fecha posterior a la original.
CONFIRMED_EARLYConfirmada anticipada. Confirmó una fecha anterior a la original.
REQUIRES_REVIEWRequiere revisión. Respondió algo ambiguo o cambió condiciones; hay que revisarlo.
DELIVEREDEntregada. Ya entregada.
CANCELLEDCancelada. La OC fue cancelada.
SUPPLIER_NOT_FOUNDProveedor no identificado. No se pudo matchear el proveedor (por código ERP o CUIT).
STAND_BYPausada manualmente y todavía abierta: no se le manda ningún mail. Es un estado virtual — un estado terminal (DELIVERED/CANCELLED) siempre le gana a STAND_BY, así que una línea pausada que se entrega o se cancela se informa por su estado real.
Solicitud - Sincronización incremental
curl -H "X-API-Key: sk_live_abc123..." \
  "https://api.gosourced.ai/api/v1/delivery-trackings?since=2026-09-01T00:00:00Z&open_only=true"

Objeto de seguimiento (delivery tracking)

CampoTipoRequeridoDescripción
idintegerrequeridoId interno del seguimiento.
po_codestringrequeridoNúmero de OC en el sistema del cliente.
line_positionintegerrequeridoPosición de la línea dentro de la OC.
sourcestringrequeridoOrigen del seguimiento: api, csv o erp_sync. La API lee seguimientos de las tres fuentes.
supplierobjectopcionalProveedor vinculado, o null si todavía no se pudo resolver (SUPPLIER_NOT_FOUND).
supplier.idintegeropcionalId interno del proveedor.
supplier.namestringopcionalRazón social del proveedor.
supplier.tax_idstringopcionalCUIT/RUT del proveedor.
supplier.erp_codestringopcionalCódigo ERP del proveedor para tu organización.
supplier_namestringopcionalNombre del proveedor tal como se cargó (puede diferir de supplier.name si vino solo como texto libre).
material_codestringopcionalCódigo del material o ítem.
descriptionstringopcionalDescripción del ítem.
quantity_orderedfloatopcionalCantidad pedida.
quantity_pendingfloatopcionalCantidad pendiente de entrega.
unit_of_measurestringopcionalUnidad de medida.
po_datedateopcionalFecha de la OC.
original_delivery_datedateopcionalFecha de entrega comprometida originalmente.
etadateopcionalFecha estimada de entrega. La completa un comprador a mano desde la app (el modal de detalle); ningún proceso automático la calcula. En una línea creada por API queda null hasta que alguien la carga.
actual_delivery_datedateopcionalFecha real de entrega, cuando el estado es DELIVERED.
received_quantityfloatopcionalCantidad recibida, cuando el estado es DELIVERED.
statusstringopcionalEstado público (ver la tabla de estados).
is_urgentbooleanrequeridoSi la línea está marcada como urgente.
pausedbooleanrequeridoSi el envío de seguimiento está pausado (sending_paused).
pause_reasonstringopcionalMotivo de la pausa. null si no está pausada.
followup_countintegerrequeridoCantidad de recordatorios enviados en el ciclo actual.
last_followup_sent_atdatetimeopcionalFecha y hora del último recordatorio enviado.
next_followup_datedatetimeopcionalFecha planificada para el próximo recordatorio.
supplier_responseobjectrequeridoÚltima respuesta del proveedor.
supplier_response.responded_atdatetimeopcionalFecha y hora en que respondió el proveedor.
supplier_response.confirmed_delivery_datedateopcionalFecha de entrega que confirmó el proveedor.
supplier_response.delay_reasonstringopcionalMotivo de la demora, si el proveedor lo indicó.
supplier_response.notesstringopcionalNotas de la última respuesta del proveedor. null si todavía no respondió.
supplier_response.qualitystringopcionalCalidad de la respuesta interpretada por la IA (por ejemplo, concrete).
created_atdatetimeopcionalFecha de creación del seguimiento.
updated_atdatetimeopcionalFecha de la última modificación.
Respuesta - 200 OK
{
  "data": [
    {
      "id": 8821,
      "po_code": "OC-2026-0455",
      "line_position": 10,
      "source": "api",
      "supplier": {
        "id": 42,
        "name": "Distribuidora Industrial SA",
        "tax_id": "30712345678",
        "erp_code": "PROV-CAL-00123"
      },
      "supplier_name": "Distribuidora Industrial SA",
      "material_code": "MAT-001234",
      "description": "Rodamiento SKF 6205",
      "quantity_ordered": 50,
      "quantity_pending": 20,
      "unit_of_measure": "UN",
      "po_date": null,
      "original_delivery_date": "2026-10-15",
      "eta": "2026-10-18",
      "actual_delivery_date": null,
      "received_quantity": null,
      "status": "CONFIRMED_DELAYED",
      "is_urgent": false,
      "paused": false,
      "pause_reason": null,
      "followup_count": 2,
      "last_followup_sent_at": "2026-09-10T13:05:00+00:00",
      "next_followup_date": "2026-09-17T00:00:00+00:00",
      "supplier_response": {
        "responded_at": "2026-09-12T09:40:00+00:00",
        "confirmed_delivery_date": "2026-10-18",
        "delay_reason": "Demora del fabricante",
        "notes": "Se despachó el lote parcial, el resto llega la semana próxima.",
        "quality": "concrete"
      },
      "created_at": "2026-09-01T12:00:00+00:00",
      "updated_at": "2026-09-12T09:40:00+00:00"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 50,
  "has_more": false
}

Obtener un seguimiento de entrega

GET/api/v1/delivery-trackings/{id}API Key requerida

Obtener el detalle de un seguimiento por su id interno.

Misma forma que cada elemento de GET /delivery-trackings.

Solicitud
curl -H "X-API-Key: sk_live_abc123..." \
  https://api.gosourced.ai/api/v1/delivery-trackings/8821
Respuesta - 200 OK
{
  "id": 8821,
  "po_code": "OC-2026-0455",
  "line_position": 10,
  "source": "api",
  "supplier": {
    "id": 42,
    "name": "Distribuidora Industrial SA",
    "tax_id": "30712345678",
    "erp_code": "PROV-CAL-00123"
  },
  "supplier_name": "Distribuidora Industrial SA",
  "material_code": "MAT-001234",
  "description": "Rodamiento SKF 6205",
  "quantity_ordered": 50,
  "quantity_pending": 20,
  "unit_of_measure": "UN",
  "po_date": null,
  "original_delivery_date": "2026-10-15",
  "eta": "2026-10-18",
  "actual_delivery_date": null,
  "received_quantity": null,
  "status": "CONFIRMED_DELAYED",
  "is_urgent": false,
  "paused": false,
  "pause_reason": null,
  "followup_count": 2,
  "last_followup_sent_at": "2026-09-10T13:05:00+00:00",
  "next_followup_date": "2026-09-17T00:00:00+00:00",
  "supplier_response": {
    "responded_at": "2026-09-12T09:40:00+00:00",
    "confirmed_delivery_date": "2026-10-18",
    "delay_reason": "Demora del fabricante",
    "notes": "Se despachó el lote parcial, el resto llega la semana próxima.",
    "quality": "concrete"
  },
  "created_at": "2026-09-01T12:00:00+00:00",
  "updated_at": "2026-09-12T09:40:00+00:00"
}

Un id que no existe, o que pertenece a otra organización, devuelve 404 dlvTrackingNotFound: nunca se confirma la existencia de un seguimiento ajeno.

Historial de eventos de un seguimiento

GET/api/v1/delivery-trackings/{id}/eventsAPI Key requerida

Línea de tiempo paginada de los eventos de un seguimiento, del más antiguo al más reciente.

Parámetros de consulta

CampoTipoRequeridoDescripción
pageintegeropcionalNúmero de página (por defecto: 1).
page_sizeintegeropcionalItems por página, 1-200 (por defecto: 50).

Objeto de evento

CampoTipoRequeridoDescripción
idintegerrequeridoId interno del evento.
typestringrequeridoTipo de evento (ver la tabla de tipos).
occurred_atdatetimeopcionalFecha y hora en que ocurrió.
actor_typestringrequeridoQuién lo generó: system, supplier, user o api.
dataobjectrequeridoDetalle del evento; la forma depende de type (ver la tabla de tipos).

Tipos de evento

Tipodata
created{ source }
reactivated{ source } (el path de la API también agrega from con el estado anterior)
cycle_reset{ reason? } — reinicio del ciclo de seguimiento sin reactivación (cambio de proveedor por API, reinicio manual desde la app): la respuesta del proveedor anterior a este evento deja de ser la vigente
line_updated{ <campo>: { from, to } } — una clave por cada campo modificado (excepción: buyer_email solo lleva to, sin from)
followup_sent{ followup_number, escalated }
response_received{ confirmed_delivery_date, delay_reason, notes, quality, summary, outcome }
status_changed{ from, to, reason? }
paused{ reason }
resumed{ reason? }
urgency_changed{ is_urgent }
delivered{ from, to, reason?, actual_delivery_date, received_quantity }
cancelled{ from, to, reason? }
El evento note_added (notas manuales del comprador desde la UI) y el campo actor_ref nunca salen por esta API.
El historial arranca en la fecha de salida de esta funcionalidad: los seguimientos que ya existían antes no tienen eventos previos a ese momento (sin backfill).
Respuesta - 200 OK
{
  "data": [
    {
      "id": 55009,
      "type": "created",
      "occurred_at": "2026-09-01T12:00:00+00:00",
      "actor_type": "api",
      "data": { "source": "api" }
    },
    {
      "id": 55012,
      "type": "status_changed",
      "occurred_at": "2026-09-12T09:40:00+00:00",
      "actor_type": "supplier",
      "data": {
        "from": "SENT_PENDING_RESPONSE",
        "to": "CONFIRMED_DELAYED",
        "reason": "supplier_response"
      }
    }
  ],
  "total": 2,
  "page": 1,
  "page_size": 50,
  "has_more": false
}

Acciones en bloque

POST/api/v1/delivery-trackings/actionsAPI Key requerida

Aplicar una acción a hasta 200 líneas a la vez. Si se omite line_position en un target, la acción se aplica a todas las líneas abiertas de esa OC.

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
actionstringrequeridomark_delivered, cancel, pause, resume, set_urgent o unset_urgent.
targetsarrayrequeridoLíneas a las que aplicar la acción. Entre 1 y 200.
targets[].po_codestringrequeridoNúmero de OC. Máximo 100 caracteres.
targets[].line_positionintegeropcionalPosición de línea. Si se omite, aplica a todas las líneas abiertas de la OC.
reasonstringopcionalMotivo. Obligatorio para pause; opcional para el resto. Máximo 500 caracteres.
actual_delivery_datedateopcionalFecha real de entrega. Solo se usa con mark_delivered; por defecto, hoy.
received_quantityfloatopcionalCantidad recibida. Solo se usa con mark_delivered. ≥ 0.
Omitir targets[].line_position aplica la acción a todas las líneas abiertas (no entregadas ni canceladas) de esa OC.

Acciones disponibles

actionEfectoRequiere
mark_deliveredPasa a DELIVERED. actual_delivery_date usa la fecha enviada o, si no se envía, la fecha de hoy de la organización. received_quantity queda registrada si se envía.
cancelPasa a CANCELLED.
pausePausa el envío de recordatorios (sending_paused = true).reason
resumeReanuda el envío de recordatorios (sending_paused = false).
set_urgentMarca la línea como urgente.
unset_urgentDesmarca la línea como urgente.
  • Las acciones son idempotentes: repetir una acción ya aplicada no escribe un evento nuevo y devuelve uno de estos seis resultados: already_delivered, already_cancelled, already_paused, already_active (para resume), already_urgent o already_not_urgent.
  • No se puede cruzar entre estados terminales: mark_delivered sobre una línea CANCELLED, o cancel sobre una línea DELIVERED, devuelve el error dlvActionNotAllowedInStatus. Para reabrir una línea cerrada hay que reenviarla por POST /delivery-trackings (el upsert sí reactiva).
  • mark_delivered y cancel solo aplican a líneas creadas por esta API (source = "api"); sobre líneas de CSV o de la sincronización ERP devuelven dlvTrackingManagedByOtherSource. pause, resume, set_urgent y unset_urgent aplican a líneas de cualquier fuente, siempre que no estén en un estado terminal.
Solicitud
curl -X POST https://api.gosourced.ai/api/v1/delivery-trackings/actions \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "mark_delivered",
    "targets": [
      { "po_code": "OC-2026-0455", "line_position": 10 }
    ],
    "received_quantity": 50
  }'

Respuesta de las acciones

CampoTipoRequeridoDescripción
applied_countintegerrequeridoCantidad de seguimientos (no de targets del request) sobre los que se aplicó la acción. Los resultados already_* no suman acá.
error_countintegerrequeridoCantidad de seguimientos (no de targets del request) con error. Los resultados already_* no suman acá.
resultsarrayrequeridoResultado de cada target.

Objeto de resultado por target (results[])

CampoTipoRequeridoDescripción
po_codestringrequeridoNúmero de OC.
line_positionintegeropcionalPosición de línea afectada. Puede diferir del target si se omitió (una OC entera puede generar varios resultados).
idintegeropcionalId interno del seguimiento. Ausente cuando el target no matchea ningún seguimiento.
resultstringrequeridoapplied, error, o uno de los seis resultados idempotentes: already_delivered, already_cancelled, already_paused, already_active, already_urgent, already_not_urgent.
statusstringopcionalEstado del seguimiento después de la acción.
codestringopcionalCódigo del error (dlv*). Solo presente cuando result es error.
messagestringopcionalDetalle del error en inglés, pensado para logs.
Respuesta - 200 OK
// 200 OK
{
  "applied_count": 1,
  "error_count": 0,
  "results": [
    {
      "po_code": "OC-2026-0455",
      "line_position": 10,
      "id": 8821,
      "status": "DELIVERED",
      "result": "applied"
    }
  ]
}

Códigos de error — seguimiento de entregas

Códigos propios de los endpoints de seguimiento de entregas. Cinco son errores de la solicitud completa (columna HTTP 400/403/404); el resto viaja en el campo code de cada línea o target con error, dentro de una respuesta 200. Un campo obligatorio ausente o inválido (descripción, cantidades, proveedor sin código ERP ni CUIT, pendiente mayor que pedida) no tiene código propio: rechaza el batch completo con 422 y el detalle estándar de validación.

CódigoHTTPSignificado
dlvFeatureDisabled403El seguimiento de entregas no está habilitado para esta organización.
dlvTrackingNotFound404No se encontró el seguimiento (id inexistente o de otra organización).
dlvTrackingManagedByOtherSource200Esta línea la administra otra fuente (CSV o sincronización ERP).
dlvSupplierReferenceConflict200El código ERP y el CUIT corresponden a proveedores distintos.
dlvSupplierReferenceAmbiguous200El código ERP o el CUIT corresponde a más de un proveedor.
dlvDuplicateLineInBatch200La línea (mismo po_code y line_position) aparece más de una vez en el envío.
dlvNothingPending200La línea no tiene nada pendiente (quantity_pending = 0): no se crea un seguimiento para reclamar 0 unidades.
dlvConcurrentUpsert200La línea se modificó al mismo tiempo desde otro envío. Reintentá.
dlvLineWriteFailed200No se pudo guardar la línea. Revisá los datos e intentá de nuevo (no es un problema de concurrencia).
dlvInvalidStatusFilter400El valor de status no es un estado válido.
dlvInvalidSince400Fecha inválida en since. Usá formato ISO 8601.
dlvPauseReasonRequired400Para pause hay que indicar un reason.
dlvActionNotAllowedInStatus200La acción no aplica al estado actual del seguimiento.
200 = va dentro de la respuesta, en el code de una línea o target puntual (el resto del batch sigue). 400/403/404 = rechaza la solicitud completa. 422 = validación del cuerpo (campo obligatorio ausente o inválido), también rechaza la solicitud completa. dlvTrackingNotFound es las dos cosas: 404 en los GET por id, y por target dentro de /actions.

Webhooks

Recibí eventos en tiempo real cuando cambia una orden de compra, una requisición o un seguimiento de entrega, en vez de tener que consultarlos por polling.

Configuración

Los webhooks se configuran desde la aplicación (no por API key), con un usuario organization_admin, en Mi Organización → Desarrolladores → Webhooks.

Al crear un webhook se genera un secret que se muestra una sola vez: guardalo, se usa para verificar la firma de cada entrega.

Envelope

Todo evento llega con esta forma:

Evento
{
  "id": "evt_5f2a1c9b8e7d4a3f1b2c",
  "type": "delivery_tracking.status_changed",
  "created_at": "2026-09-17T14:05:00+00:00",
  "data": {
    "tracking": "... mismo objeto que devuelve GET /delivery-trackings/{id} ...",
    "event": "... mismo objeto que devuelve GET /delivery-trackings/{id}/events ..."
  }
}

Headers

CampoTipoRequeridoDescripción
X-Webhook-SignaturestringrequeridoFirma HMAC-SHA256 del cuerpo crudo de la solicitud, con el prefijo sha256=.
X-Webhook-EventstringrequeridoEl type del evento — el mismo valor que el campo type de nivel superior del envelope. No existe data.type: data es directamente el recurso (o el par tracking / event en los eventos de seguimiento).
X-Webhook-Delivery-IdstringrequeridoId del evento, no de este intento de entrega en particular: es el mismo valor en cada reintento y en cada webhook suscrito que lo recibe (igual que id en el cuerpo) — por eso sirve para deduplicar.

Verificar la firma

Calculá el HMAC-SHA256 sobre los bytes crudos del cuerpo de la solicitud (antes de parsear el JSON) usando el secret del webhook, y compará con X-Webhook-Signature con una comparación de tiempo constante.

Python
import hashlib
import hmac

def verify_webhook(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
    if not signature_header or not signature_header.startswith("sha256="):
        return False
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    # compare_digest on two strings of different length returns False,
    # it never raises — safe even with a malformed header.
    return hmac.compare_digest(expected, signature_header)
Node.js
const crypto = require("crypto");

function verifyWebhook(secret, rawBody, signatureHeader) {
  if (!signatureHeader || !signatureHeader.startsWith("sha256=")) {
    return false;
  }
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const expectedBuf = Buffer.from(expected);
  const receivedBuf = Buffer.from(signatureHeader);
  // timingSafeEqual throws on a length mismatch — check first, it never
  // needs to be constant-time (the lengths themselves aren't a secret).
  if (expectedBuf.length !== receivedBuf.length) {
    return false;
  }
  return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
La firma se calcula sobre el cuerpo CRUDO, tal como llega por la red. Si tu framework ya parseó el JSON antes de que puedas acceder a los bytes originales, la verificación va a fallar aunque la firma sea correcta — necesitás el body sin procesar.

Reintentos

Si la entrega falla (timeout, error de red o una respuesta que no sea 2xx), se reintenta hasta 3 veces más (4 intentos en total: el envío inicial más 3 reintentos). Cada intento fallido programa el siguiente con un backoff mínimo de 1, 5 y 15 minutos; ese reintento se dispara recién la próxima vez que corre el barrido de reintentos (reminder-checker), que se ejecuta cada hora en horario laboral argentino — el tiempo real hasta el próximo intento puede ser mayor a esos minutos.

  • Intento 1: inmediato, al momento del evento.
  • Intento 2: no antes de 1 minuto después.
  • Intento 3: no antes de 5 minutos después.
  • Intento 4: no antes de 15 minutos después. Si también falla, la entrega queda FAILED.
Un mismo evento puede llegar más de una vez (reintentos, reprocesamiento). Deduplicá por el id del evento (o por el header X-Webhook-Delivery-Id, que es el mismo valor).

Eventos disponibles

typedataCuándo se dispara
purchase_order.createdobjeto de la OCSe creó una nueva orden de compra.
purchase_order.updatedid, code, status, reasonCambió el estado u otro dato de una orden de compra (por ejemplo, un rechazo).
requisition.discardedexternal_id, requisition_id, discarded_by, discarded_atSe descartó una requisición (por ejemplo, cancelada desde el ERP con POST /requisitions/cancel).
delivery_tracking.response_receivedtracking, eventEl proveedor respondió el pedido de confirmación de entrega.
delivery_tracking.status_changedtracking, eventCambió el estado del seguimiento (incluye la reactivación de una línea previamente cerrada, que vuelve a PENDING_SCHEDULE).
delivery_tracking.deliveredtracking, eventEl seguimiento pasó a DELIVERED.
delivery_tracking.cancelledtracking, eventEl seguimiento pasó a CANCELLED.
El shape exacto de data en purchase_order.created/updated no siempre es el mismo: depende de qué disparó el evento (una adjudicación dividida manda un resumen más chico que una completa; algunos triggers usan po_id en vez de id, o agregan updated_fields en vez de reason). Tratalos como una señal para volver a leer el recurso por GET, no como un contrato fijo.
En el payload del webhook, el bloque supplier del tracking NO incluye erp_code (a diferencia de GET /delivery-trackings, que sí lo trae).
Los eventos que se originan en un proceso en segundo plano (por ejemplo, la respuesta de un proveedor por mail) pueden demorar hasta una hora en dispararse. Para no depender solo del webhook, usá GET /delivery-trackings?since= como reconciliación periódica.

Referencia de esquemas

Referencia rápida de todos los esquemas de solicitud/respuesta usados en los endpoints.

Estados de requisición

EstadoDescripción
PENDINGImportada, esperando revisión de un comprador en Sourced.
LAUNCHEDEl comprador lanzó la requisición como Solicitud de Compra.
DISCARDEDLa requisición fue descartada manualmente.
SUPERSEDEDSe importó una versión más nueva con el mismo external_id.

Estados de orden de compra

EstadoDescripción
DRAFTOC creada en Sourced, pendiente de sincronización al ERP.
CREATEDOC creada en el ERP externo, pendiente de aprobación.
CONFIRMEDOC aprobada completamente en el ERP externo.
REJECTEDOC rechazada en el ERP externo. La PR asociada se reabre automáticamente.
CANCELLEDCompra caída. La OC se cancela y la PR asociada también - nada se reabre.
Próximamente se agregarán estados adicionales como RECEIVED (mercadería recepcionada), INVOICED (facturada), entre otros, para reflejar el ciclo de vida completo de la orden de compra.

Códigos de referencia

Catálogos de monedas y tipos de pago aceptados por la API. Usalos para mapear estos valores con los códigos equivalentes en tu ERP.

Monedas

Sourced no usa un enum cerrado de monedas: cualquier código ISO 4217 válido de 3 letras es aceptado. Las monedas más utilizadas por nuestros clientes son:

CódigoMoneda
ARSPeso argentino
USDDólar estadounidense
EUREuro
BRLReal brasileño
Mandá el código ISO 4217 directamente en el campo currency (ej: "currency": "EUR"). Sourced lo persiste tal cual y lo devuelve igual en las respuestas.

Tipos de pago (payment_terms_code)

Catálogo global de condiciones de pago disponibles en Sourced. Usá el código (columna Code) en el campo payment_terms_code de la PO. El campo type indica la naturaleza del pago: IMMEDIATE (al recibir), ADVANCE (anticipado), NET_DAYS (a X días de la factura).

CódigoNombreTipoDíasDescripción
CODCash On DeliveryIMMEDIATE0Pago contra entrega
IMMImmediate PaymentIMMEDIATE0Pago inmediato / Contado
ADV100Advance PaymentADVANCE0Pago por adelantado 100%
ADV50Advance 50%ADVANCE0Pago por adelantado del 50%
NET7Net 7 DaysNET_DAYS7Pago a 7 días de la fecha de factura
NET10Net 10 DaysNET_DAYS10Pago a 10 días de la fecha de factura
NET15Net 15 DaysNET_DAYS15Pago a 15 días de la fecha de factura
NET20Net 20 DaysNET_DAYS20Pago a 20 días de la fecha de factura
NET21Net 21 DaysNET_DAYS21Pago a 21 días de la fecha de factura
NET30Net 30 DaysNET_DAYS30Pago a 30 días de la fecha de factura
EOM30End of Month + 30NET_DAYS30Pago a fin de mes más 30 días
NET35Net 35 DaysNET_DAYS35Pago a 35 días de la fecha de factura
NET40Net 40 DaysNET_DAYS40Pago a 40 días de la fecha de factura
NET45Net 45 DaysNET_DAYS45Pago a 45 días de la fecha de factura
NET60Net 60 DaysNET_DAYS60Pago a 60 días de la fecha de factura
NET75Net 75 DaysNET_DAYS75Pago a 75 días de la fecha de factura
NET90Net 90 DaysNET_DAYS90Pago a 90 días de la fecha de factura
El catálogo es global a toda la plataforma y rara vez cambia. Si necesitás un código adicional para tu integración, contactanos.

Unidades de medida

Códigos de unidad de medida aceptados en items de requisiciones. Se envían en el campo unit_of_measure.

CódigoUnidad
EAUnidad
PCSPiezas
KGKilogramo
GGramo
LBLibra
OZOnza
MMetro
CMCentímetro
MMMilímetro
INPulgada
FTPie
YDYarda
LLitro
MLMililitro
GALGalón
QTCuarto de galón
PTPinta
FL_OZOnza líquida
M2Metro cuadrado
CM2Centímetro cuadrado
FT2Pie cuadrado
IN2Pulgada cuadrada
YD2Yarda cuadrada
M3Metro cúbico
CM3Centímetro cúbico
FT3Pie cúbico
IN3Pulgada cúbica
YD3Yarda cúbica
BOXCaja
CASECaja/Estuche
PACKPaquete
SETJuego
KITKit
BUNDLEManojo
ROLLRollo
SHEETHoja/Lámina
PALLETPaleta
DRUMTambor/Bidón
BAGBolsa
BOTTLEBotella
CENCentena
Si enviás una unidad no reconocida, se almacena tal cual. Recomendamos usar los códigos estándar para que la IA pueda normalizar correctamente las cotizaciones de proveedores.

Flujo de integración

Patrón de integración típico para una sincronización ERP programada (ej: cron job cada 15 minutos):

1

Recopilar nuevas requisiciones del ERP

Consultar tu ERP por requisiciones aprobadas que aún no fueron sincronizadas con Sourced.

2

Verificar existentes (opcional)

Llamar a POST /requisitions/check-existing con external_ids para filtrar requisiciones ya sincronizadas.

3

Importar requisiciones

Llamar a POST /requisitions con el lote de requisiciones nuevas/actualizadas. Guardar los requisition_ids devueltos.

4

Consultar órdenes de compra nuevas

Llamar a GET /purchase-orders?since={last_sync_timestamp}&status=DRAFT para obtener OC nuevas. Crear la OC en tu ERP y llamar a POST /purchase-orders/{id}/status con {"status": "CREATED"}.

5

Confirmar OC aprobadas

Cuando la OC se apruebe en tu ERP, llamar a POST /purchase-orders/{id}/status con {"status": "CONFIRMED"} para cerrar el ciclo.

Pseudocódigo - Cron job de sincronización
# Step 1: Get pending requisitions from ERP
ERP_REQS=$(query_erp_pending_requisitions)

# Step 2: Check which ones already exist in Sourced
EXISTING=$(curl -s -X POST \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{"external_ids": $ERP_REQ_IDS}" \
  https://api.gosourced.ai/api/v1/requisitions/check-existing)

# Step 3: Import only new requisitions
NEW_REQS=$(filter_not_found $ERP_REQS $EXISTING)
curl -s -X POST \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{"requisitions": $NEW_REQS}" \
  https://api.gosourced.ai/api/v1/requisitions

# Step 4: Pull new POs since last sync
POS=$(curl -s \
  -H "X-API-Key: $API_KEY" \
  "https://api.gosourced.ai/api/v1/purchase-orders?since=$LAST_SYNC&status=DRAFT")

# Step 5: Create POs in ERP and update status
for PO in $POS; do
  create_po_in_erp $PO
  curl -s -X POST \
    -H "X-API-Key: $API_KEY" \
    -H "Content-Type: application/json" \
    -d "{"status": "CREATED", "external_id": "$ERP_PO_NUMBER"}" \
    "https://api.gosourced.ai/api/v1/purchase-orders/$PO_ID/status"
done

# Step 6: When PO is approved in ERP, confirm it
curl -s -X POST \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "CONFIRMED"}' \
  "https://api.gosourced.ai/api/v1/purchase-orders/$PO_ID/status"

Ejemplo completo

Una solicitud de importación realista con todos los campos disponibles:

Solicitud de importación completa
curl -X POST https://api.gosourced.ai/api/v1/requisitions \
  -H "X-API-Key: sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "requisitions": [
      {
        "external_id": "CAL-REQ-2026-0042",
        "description": "Repuestos bomba centrífuga Planta Norte",
        "requester_name": "Carlos Rodríguez",
        "requester_email": "carlos.rodriguez@empresa.com",
        "comments": "Urgente: bomba fuera de servicio desde 03/03. <b>Necesitamos entrega express.</b>",
        "delivery_address": "Av. Industrial 4500, Parque Industrial Pilar, Buenos Aires",
        "delivery_address_code": "PLANTA-NORTE",
        "desired_delivery_lead_time_days": 7,
        "created_date": "2026-03-04T09:15:00Z",
        "offer_deadline": "2026-03-10T18:00:00Z",
        "total_estimated_value": 1250000.00,
        "currency": "ARS",
        "department_code": "MANT-001",
        "priority_level": "HIGH",
        "items": [
          {
            "external_line_id": "CAL-REQ-2026-0042-L10",
            "description": "Sello mecánico para bomba centrífuga KSB ETA 50-200",
            "quantity": 2,
            "unit_of_measure": "UN",
            "target_price": 185000.00,
            "estimated_price": 370000.00,
            "currency": "ARS",
            "category": "Repuestos Bombas",
            "material_code": "MAT-BOM-0234",
            "desired_delivery_lead_time_days": 10,
            "detail": "Sello tipo cartucho, material: carburo de silicio / carburo de silicio",
            "specifications": {
              "manufacturer_code": "KSB-SEAL-50200",
              "manufacturer_name": "KSB",
              "manufacturer_description": "Mechanical seal for ETA 50-200 centrifugal pump",
              "buyer_code": "MAT-BOM-0234",
              "buyer_code_description": "Sello mecánico bomba KSB ETA 50-200",
              "buyer_code_system": "Calipso",
              "technical_specs": "Diámetro eje: 35mm, Material caras: SiC/SiC, Elastómeros: Viton",
              "requirements": "Debe incluir certificado de calidad. Preferencia por repuesto original KSB."
            }
          },
          {
            "external_line_id": "CAL-REQ-2026-0042-L20",
            "description": "Rodamiento SKF 6310-2RS",
            "quantity": 4,
            "unit_of_measure": "UN",
            "target_price": 45000.00,
            "currency": "ARS",
            "category": "Rodamientos",
            "material_code": "MAT-ROD-0089",
            "specifications": {
              "manufacturer_code": "6310-2RS1",
              "manufacturer_name": "SKF",
              "technical_specs": "50x110x27mm, sellado ambos lados, grasa estándar"
            }
          },
          {
            "external_line_id": "CAL-REQ-2026-0042-L30",
            "description": "Aceite lubricante ISO VG 68",
            "quantity": 20,
            "unit_of_measure": "LT",
            "target_price": 5500.00,
            "currency": "ARS",
            "category": "Lubricantes",
            "specifications": {
              "technical_specs": "Aceite mineral ISO VG 68, índice de viscosidad > 95"
            }
          }
        ],
        "attachments": [
          {
            "url": "https://erp.empresa.com/files/plano-bomba-eta-50-200.pdf",
            "filename": "plano_bomba_KSB_ETA_50-200.pdf",
            "description": "Plano de despiece bomba KSB ETA 50-200",
            "file_type": "application/pdf"
          }
        ],
        "raw_data": {
          "calipso_doc_type": "SOL",
          "calipso_doc_number": "0042",
          "calipso_branch": "001",
          "approved_by": "María González",
          "cost_center": "CC-MANT-NORTE"
        }
      }
    ]
  }'
Respuesta - 200 OK
{
  "success": true,
  "created_count": 1,
  "updated_count": 0,
  "skipped_count": 0,
  "error_count": 0,
  "errors": [],
  "requisition_ids": [1234]
}