recurso

Estado del servicio: Activo en sandbox. Endpoint operativo: https://api.sandbox.jaak.ai/api/v1/kyc/sandbox/simulate. Nota sobre timeouts (§5.8): los prefijos SET* devuelven HTTP 408 (Request Timeout) en lugar de 504. Esto evita que el gateway interprete el escenario como un fallo transitorio y haga retry — 408 llega intacto al integrador. Audiencia: integradores (desarrolladores de clientes). Ambiente afectado: https://api.sandbox.jaak.ai.


1. Objetivo

Permitir a los integradores reproducir cualquier escenario del flujo KYC (approved, rejected, pending, errores de infraestructura) de forma determinista enviando un requestId con un prefijo convencional. El objetivo es:

  • Desarrollar integraciones cubriendo todos los caminos (no solo el happy path).
  • Construir tests automatizados en CI que no dependan de flujos manuales.
  • Evitar que el integrador descubra el manejo de rechazos/pendings hasta producción.

2. Alcance

  • Endpoints bajo /api/v1/kyc/* en api.sandbox.jaak.ai.
  • No aplica a producción — producción siempre usa la lógica real.
  • No aplica a otros endpoints (/api-key, /auth/*, /companies, etc.) en esta primera versión.

3. Conceptos clave

TérminoSignificado
requestIdIdentificador único que el cliente envía en el cuerpo de POST /api/v1/kyc/flow. El dispatcher del sandbox lo lee para decidir qué escenario simular.
PrefijoPrimeras 3–5 letras del requestId que el sandbox reconoce como escenario predefinido.
DispatcherComponente del sandbox que mapea el requestId a una respuesta concreta.
Modo determinista aleatorioSi el requestId no tiene un prefijo reconocido, el sandbox calcula un hash y escoge un escenario de forma ponderada; el mismo requestId siempre devuelve el mismo escenario.

4. Convención del requestId

4.1 Formato

S<resultado><step>[<detalle>][<modificador>]-<N>
PosiciónLetrasSignificado
1SSandbox (fijo)
2A / R / P / EApproved · Rejected · Pending · Error
3H / D / L / F / B / XHappy (sin step específico) · Document · Liveness · Face · Blacklist · X-checks (consistencia de datos)
4 (opcional)letraDetalle del step (ej. P en SRLP = Photo spoof)
5 (opcional)letraModificador edge case
sufijo-<N>Número aleatorio para mantener idempotencia por eventId sin afectar al escenario

Regex que reconoce el dispatcher:

^S[ARPE][HDLFBX][A-Z]{0,2}(-\d+)?$

4.2 Parsing "greedy"

El dispatcher intenta matchear con el prefijo más específico primero:

  1. 5 letras → escenario muy específico
  2. 4 letras → detalle del step
  3. 3 letras → resultado general

Si tu requestId no matchea ninguna variante, cae al modo determinista aleatorio (§6) — nunca a un error de parsing.


5. Catálogo completo de prefijos

5.1 Happy paths (Approved)

PrefijoEscenarioCaso de uso típico
SAH-NApproved Happy genéricoSmoke test del flujo entero
SAHM-NApproved Happy MX (CURP y RFC válidos, RENAPO match)Integraciones enfocadas en México
SAHP-NApproved Happy Passport (documento internacional)Clientes con usuarios extranjeros
SAL-NApproved Low-confidence (pasa con scores borderline)Verificar que la UI no alerta scores bajos si el resultado es approved

5.2 Rechazos por Document

PrefijoEscenario
SRDE-NDocument Expired — ID caducado
SRDT-NDocument Tampered — alteración visible
SRDQ-NDocument low Quality — imagen borrosa / reflejos / recortada
SRDW-NWrong document type — usuario subió comprobante de domicilio en lugar de ID
SRDO-NOCR partial / failure — campos no extraídos

5.3 Rechazos por Liveness (detección de vida)

PrefijoEscenario
SRLP-NSpoof Photo — foto impresa frente a la cámara
SRLS-NSpoof Screen — replay de pantalla
SRLM-NSpoof Mask — máscara 3D
SRLC-NChallenge failed — el usuario no ejecutó el gesto pedido

5.4 Rechazos por Face (One-to-One)

PrefijoEscenario
SRFN-NNo match — el rostro del selfie no coincide con el del ID
SRFM-NMultiple faces — más de una cara visible en el selfie
SRFO-NObscured — lentes oscuros / cubrebocas / obstrucción

5.5 Rechazos por Blacklist (listas negras)

PrefijoEscenario
SRBO-NOFAC hit
SRBP-NPEP (Personas Expuestas Políticamente)
SRBI-NInternal fraud — blacklist interna de JAAK
SRBC-NCriminal record — antecedentes penales

5.6 Rechazos por Cross-checks (consistencia de datos)

PrefijoEscenario
SRXN-NName mismatch — el nombre del OCR no coincide con el del submitter
SRXD-NDOB mismatch — fecha de nacimiento inconsistente
SRXI-NID number mismatch — número de documento inconsistente
SRXC-NCURP not found / mismatch

5.7 Pending (requiere revisión manual)

PrefijoEscenario
SPR-NPending manual Review (genérico)
SPLB-NPending — Liveness Borderline (score indeterminado)
SPFB-NPending — Face Borderline (match indeterminado)

5.8 Errores de infraestructura

PrefijoEscenarioHTTP
SET-NTimeout genérico408
SETD-NTimeout en Document408
SETL-NTimeout en Liveness408
SETO-NTimeout en OTO / Face408
SEI-NInternal Server Error500
SEQ-NQuota exceeded402
SER-NRate limited429
SEA-NAuth / licencia inválida (licence 401)401

6. Modo determinista aleatorio

Si el requestId no empieza con un prefijo reconocido, el sandbox calcula un escenario como sigue:

bucket = crc32(requestId) % 100
scenario = weighted_pick(bucket)

6.1 Tabla de pesos por defecto

PesoRango bucketEscenario asignado
650–64SAH (40), SAHM (15), SAHP (5), SAL (5)
1565–79Rejects distribuidos: SRDE, SRLP, SRFN, SRBP, SRDW
1080–89Pending: SPR (5), SPLB (3), SPFB (2)
890–97Cross-checks: SRXN (3), SRXD (2), SRXC (3)
298–99Errors: SEI (1), SET (1)

6.2 Propiedades garantizadas

  • Determinismo: el mismo requestId → el mismo escenario siempre. Reproducible en CI.
  • Distribución: crc32 produce una distribución uniforme sobre los 100 buckets, con lo que 100 requestId distintos cubren los escenarios según la tabla.
  • Si necesitas un escenario concreto: usa el prefijo explícito de §5 (siempre gana sobre el modo aleatorio).

7. Ejemplos de uso

7.1 cURL

Aprobar siempre (happy path):

curl -X POST https://api.sandbox.jaak.ai/api/v1/kyc/flow \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "SAH-42",
    "submitter": { ... },
    "config": { ... }
  }'
# → 200 { "status": "approved", "result": { "decision": "APPROVED", ... }, ... }

Simular rechazo por spoof de liveness con foto impresa:

curl -X POST https://api.sandbox.jaak.ai/api/v1/kyc/flow \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "SRLP-1234",
    "submitter": { ... }
  }'
# → 200 { "status": "rejected", "result": { "decision": "REJECTED", "reason": "LIVENESS_SPOOF_PHOTO" }, ... }

Simular error 401 de licencia:

curl -X POST https://api.sandbox.jaak.ai/api/v1/kyc/flow \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "requestId": "SEA-999" }'
# → 401 { "eventId": "...", "statusCode": 401, "errorCode": "A001", "message": "Licencia inválida o ausente" }

Cobertura automática de escenarios (CI):

for scenario in SAH SAHM SRDE SRLP SRFN SRBP SRXN SPR SET SEI; do
  RID="${scenario}-$(date +%s%N)"
  curl -s -X POST https://api.sandbox.jaak.ai/api/v1/kyc/flow \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"requestId\":\"${RID}\"}"  \
    | jq -r --arg s "$scenario" '. | "\($s): \(.status // .errorCode)"'
done

7.2 Node.js

import axios from "axios";

const SCENARIOS = ["SAH", "SRDE", "SRLP", "SRFN", "SPR", "SEI"];

for (const prefix of SCENARIOS) {
  const requestId = `${prefix}-${Math.floor(Math.random() * 1e6)}`;
  const { data, status } = await axios.post(
    "https://api.sandbox.jaak.ai/api/v1/kyc/flow",
    { requestId },
    {
      headers: { Authorization: `Bearer ${token}` },
      validateStatus: () => true,
    },
  );
  console.log(prefix, status, data.status || data.errorCode);
}

7.3 Python

import requests, secrets

SCENARIOS = ["SAH", "SAHM", "SRDE", "SRLP", "SRFN", "SRBP", "SPR", "SEI"]

for prefix in SCENARIOS:
    request_id = f"{prefix}-{secrets.randbelow(10**6)}"
    r = requests.post(
        "https://api.sandbox.jaak.ai/api/v1/kyc/flow",
        headers={"Authorization": f"Bearer {token}"},
        json={"requestId": request_id},
    )
    body = r.json()
    print(f"{prefix}: HTTP {r.status_code} — {body.get('status', body.get('errorCode'))}")

8. Shape de respuesta

El dispatcher respeta el mismo shape que el endpoint en producción. Solo cambian los valores concretos según el escenario. Así tu parser del cliente puede integrarse sin ramas especiales para sandbox.

8.1 Approved (ejemplo con SAH)

{
  "id": "69e11ec8380058638712a280",
  "status": "approved",
  "result": { "decision": "APPROVED", "score": 0.97 },
  "steps": {
    "document": {
      "status": "ok",
      "type": "INE",
      "fields": { "name": "Juan Pérez López", "dob": "1990-05-12", "id_number": "..." }
    },
    "liveness": { "status": "ok", "score": 0.98 },
    "face":     { "status": "match", "score": 0.94 },
    "blacklist":{ "status": "clean" }
  },
  "createdAt": "2026-04-17T18:30:00Z",
  "updatedAt": "2026-04-17T18:30:02Z"
}

8.2 Rejected (ejemplo con SRLP)

{
  "id": "69e11ec8380058638712a281",
  "status": "rejected",
  "result": { "decision": "REJECTED", "reason": "LIVENESS_SPOOF_PHOTO" },
  "steps": {
    "document": { "status": "ok", ... },
    "liveness": { "status": "failed", "reason": "SPOOF_PHOTO", "score": 0.12 },
    "face":     { "status": "skipped" },
    "blacklist":{ "status": "skipped" }
  }
}

8.3 Pending (ejemplo con SPR)

{
  "id": "69e11ec8380058638712a282",
  "status": "pending_review",
  "result": { "decision": "PENDING_REVIEW", "reason": "MANUAL_REVIEW_REQUIRED" },
  "steps": {
    "document": { "status": "ok" },
    "liveness": { "status": "borderline", "score": 0.73 },
    ...
  }
}

8.4 Error (ejemplo con SEI / SEA)

{
  "eventId": "69e11ec8-3800-5863-8712-a2830000",
  "statusCode": 500,
  "errorCode": "I001",
  "message": "Internal server error"
}

9. Preguntas frecuentes

¿Tengo que cambiar mi integración existente?

No. Si ya envías un requestId arbitrario (por ejemplo un UUID), el dispatcher lo procesará por el modo determinista aleatorio (§6) y tu integración seguirá recibiendo respuestas válidas. El 65 % de las veces recibirás happy paths — la distribución refleja la realidad de producción.

¿Qué pasa si no envío requestId?

El servidor lo genera automáticamente. En ese caso, el escenario se elige según la distribución aleatoria de §6 (pero no será reproducible entre llamadas). Para tener control, envía siempre un requestId explícito.

¿Los prefijos son case-sensitive?

Sí. Usa mayúsculas. sah-42 no matchea, se tratará como aleatorio determinista.

¿Cómo sé qué versión del catálogo está desplegada?

Consulta GET https://api.sandbox.jaak.ai/api/v1/kyc/sandbox-info (endpoint opcional que devuelve la versión del catálogo activo — será añadido junto con la implementación).

¿Puedo combinar múltiples escenarios en un solo request?

No en la v1. Cada requestId mapea a un único escenario. Si necesitas, por ejemplo, "documento expirado + liveness spoof" combinados, reportá el caso de uso y evaluamos añadir un prefijo específico.

¿Este mecanismo aplica a producción?

No. Solo en api.sandbox.jaak.ai (namespace api-dummy). En producción los prefijos son ignorados — se ejecuta el flujo real.

¿Qué hago si encuentro una diferencia entre la respuesta del sandbox y producción?

Reporta a soporte@jaak.ai incluyendo: requestId, eventId, timestamp y ejemplos de ambas respuestas. El shape del sandbox debe ser idéntico al real; cualquier desviación es un bug.


10. Referencias


11. Validez y versionado

CampoValor
EstadoActivo en sandbox
Versión del catálogov0.1
Última revisión2026-04-17
Contacto externosoporte@jaak.ai

Anexo A — Checklist para integradores

  • Envías un requestId explícito en cada request (no dependas de autogeneración).
  • Tu CI prueba al menos un SAH y uno por cada familia de rechazo (SRD*, SRL*, SRF*, SRB*).
  • Tu código maneja status: "pending_review" (revisa SPR, SPLB, SPFB).
  • Tu código maneja errores HTTP 4xx/5xx sin crashear (prueba con SER, SEI, SET).
  • Nunca usas estos prefijos en producción (solo tienen efecto en sandbox).