Status of service: Sandbox assets.Operating endpoint:
https://api.sandbox.jaak.ai/api/v1/kyc/sandbox/simulate. Timeouts note (§ 5.8): prefixesSET*They come backHTTP 408(Request Timeout) instead of504. That prevents the gateway from interpreting the stage as a transitional failure and retracting -408gets 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/*inapi.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
| Term | Meaning |
|---|---|
requestId | Unique identifier that the client sends to the body of POST /api/v1/kyc/flowThe Sandbox's dashboard reads to decide what stage to simulate. |
| Dialcode | First 3-5 letters from requestId That Sandbox recognizes as a predefined stage. |
| Dyspatcher | component of the sandbox that mapes the requestId to a concrete response. |
| Random determinist mode | If 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>
| Position | Letters | Meaning |
|---|---|---|
| 1 | S | Sandbox (fixed) |
| 2 | A / R / P / E | Apsudd Rejected · Pending Eor |
| 3 | H / D / L / F / B / X | Happy (with no specific step) Dcare Liveness · Face Blacklist X-checks (data consistency) |
| 4 (optional) | letter | Detail of step (e. g. P in SRLP = Photo spokesperson) |
| 5 (optional) | letter | Edge 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:
- 5 letters → very specific stage
- 4 letters → step detail
- 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)
| Dialcode | Scenario | Typical use case |
|---|---|---|
SAH-N | Generic Approved Happy | Smoke Whole Flow Test |
SAHM-N | Approved Happy MX (CURP and RFC valid, RENAPO ) | Inputs focused in Mexico |
SAHP-N | Approved Happy Passport (international document) | Customers with foreign users |
SAL-N | Approved Low-confidence (passing with border scors) | Check that IU does not alert low scores if the result is approved |
5.2 Rejected by document
| Dialcode | Scenario |
|---|---|
SRDE-N | Document Expitted - Expand ID |
SRDT-N | document Tampered - visible impairment |
SRDQ-N | document low quality - blurred image / reflections / cut |
SRDW-N | WRong document type - user uploaded home voucher rather than ID |
SRDO-N | OCR Partial / Failure - Unextracted fields |
5.3 Rejected by Liveness (Life detection)
| Dialcode | Scenario |
|---|---|
SRLP-N | Spoof PHoto - photo printed in front of camera |
SRLS-N | Spoof Screan - screen replay |
SRLM-N | Spoof Mask - 3D mask |
SRLC-N | Challenge failed - the user did not run the requested gesture |
5.4 Rejected by Face (One-to-One)
| Dialcode | Scenario |
|---|---|
SRFN-N | No match - the face of the selfie does not match that of the ID |
SRFM-N | Mtipple faces - more than a visible face in the selfie |
SRFO-N | Orbscuse - dark lenses / cover / obstruction |
5.5 Rejected by Blacklist (blacklist)
| Dialcode | Scenario |
|---|---|
SRBO-N | OrFEC hit |
SRBP-N | PEP (Political Expand) |
SRBI-N | Internal fraud - internal blacklist of JAAK |
SRBC-N | CMilling record - criminal record |
5.6 Rejected by Cross-checks (data consistency)
| Dialcode | Scenario |
|---|---|
SRXN-N | Name mismatch - name of OCR doesn't match that of the Submitter. |
SRXD-N | DOB mismatch - date of birth inconsistent |
SRXI-N | ID number mismatch - document number inconsistent |
SRXC-N | CURP not found / mismatch |
5.7 Pending (manual review required)
| Dialcode | Scenario |
|---|---|
SPR-N | Manual pending Ravoid (generic) |
SPLB-N | Pending - Liveness Bordline (unchanged score) |
SPFB-N | Pending - Face BOrderline (indeterminate match) |
5.8 Infrastructure errors
| Dialcode | Scenario | HTTP |
|---|---|---|
SET-N | Tgeneric imeout | 408 |
SETD-N | Timeout at Doffice | 408 |
SETL-N | Timeout at Liveness | 408 |
SETO-N | Timeout at OrTO / Face | 408 |
SEI-N | Internal Server Error | 500 |
SEQ-N | QUota exceeded | 402 |
SER-N | Rate limited | 429 |
SEA-N | AInvalid 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
| Weight | Bucket range | Scenario assigned |
|---|---|---|
| 65 | 0–64 | SAH (40), SAHM (15), SAHP (5), SAL (5) |
| 15 | 65–79 | Reject distributed: SRDE, SRLP, SRFN, SRBP, SRDW |
| 10 | 80–89 | Pending: SPR (5), SPLB (3), SPFB (2) |
| 8 | 90–97 | Cross-- checks: SRXN (3), SRXD (2), SRXC (3) |
| 2 | 98–99 | Errors: SEI (1), SET (1) |
6.2 Property guaranteed
- Determinism:
requestId→ The same stage always. Reducible in CI. - Distribution:
crc32production of a uniform distribution about 100 buckets, with a result 100requestIddifferent 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
- Guides KYC related:
11. Validity and versionation
| Field | Value |
|---|---|
| State | Sandbox assets |
| Catalogue version | v0.1 |
| Last review | 2026-04-17 |
| External contact | soporte@jaak.ai |
Annex A - Checklist for integrators
-
requestIdexplicit at each request (do not depend on autogeneration). - Your CI tests at least one
SAHand one for each rejection family (SRD*,SRL*,SRF*,SRB*). - Your code runs
status: "pending_review"(checkSPR,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).