ProbabilityAPI de integración · v1
sin token

Introducción

El API de Probability permite que tu software (ERP, POS, sistema de bodega o desarrollo propio) cree órdenes, sincronice catálogo e inventario, consulte bodegas y reciba por webhook firmado el resultado de cada operación y los cambios de estado de sus órdenes.

Cómo empezar

  1. En Probability entra a Integraciones → Crear integración → API de integración → API de Probability.
  2. Ponle el nombre de tu software y, si ya la tienes, la URL donde recibirás webhooks.
  3. Al crearla verás una sola vez el client_id (pk_…), el client_secret (sk_…), el secreto de webhook (whsec_…) y el identificador de tu negocio (biz_…). Guárdalos en un lugar seguro.
  4. Pide un token (abajo, en Autenticación) y prueba cualquier endpoint desde su consola.

Forma de las respuestas

Toda respuesta trae request_id, también en la cabecera X-Request-Id. Es el dato que debes compartir con soporte.

CasoForma
Lectura{ request_id, data }
Lista{ request_id, data, total, page, page_size, total_pages } · page_size máximo 100
Escritura aceptada202 { request_id, transaction_id, status, kind, replayed, status_url }
Error{ request_id, error: { code, message, details } }
Las consolas de esta página hacen llamadas reales al entorno elegido arriba. En producción, una orden creada aquí es una orden real de tu negocio.

Autenticación

El API usa OAuth 2.0 client_credentials. Cambias tu client_id y client_secret por un token de acceso que dura 1 hora y lo envías en cada llamada:

Authorization: Bearer pat_…

El endpoint de token acepta el cuerpo como formulario (application/x-www-form-urlencoded), como JSON, o las credenciales por HTTP Basic. Cuando el token vence, pide otro. Regenerar las llaves en Probability revoca los tokens activos.

Obtener un token para esta página

El secreto solo vive en la memoria de esta pestaña. El token se guarda en sessionStorage y se borra al cerrar la pestaña; nunca en localStorage.

Permisos

PermisoHabilita
orders:readGET /orders, GET /orders/{id}
orders:writePOST /orders, POST /orders/{id}/status
products:read / products:writeGET /products / POST /products
inventory:read / inventory:writeGET /inventory / PUT /inventory
warehouses:readGET /warehouses

Los permisos se encienden o apagan desde la integración en Probability. Un permiso apagado responde 403 insufficient_scope.

Transacciones y webhooks

Las lecturas responden en el momento. Las escrituras (crear órdenes, cambiar estados, catálogo e inventario) son asíncronas:

  1. Validación temprana. Lo que se puede saber sin procesar responde 400 validation_error con details campo por campo: orden sin ítems, bodega de otro negocio, SKU repetido en el lote, estado desconocido…
  2. Aceptada. Si pasa, respondemos 202 con transaction_id. La operación se encola y la ejecuta el módulo de Probability que corresponde.
  3. Resultado por webhook. Cuando termina, enviamos el evento a tu URL. Si no te llega, consulta GET /transactions/{id}. Una transacción que no termina en 30 minutos pasa a failed con código timeout y emite transaction.failed.

Idempotencia

Envía la cabecera Idempotency-Key en toda escritura. La misma llave con el mismo cuerpo devuelve la misma transacción (replayed: true y cabecera Idempotent-Replayed: true). La misma llave con otro cuerpo responde 409 idempotency_key_reused. Un external_id de orden ya usado con otra llave responde 409 duplicate_external_id.

Formato del webhook


  
EventoCuándo llega
order.created / order.rejectedResultado de POST /orders.
order.status_applied / order.status_rejectedResultado de POST /orders/{id}/status.
order.status_changedCualquier cambio de estado de una orden de tu integración, venga de donde venga (operación, transportadora, otra persona).
products.upsertedResultado de POST /products, por SKU.
inventory.updatedResultado de PUT /inventory; los SKU desconocidos se reportan.
transaction.failedLa escritura no se pudo completar.
webhook.pingPrueba enviada desde Probability o con POST /webhooks/test.

Verificar la firma

Cada webhook trae X-Probability-Signature: t=<unix>,v1=<hex>, donde hex = HMAC-SHA256(whsec, "<t>." + cuerpo crudo). Verifica sobre el cuerpo sin parsear, rechaza si t tiene más de 5 minutos y compara en tiempo constante. También llegan X-Probability-Event y X-Probability-Event-Id; usa este último para descartar duplicados.

// Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''));
}

# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split('=', 1) for p in header.split(','))
    if abs(time.time() - int(parts['t'])) > 300:
        return False
    expected = hmac.new(secret.encode(), parts['t'].encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get('v1', ''))

Reintentos

Si tu URL no responde 2xx, reintentamos a los 10 s, 60 s, 300 s y 300 s (5 intentos en total). Después la entrega queda como fallida y la ves en Uso del API → Entregas de webhook. Responde 2xx rápido y procesa después.

Errores y límites

HTTPcodeQué significa
400validation_errorLa solicitud tiene errores; mira details.
400invalid_jsonEl cuerpo no es un JSON válido para el endpoint.
400unsupported_grant_type / invalid_requestToken: solo grant_type=client_credentials, con client_id y client_secret.
401invalid_clientclient_id o client_secret inválidos.
401invalid_tokenToken ausente, inválido o vencido.
403insufficient_scopeLa credencial no tiene permiso para la operación.
403integration_inactiveLa integración está desactivada en Probability.
404not_foundEl recurso no existe o no es de tu negocio.
409duplicate_external_idYa existe una orden con ese external_id en tu integración.
409idempotency_key_reusedLa Idempotency-Key ya se usó con otro cuerpo.
413payload_too_largeEl cuerpo supera 2 MB.
429rate_limited / slow_downDemasiadas solicitudes; respeta Retry-After.
503queue_unavailableNo pudimos encolar la escritura; reintenta con la misma Idempotency-Key.
500internal_errorError nuestro; comparte el request_id.

Límites

  • 20 solicitudes por segundo por integración, con ráfagas de hasta 60.
  • /oauth/token: 1 por segundo, ráfagas de hasta 10.
  • Cuerpo máximo de 2 MB. Lotes de hasta 100 productos o 500 filas de inventario; órdenes de hasta 200 ítems.
  • Paginación: page y page_size (por defecto 10, máximo 100).