Status of service: Sandbox assets.Operating endpoint: https://api.sandbox.jaak.ai/api/v1/kyc/sandbox/simulate. Timeouts note (§ 5.8): prefixes SET* They come back HTTP 408 (Request Timeout) instead of 504. That prevents the gateway from interpreting the stage as a transitional failure and retracting - 408 gets to the integrator intact. Hearing: integrators (client developers). affected environment: https://api.sandbox.jaak.ai.


1. Objective

To allow the integrators play any stage of flow KYC (approved, rejected, pending, infrastructure errors) with determinist form sending a requestId with a conventional prefix. The goal is:

  • To develop integration cover all roads (not just the happy path).
  • Building automated CI tests that do not depend on manual flows.
  • Avoid the integrator from finding out how to manage rejections / pendings up to production.

2. Scope

  • low endpoints /api/v1/kyc/* in api.sandbox.jaak.ai.
  • Not applicable production - production always uses true logic.
  • Not applicable to other endpoints (/api-key, /auth/*, /companies) in this first version.

3. Key concepts

TermMeaning
requestIdUnique identifier that the client sends to the body of POST /api/v1/kyc/flowThe Sandbox's dashboard reads to decide what stage to simulate.
DialcodeFirst 3-5 letters from requestId That Sandbox recognizes as a predefined stage.
Dyspatchercomponent of the sandbox that mapes the requestId to a concrete response.
Random determinist modeIf his requestId does not have a recognised prefix, the sandbox calculates a hash and chooses a weighted stage. The same requestId always returns the same stage.

4. Convention requestId

4.1 Format

S<resultado><step>[<detalle>][<modificador>]-<N>
PositionLettersMeaning
1SSandbox (fixed)
2A / R / P / EApsudd Rejected · Pending Eor
3H / D / L / F / B / XHappy (with no specific step) Dcare Liveness · Face Blacklist X-checks (data consistency)
4 (optional)letterDetail of step (e. g. P in SRLP = Photo spokesperson)
5 (optional)letterEdge case modifier
sufix-<N>Random number to maintain idempotency by eventId without affecting the stage

Regex that recognizes dyspatcher:

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

4.2 Parting "greedy"

The dyspatcher tries to match with the prefix more specific first:

  1. 5 letters → very specific stage
  2. 4 letters → step detail
  3. 3 letters → general result

If you requestId does not kill any variant, falls to Random determinist mode (§ 6) - never to a parting error.


5. A complete catalogue of prefixes

5.1 Happy paths (Approved)

DialcodeScenarioTypical use case
SAH-NGeneric Approved HappySmoke Whole Flow Test
SAHM-NApproved Happy MX (CURP and RFC valid, RENAPO )Inputs focused in Mexico
SAHP-NApproved Happy Passport (international document)Customers with foreign users
SAL-NApproved Low-confidence (passing with border scors)Check that IU does not alert low scores if the result is approved

5.2 Rejected by document

DialcodeScenario
SRDE-NDocument Expitted - Expand ID
SRDT-Ndocument Tampered - visible impairment
SRDQ-Ndocument low quality - blurred image / reflections / cut
SRDW-NWRong document type - user uploaded home voucher rather than ID
SRDO-NOCR Partial / Failure - Unextracted fields

5.3 Rejected by Liveness (Life detection)

DialcodeScenario
SRLP-NSpoof PHoto - photo printed in front of camera
SRLS-NSpoof Screan - screen replay
SRLM-NSpoof Mask - 3D mask
SRLC-NChallenge failed - the user did not run the requested gesture

5.4 Rejected by Face (One-to-One)

DialcodeScenario
SRFN-NNo match - the face of the selfie does not match that of the ID
SRFM-NMtipple faces - more than a visible face in the selfie
SRFO-NOrbscuse - dark lenses / cover / obstruction

5.5 Rejected by Blacklist (blacklist)

DialcodeScenario
SRBO-NOrFEC hit
SRBP-NPEP (Political Expand)
SRBI-NInternal fraud - internal blacklist of JAAK
SRBC-NCMilling record - criminal record

5.6 Rejected by Cross-checks (data consistency)

DialcodeScenario
SRXN-NName mismatch - name of OCR doesn't match that of the Submitter.
SRXD-NDOB mismatch - date of birth inconsistent
SRXI-NID number mismatch - document number inconsistent
SRXC-NCURP not found / mismatch

5.7 Pending (manual review required)

DialcodeScenario
SPR-NManual pending Ravoid (generic)
SPLB-NPending - Liveness Bordline (unchanged score)
SPFB-NPending - Face BOrderline (indeterminate match)

5.8 Infrastructure errors

DialcodeScenarioHTTP
SET-NTgeneric imeout408
SETD-NTimeout at Doffice408
SETL-NTimeout at Liveness408
SETO-NTimeout at OrTO / Face408
SEI-NInternal Server Error500
SEQ-NQUota exceeded402
SER-NRate limited429
SEA-NAInvalid leave (licence 401)401

6. Random determinist mode

If his requestId doesn't start with a recognised prefixThe sandbox calculates a stage as follows:

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

6.1 Table of weights by default

WeightBucket rangeScenario assigned
650–64SAH (40), SAHM (15), SAHP (5), SAL (5)
1565–79Reject distributed: 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 Property guaranteed

  • Determinism: requestId → The same stage always. Reducible in CI.
  • Distribution: crc32 production of a uniform distribution about 100 buckets, with a result 100 requestId different scenarios cover them as per the table.
  • If you need a concrete stage: use an explicit § 5 prefix (always gain over random mode).

7. Examples of use

7.1 cURL

Always approve (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 spoof rejection of living with printed photo:

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" }, ... }

Simple license 401 error:

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": "Invalid or absent leave"}

Automatic coverage of scenarios (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. Response shape

The dyspatcher respect the same shape That endpoint production. They only change the concrete values according to the stage. Thus your client's parser can be integrated without special branches for sandbox.

8.1 Approved (example with 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 (example with 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 (example with 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 (example with SEI / SEA)

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

9. FAQ

Do I have to change my existing integration?

No. If you send a requestId an arbitrary (for example an UUID), dyspatcher will process them by Random determinist mode (§ 6) and your integration will continue to get valid answers. 65% of the time you will get happy paths - distribution reflect production reality.

What happens if I do not send requestId?

The server generates it automatically. In that case, the stage is chosen according to the random distribution of § 6 (but will not be reproducible between calls). To have control, always send a requestId explicit.

Are the prefixes homemade-sensitive?

That's right. capital letters. sah-42 No matchea, will be treated as a determinist randomization.

How do I know what version of the catalogue's been deployed?

Consultation GET https://api.sandbox.jaak.ai/api/v1/kyc/sandbox-info (optional endpoint that returns the version of the active catalogue - will be added with implementation).

Can I combine multiple scenarios with a single request?

Not at v1. requestId a a single stage. If you require, for example, "expired document + livelihood spoof" combined, report the case of use and we evaluate an added specific prefix.

Does this mechanism apply to production?

No. Just at api.sandbox.jaak.ai (namespace) api-dummy). In production prefixes are ignored - actual flow runs.

What do I do if I find a difference between Sandbox's response and production?

Report to soporte@jaak.ai including: requestId, eventIdand examples of both responses. should To be identical with the actual, any deviation is a bug.


10. References


11. Validity and versionation

FieldValue
StateSandbox assets
Catalogue versionv0.1
Last review2026-04-17
External contactsoporte@jaak.ai

Annex A - Checklist for integrators

  • requestId explicit at each request (do not depend on autogeneration).
  • Your CI tests at least one SAH and one for each rejection family (SRD*, SRL*, SRF*, SRB*).
  • Your code runs status: "pending_review" (check SPR, SPLB, SPFB).
  • Your code handles HTTP 4xx / 5xx errors without crashear (test with SER, SEI, SET).
  • Do never use these prefixes in production (have only Sandbox effect).