Conventions
Things that are true across every product, so you only learn them once.
Identifiers
All resource IDs are CUID2 strings (24-char URL-safe). Don't parse them, don't infer prefixes — treat them as opaque.
cus_01HXY3J5VEBN8KMP7TQR2WC4DZSome older fields still use UUIDv4. Both are 25–36 characters of [A-Za-z0-9-] — your DB columns should be VARCHAR(64).
Timestamps
All timestamps are ISO-8601 with timezone, UTC by default:
"createdAt": "2026-05-24T08:14:22.481Z"Date-only fields (DOB, statement period boundaries) are YYYY-MM-DD.
We never use Unix epoch in API bodies — only in HTTP headers (where it's standard practice for X-RateLimit-Reset etc.).
Money
Indonesian rupiah is the default currency. All IDR amounts are integers in the smallest unit — there is no smaller unit than IDR, so just integer rupiah.
"amountIdr": 50000000 // 50 million rupiahFor amounts that can exceed Number.MAX_SAFE_INTEGER (≈9 quadrillion), we serialize as string to avoid floating-point precision loss. The endpoint reference flags which fields are stringified:
"amountIdr": "5000000000000000" // 5 quadrillion IDR — string to preserve precisionOther currencies: ISO-4217 alphabetic codes (USD, SGD, JPY). Amount is in the smallest unit (USD cents, JPY yen).
{
"amount": 12500,
"currency": "USD"
}Enums
Enums are lowercase snake_case strings. Examples:
"status": "in_review"
"riskRating": "high"
"direction": "credit"
"lane": "onboarding"
"kycLevel": "premium"We never use integers for enums. The full vocabulary per field is documented inline on the endpoint reference.
Booleans
Standard JSON true / false. Never "true" / "false" strings.
Nulls vs. omitted
For optional response fields, we omit the key entirely rather than emit null. Treat field === undefined and field === null as equivalent on the read side.
For request bodies, null means "set to null" (e.g. unassign), while omitting the key means "don't change". Most relevant on PATCH endpoints.
Casing
All JSON keys are camelCase. URLs and query parameters are kebab-case where multi-word.
GET /api/api-keys?include-revoked=true{ "fullName": "Budi", "phoneNumber": "+6281..." }File uploads
multipart/form-data with the file in a field named file. Always pass Content-Type for the file part — we sniff if missing but it's faster if you tell us.
Max size depends on endpoint, usually 20 MB (documents) or 50 MB (bank statements). The endpoint reference is the source of truth.
Bulk operations
When an endpoint supports a list (e.g. POST /api/screenings with subjects: [...]), the response is always a per-item array with success and failure broken out:
{
"data": {
"total": 100,
"succeeded": 98,
"failed": 2,
"results": [
{ "ok": true, "data": { ... } },
{ "ok": false, "error": "...", "input": { ... } }
]
}
}This way one bad row doesn't fail the whole batch.
PII handling
Sensitive fields (NIK, full email, phone) are masked in responses for viewer-role users on the dashboard. Server-to-server API key access sees full values, but the calling org is fully audited — every read of a sensitive field is logged.
Don't log API response bodies on your side either. Use a logging library that scrubs known PII patterns.