PusoPay serves PWDs, senior citizens, and solo parents — people who can't afford a branch visit. This API lets the app verify identity with a selfie and an ID, under one session ID, and open a wallet on a pass.
PusoPay owns the wallet and the customer screens. SVI owns identity proofing. The two sides meet at exactly one concept: the TRN — a session ID created up front and stapled to every photo and check.
OAuth 2.0 client-credentials. The server trades its provisioned credentials for a short-lived bearer token — the phone never sees secrets.
/auth/token| Header | Value |
|---|---|
| Content-Type | application/x-www-form-urlencoded |
| Authorization | HTTP Basic — base64(Client ID : Client Secret) |
| x-api-key | Gateway key issued at onboarding |
| Field | Required | Value |
|---|---|---|
| grant_type | required | client_credentials |
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"expires_at": "2026-07-14T22:48:46Z"
}| Field | Meaning |
|---|---|
| access_token | JWT → Authorization: Bearer on later calls |
| expires_in | 900 s = 15 min; the demo caches + refreshes silently |
| expires_at | Exact UTC expiry |
One call, no body. The returned UUID is the claim ticket for the whole onboarding.
/transaction/create| Header | Value |
|---|---|
| Authorization | Bearer <token> |
| x-api-key | Gateway key |
None — empty JSON object.
{
"transaction_id": "c5a21976-2e82-4ac0-b173-85aaa1e500a3"
}Passive liveness on a single selfie frame. The cheapest fraud filter: printed photos and screen replays fail here.
/liveness/passive| Header | Value |
|---|---|
| Authorization | Bearer <token> |
| X-Transaction-Id | TRN from §03 |
| x-api-key | Gateway key |
| Field | Required | Meaning |
|---|---|---|
| image | required | Base64 JPEG/PNG selfie |
{
"status": "success",
"results": {
"passed": true,
"confidence_score": 0.82,
"threshold": 0.8
}
}
OCR extracts printed identity + personal data into a standard JSON shape. Missing fields come back null — never guessed.
/id/ocr| Header | Value |
|---|---|
| Authorization | Bearer <token> |
| X-Transaction-Id | TRN |
| x-api-key | Gateway key |
| Field | Required | Meaning |
|---|---|---|
| id_front_base64 | required | ID front photo |
| id_back_base64 | optional | ID back photo |
{
"status": "success",
"extracted_information": {
"id_information": {
"code": "3",
"type": "PNID",
"id_number": "000-000-000",
"expiration_date": "2026-12-12"
},
"personal_information": {
"last_name": "Dela Cruz",
"first_name": "Juan",
"suffix": "Jr",
"birthdate": "1990-01-01",
"address": {
"address_line_1": "45 Mabini Street",
"barangay": "Barangay Poblacion",
"city_municipality": "Makati City",
"province": "Metro Manila",
"zipcode": "1210"
}
}
}
}
code/type (unknown → code 0, Other) and auto-fills the review form — no typing, fewer errors, kinder to seniors.1:1 biometric compare: the captured selfie against the captured ID portrait. Same TRN, verdict recorded as FACE_MATCH.
/face-match/check| Field | Required | Meaning |
|---|---|---|
| face_bio_base64 | required | Captured selfie |
| id_base64 | required | Captured ID front |
{
"status": "success",
"results": {
"is_matched": true,
"confidence_score": 0.82,
"threshold": 0.75
}
}
For PhilSys cards: scan the QR, verify payload + selfie through eVerify. Same outcome, no ID photo needed.
/verifications/qr| Field | Required | Meaning |
|---|---|---|
| qr_value | required | Raw PNID QR string (camera scan or upload decodes it) |
| face_biometric_base64 | required | Captured selfie |
{ "status": "success", "is_verified": true }
Confirmed identity + captured photos go in; verification_status comes out: PASSED (PNID) or PENDING_REVIEW.
/transaction/submit| Field | Required | Meaning |
|---|---|---|
| id_information.code / .type / .id_number | required | e.g. 3 / PNID / 000-000-000 |
| id_information.expiration_date | optional | YYYY-MM-DD |
| personal_information.last_name / .first_name / .birthdate | required | — |
| personal_information.suffix / .address | optional | Address object: line 1, barangay, city, province, zip |
| images.face_bio_base64 / .id_front_base64 | required | Captures |
| images.id_back_base64 | optional | — |
{
"id_information": {
"code": "3", "type": "PNID",
"id_number": "000-000-000"
},
"personal_information": {
"last_name": "Dela Cruz",
"first_name": "Juan",
"birthdate": "1990-01-01"
},
"images": {
"face_bio_base64": "<base64>",
"id_front_base64": "<base64>"
}
}{
"verification_status": "PENDING_REVIEW",
"session_transaction_id": "c5a21976-…",
"create_at": "2026-09-18T09:57:00Z"
}
// PNID path:
{ "verification_status": "PASSED", ... }
Every failure is HTTP status + error_code + human message. The demo surfaces all three in the backend log.
| HTTP | Code | What it means / what to do |
|---|---|---|
| 400 | INVALID_REQUEST | Malformed body/headers/JSON — fix the payload |
| 400 | INVALID_ID_TYPE | ID code unknown or outside contract scope |
| 401 | UNAUTHORIZED | Token missing/expired — re-login (§02) |
| 403 | FORBIDDEN | Token lacks scope or IP not allowed |
| 405 | METHOD_NOT_ALLOWED | Wrong HTTP verb |
| 415 | UNSUPPORTED_MEDIA_TYPE | Wrong Content-Type (json vs form-urlencoded) |
| 422 | INVALID_BIOMETRIC | Bad base64 or failed quality/liveness — recapture |
| 429 | LIMIT_EXCEEDED | Rate limit — back off and retry |
| 502 | REGISTRY_UNAVAILABLE | Downstream registry offline — retry later |
| 502 | UPSTREAM_ERROR | Processing dependency failed |
| 500 | SERVER_ERROR | Unexpected — escalate to SVI support |
Guide §10 lists 14; the demo adds 0 = Other for anything the OCR can't classify. Contract scope applies: query only what our agreement covers.
| Code | Type | Code | Type |
|---|---|---|---|
| 0 | OTHER_ID (demo) | 8 | TIN |
| 1 | PASSPORT | 9 | PWD_ID |
| 2 | PNID | 10 | SENIOR_CITIZEN_ID |
| 3 | PNID (ePhil ID) | 11 | PHILHEALTH |
| 4 | UMID | 12 | POSTAL_ID |
| 5 | PRC_ID | 13 | DRIVERS_LICENSE |
| 6 | SSS_ID | 14 | BATAENO_PASS |
| 7 | GSIS_ID |
Three columns: the customer app, the backend log, results + docs. Every screen calls exactly one endpoint.
| App step | Endpoint | Evidence in demo |
|---|---|---|
| Send code / login | — (mock SMS) | Inbox panel, tap-to-fill OTP |
| Start verification | POST /auth/token → /transaction/create | TRN in session panel |
| Selfie | POST /liveness/passive | LIVE 82% chip |
| ID scan | POST /id/ocr | Auto-fill + detected type |
| Match / QR | POST /face-match/check · /verifications/qr | Side-by-side photos · decoded string |
| Confirm | POST /transaction/submit | Result screen + wallet unlock |
Seven endpoints, one session ID, three questions answered
before PusoPay opens a wallet:
Three checks. Zero friction.
Selfie to wallet in one smooth flow.
That's DigiCash's PusoPay onboarding, powered by SVI.
PusoPay demo · DigiCash eWallet · identity checks by SVI
base https://verify.dev.svi.cloud/api/v1
PusoPay logo and other details are placeholders for demo purposes only — no real money was used.
Demo by Kevin G. Vega