diff --git a/api-playground/json/payments/upi-issuance/blockPayeeVPA.json b/api-playground/json/payments/upi-issuance/blockPayeeVPA.json new file mode 100644 index 00000000..107f3c45 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/blockPayeeVPA.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" + } +} diff --git a/api-playground/json/payments/upi-issuance/checkVPA.json b/api-playground/json/payments/upi-issuance/checkVPA.json new file mode 100644 index 00000000..250b8122 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/checkVPA.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/json/payments/upi-issuance/createProgram.json b/api-playground/json/payments/upi-issuance/createProgram.json new file mode 100644 index 00000000..19b5bb0b --- /dev/null +++ b/api-playground/json/payments/upi-issuance/createProgram.json @@ -0,0 +1,16 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet" + } +} diff --git a/api-playground/json/payments/upi-issuance/createVPA.json b/api-playground/json/payments/upi-issuance/createVPA.json new file mode 100644 index 00000000..5ae00f12 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/createVPA.json @@ -0,0 +1,22 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "defaultCredit": true, + "defaultDebit": true, + "deviceId": "device-abc-123", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "otpRequired": false, + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/json/payments/upi-issuance/deregisterVPA.json b/api-playground/json/payments/upi-issuance/deregisterVPA.json new file mode 100644 index 00000000..17b0f0b0 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/deregisterVPA.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/json/payments/upi-issuance/getVPA.json b/api-playground/json/payments/upi-issuance/getVPA.json new file mode 100644 index 00000000..17b0f0b0 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/getVPA.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/json/payments/upi-issuance/listBlockedPayeeVPAs.json b/api-playground/json/payments/upi-issuance/listBlockedPayeeVPAs.json new file mode 100644 index 00000000..4f660332 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/listBlockedPayeeVPAs.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "cursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "deviceId": "device-abc-123", + "limit": 20, + "mobile": "919999999999" + } +} diff --git a/api-playground/json/payments/upi-issuance/listVPAs.json b/api-playground/json/payments/upi-issuance/listVPAs.json new file mode 100644 index 00000000..4f660332 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/listVPAs.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "cursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "deviceId": "device-abc-123", + "limit": 20, + "mobile": "919999999999" + } +} diff --git a/api-playground/json/payments/upi-issuance/pollBindingStatus.json b/api-playground/json/payments/upi-issuance/pollBindingStatus.json new file mode 100644 index 00000000..7d7a99cd --- /dev/null +++ b/api-playground/json/payments/upi-issuance/pollBindingStatus.json @@ -0,0 +1,13 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999" + } +} diff --git a/api-playground/json/payments/upi-issuance/requestBindingToken.json b/api-playground/json/payments/upi-issuance/requestBindingToken.json new file mode 100644 index 00000000..5b0530d3 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/requestBindingToken.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext,sim.bind-ok" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "os": "android", + "otpRequired": true + } +} diff --git a/api-playground/json/payments/upi-issuance/requestOTP.json b/api-playground/json/payments/upi-issuance/requestOTP.json new file mode 100644 index 00000000..17b0f0b0 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/requestOTP.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/json/payments/upi-issuance/resolveVPA.json b/api-playground/json/payments/upi-issuance/resolveVPA.json new file mode 100644 index 00000000..e5a81b0a --- /dev/null +++ b/api-playground/json/payments/upi-issuance/resolveVPA.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "someone@ybl", + "geocode": "12.9716,77.5946" + } +} diff --git a/api-playground/json/payments/upi-issuance/unblockPayeeVPA.json b/api-playground/json/payments/upi-issuance/unblockPayeeVPA.json new file mode 100644 index 00000000..107f3c45 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/unblockPayeeVPA.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" + } +} diff --git a/api-playground/json/payments/upi-issuance/updateProgram.json b/api-playground/json/payments/upi-issuance/updateProgram.json new file mode 100644 index 00000000..d7553443 --- /dev/null +++ b/api-playground/json/payments/upi-issuance/updateProgram.json @@ -0,0 +1,22 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext" + } + ], + "path": [ + { + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + ] + }, + "body": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": false, + "name": "Acme Wallet", + "status": "inactive" + } +} diff --git a/api-playground/json/payments/upi-issuance/verifyOTP.json b/api-playground/json/payments/upi-issuance/verifyOTP.json new file mode 100644 index 00000000..125f3fed --- /dev/null +++ b/api-playground/json/payments/upi-issuance/verifyOTP.json @@ -0,0 +1,15 @@ +{ + "parameters": { + "header": [ + { + "idempotencyKey": "sim.plaintext,sim.otp-ok" + } + ] + }, + "body": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "otp": "123456", + "vpa": "919999999999-acme@setu" + } +} diff --git a/api-playground/products.json b/api-playground/products.json index 2e5e5186..823eb330 100644 --- a/api-playground/products.json +++ b/api-playground/products.json @@ -19,6 +19,10 @@ { "name": "BBPS BillCollect", "path": "payments/bbps" + }, + { + "name": "UPI Issuance", + "path": "payments/upi-issuance" } ] }, diff --git a/api-references/payments/upi-issuance.json b/api-references/payments/upi-issuance.json new file mode 100644 index 00000000..02ab14ff --- /dev/null +++ b/api-references/payments/upi-issuance.json @@ -0,0 +1,4985 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Setu UPI Issuance — TPAP API", + "description": "TPAP-facing APIs for the Setu UPI Issuance switch (closed-stack PPI issuer). Phase 1: onboarding / device binding.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://upi-issuance-qa.setu.co" + } + ], + "paths": { + "/api/v1/onboarding/binding-token": { + "post": { + "tags": [ + "Device binding" + ], + "summary": "Generate a binding token", + "description": "Generate a device-binding token: returns the VMN and the ready-to-send silent-SMS body, and starts onboarding for the user's mobile number.", + "operationId": "requestBindingToken", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestBindingTokenRequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "os": "android", + "otpRequired": true + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BindingTokenResponse" + }, + "example": { + "smsBody": "VERIFY SETqxf8aemkxxdrhbifd4n4zldq7guvxb2k", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vmn": "919900000001" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "429": { + "description": "Too many requests: Too Many Requests response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-binding-cap-exceeded", + "mobile-binding-cap-exceeded" + ], + "example": "device-binding-cap-exceeded" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-binding-cap-exceeded", + "message": "Too many binding attempts for this device.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/binding-status": { + "post": { + "tags": [ + "Device binding" + ], + "summary": "Poll binding status", + "description": "Poll a user's device-binding status. Returns the user object in its current state.", + "operationId": "pollBindingStatus", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PollBindingStatusRequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BindingStatusResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "alias": "919999999999", + "deviceId": "device-abc-123", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "mobile": "919999999999", + "status": "active" + } + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "binding-not-found" + ], + "example": "binding-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "binding-not-found", + "message": "No binding for this device.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/otp/request": { + "post": { + "tags": [ + "OTP verification" + ], + "summary": "Request an OTP", + "description": "Request the user-presence OTP SMS — include the vpa to verify a new VPA (new onboarding / new VPA); omit the vpa for a device change (SIM re-binding). Asynchronous: returns 202; the OTP SMS is sent shortly after.", + "operationId": "requestOTP", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestOTPRequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "202": { + "description": "Accepted response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OTPRequestResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found", + "vpa-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "429": { + "description": "Too many requests: Too Many Requests response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "otp-cap-exceeded" + ], + "example": "otp-cap-exceeded" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "otp-cap-exceeded", + "message": "Too many OTP requests.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/otp/verify": { + "post": { + "tags": [ + "OTP verification" + ], + "summary": "Verify an OTP", + "description": "Verify the OTP the user entered. Activates what it was requested for — a new VPA (new onboarding / new VPA, vpa in the body) or the user (a device change, no vpa).", + "operationId": "verifyOTP", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerifyOTPRequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "otp": "123456", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OTPVerifyResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "alias": "919999999999", + "deviceId": "device-abc-123", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "mobile": "919999999999", + "status": "active" + }, + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked", + "invalid-otp" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "410": { + "description": "Gone: Gone response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "otp-expired-or-exhausted" + ], + "example": "otp-expired-or-exhausted" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "otp-expired-or-exhausted", + "message": "No live OTP to verify.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/vpa/check": { + "post": { + "tags": [ + "VPA management" + ], + "summary": "Check VPA availability", + "description": "Check whether a chosen VPA prefix is available. A taken prefix is not an error.", + "operationId": "checkVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpaCheckResponse" + }, + "example": { + "available": true, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "invalid-program", + "missing-parameter", + "invalid-vpa", + "invalid-handle" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/vpa/resolve": { + "post": { + "tags": [ + "VPA resolution" + ], + "summary": "Resolve a payee VPA", + "description": "Resolve a payee VPA on the payer side. A VPA under our handle is answered immediately from local records; a foreign-handle VPA triggers an async resolution with NPCI and returns pending until the result arrives. Poll by repeating this same POST. pending is not an error.", + "operationId": "resolveVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + }, + { + "name": "X-Sim-Resolution", + "in": "header", + "description": "**QA / sandbox only.** Pins the values this resolution returns, so a person, a merchant, a verified payee, or a failure can be reproduced deterministically instead of depending on what a real payee PSP reports. The value is a JSON object of the fields you expect back, e.g. {\"name\":\"Test Merchant\",\"entityType\":\"ENTITY\",\"verified\":{\"name\":\"Test Merchant\"}} or {\"status\":\"failed\",\"errorCode\":\"ZH\"}. Send it on every poll \u2014 it is read on the poll that returns resolved. Ignored on production.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "description": "**QA / sandbox only.** Pins the values this resolution returns, so a person, a merchant, a verified payee, or a failure can be reproduced deterministically instead of depending on what a real payee PSP reports. The value is a JSON object of the fields you expect back, e.g. {\"name\":\"Test Merchant\",\"entityType\":\"ENTITY\",\"verified\":{\"name\":\"Test Merchant\"}} or {\"status\":\"failed\",\"errorCode\":\"ZH\"}. Send it on every poll \u2014 it is read on the poll that returns resolved. Ignored on production.", + "example": "{\"name\":\"Test Merchant\",\"entityType\":\"ENTITY\",\"verified\":{\"name\":\"Test Merchant\"}}" + }, + "example": "{\"name\":\"Test Merchant\",\"entityType\":\"ENTITY\",\"verified\":{\"name\":\"Test Merchant\"}}" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "geocode": "12.9716,77.5946", + "mobile": "919999999999", + "vpa": "someone@ybl" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpaResolveResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "resolved", + "vpa": "brandx@ybl", + "name": "BRAND****X", + "accountType": "CURRENT", + "ifsc": "ICIC0000052", + "entityType": "ENTITY", + "genre": "ONLINE", + "mcc": "5732", + "merchantType": "LARGE", + "brandName": "Brand-X", + "franchiseName": "Brand-X Koramangala", + "legalName": "Acme Pvt Ltd", + "ownership": "PUBLIC", + "verified": true, + "verifiedName": "Brand-X", + "verifiedLogo": "https://cdn.example/brandx.png", + "verifiedUrl": "https://brandx.example" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-vpa" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user has no active VPA to originate a resolution.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/create-vpa": { + "post": { + "tags": [ + "VPA management" + ], + "summary": "Create a VPA", + "description": "Create a VPA over the user's chosen account. Requires an active user. With otpRequired, the VPA is created pending-verification until an OTP activates it.", + "operationId": "createVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVPARequestBody" + }, + "example": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "defaultCredit": true, + "defaultDebit": true, + "deviceId": "device-abc-123", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "otpRequired": false, + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVPAResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "invalid-program", + "missing-parameter", + "invalid-vpa", + "invalid-handle" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state", + "vpa-taken", + "vpa-account-mismatch" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/get-vpa": { + "post": { + "tags": [ + "VPA management" + ], + "summary": "Get a VPA", + "description": "Fetch one of the user's VPAs by its VPA string.", + "operationId": "getVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVPAResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-vpa", + "invalid-handle" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found", + "vpa-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/list-vpas": { + "post": { + "tags": [ + "VPA management" + ], + "summary": "List VPAs", + "description": "List the user's live VPAs (active and pending-verification), newest-added first, keyset-paginated.", + "operationId": "listVPAs", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListVPAsRequestBody" + }, + "example": { + "cursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "deviceId": "device-abc-123", + "limit": 20, + "mobile": "919999999999" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpaListResponse" + }, + "example": { + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpas": [ + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + ] + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/vpa/deregister": { + "post": { + "tags": [ + "VPA management" + ], + "summary": "Deregister a VPA", + "description": "Deregister one of the user's VPAs (soft delete; the entry is preserved for audit and re-registration). Idempotent for an already-deregistered VPA.", + "operationId": "deregisterVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVPAResponse" + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-vpa", + "invalid-handle" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found", + "vpa-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/programs": { + "post": { + "tags": [ + "Programs" + ], + "summary": "Create a program", + "description": "Create a program — an engagement channel that VPAs are created under — and return its programId.", + "operationId": "createProgram", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProgramRequestBody" + }, + "example": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet" + } + } + } + }, + "responses": { + "201": { + "description": "Created response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProgramResponse" + }, + "example": { + "program": { + "amountLimit": 500000, + "code": "ACME", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet", + "status": "active" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/programs/{programId}": { + "patch": { + "tags": [ + "Programs" + ], + "summary": "Update a program", + "description": "Update a program's configuration. Only the fields supplied in the body are changed.", + "operationId": "updateProgram", + "parameters": [ + { + "name": "programId", + "in": "path", + "description": "The program to update (ULID).", + "required": true, + "schema": { + "type": "string", + "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$", + "description": "The program to update (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProgramRequestBody" + }, + "example": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": false, + "name": "Acme Wallet", + "status": "inactive" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProgramResponse" + }, + "example": { + "program": { + "amountLimit": 500000, + "code": "ACME", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet", + "status": "active" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "program-not-found" + ], + "example": "program-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "program-not-found", + "message": "No program with that id.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/block-vpa": { + "post": { + "tags": [ + "Payee blocklist" + ], + "summary": "Block a payee VPA", + "description": "Block an external payee VPA for the user. Idempotent; requires an active user.", + "operationId": "blockPayeeVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockPayeeVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayeeBlockResponse" + }, + "example": { + "blockedPayee": { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-vpa" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/unblock-vpa": { + "post": { + "tags": [ + "Payee blocklist" + ], + "summary": "Unblock a payee VPA", + "description": "Unblock a previously-blocked payee VPA (soft flip; the entry is preserved). Idempotent for an already-unblocked payee; requires an active user.", + "operationId": "unblockPayeeVPA", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockPayeeVPARequestBody" + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayeeBlockResponse" + }, + "example": { + "blockedPayee": { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-vpa" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found", + "payee-not-blocked" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + }, + "/api/v1/onboarding/list-blocked-vpas": { + "post": { + "tags": [ + "Payee blocklist" + ], + "summary": "List blocked payee VPAs", + "description": "List the user's currently-blocked payee VPAs, newest first, keyset-paginated.", + "operationId": "listBlockedPayeeVPAs", + "parameters": [ + { + "name": "idempotencyKey", + "in": "header", + "description": "Optional client-generated idempotency key for safe retries.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "maxLength": 128, + "description": "Optional client-generated idempotency key for safe retries.", + "example": "req-8f3a2b1c" + }, + "example": "req-8f3a2b1c" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListVPAsRequestBody" + }, + "example": { + "cursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "deviceId": "device-abc-123", + "limit": 20, + "mobile": "919999999999" + } + } + } + }, + "responses": { + "200": { + "description": "OK response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockedPayeeListResponse" + }, + "example": { + "blockedPayees": [ + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + } + ], + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "400": { + "description": "Bad request: Bad Request response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-request" + ], + "example": "invalid-request" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-request", + "message": "The request could not be parsed.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "401": { + "description": "Unauthorized: Unauthorized response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "device-not-linked" + ], + "example": "device-not-linked" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "device-not-linked", + "message": "This device is not bound for this user.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "404": { + "description": "Not found: Not Found response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "user-not-found" + ], + "example": "user-not-found" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "user-not-found", + "message": "No user for this mobile number.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "409": { + "description": "Conflict: Conflict response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "invalid-user-state" + ], + "example": "invalid-user-state" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "invalid-user-state", + "message": "The user is not in a valid state for this operation.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + }, + "500": { + "description": "Internal server error: Internal Server Error response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "traceId", + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code.", + "enum": [ + "internal-error" + ], + "example": "internal-error" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID)." + } + } + }, + "example": { + "code": "internal-error", + "message": "Something went wrong on Setu's side.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BadRequestError": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code from the published dictionary.", + "example": "invalid-request", + "enum": [ + "invalid-request", + "missing-parameter", + "invalid-program", + "invalid-vpa", + "invalid-handle" + ] + }, + "message": { + "type": "string", + "description": "Human-readable description of the error.", + "example": "A required parameter is missing or invalid." + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "example": { + "code": "invalid-request", + "message": "A required parameter is missing or invalid.", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "required": [ + "traceId", + "code", + "message" + ] + }, + "BindingStatusResponse": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "user": { + "$ref": "#/components/schemas/UserView" + } + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "alias": "919999999999", + "deviceId": "device-abc-123", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "mobile": "919999999999", + "status": "active" + } + }, + "required": [ + "traceId", + "user" + ] + }, + "BindingTokenResponse": { + "type": "object", + "properties": { + "smsBody": { + "type": "string", + "description": "The ready-to-send silent-SMS body. The app sends this verbatim to the VMN; the token is embedded here and is never returned separately.", + "example": "VERIFY SETqxf8aemkxxdrhbifd4n4zldq7guvxb2k" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vmn": { + "type": "string", + "description": "The Virtual Mobile Number to send the SMS to.", + "example": "919900000001" + } + }, + "example": { + "smsBody": "VERIFY SETqxf8aemkxxdrhbifd4n4zldq7guvxb2k", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vmn": "919900000001" + }, + "required": [ + "traceId", + "vmn", + "smsBody" + ] + }, + "BlockPayeeVPARequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "payeeVpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "The external payee VPA to block or unblock (prefix@handle; any handle).", + "example": "merchant@otherbank" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" + }, + "required": [ + "deviceId", + "mobile", + "payeeVpa" + ] + }, + "BlockedPayeeListResponse": { + "type": "object", + "properties": { + "blockedPayees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlockedPayeeView" + }, + "description": "This page of the user's currently-blocked payee VPAs (newest-added first); empty when none.", + "example": [ + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + } + ] + }, + "nextCursor": { + "type": "string", + "description": "Cursor for the next page; absent on the last page.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "example": { + "blockedPayees": [ + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + } + ], + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "required": [ + "traceId", + "blockedPayees" + ] + }, + "BlockedPayeeView": { + "type": "object", + "properties": { + "blockedAt": { + "type": "string", + "description": "When the payee was most recently blocked (RFC3339).", + "example": "2026-07-09T12:00:00Z" + }, + "id": { + "type": "string", + "description": "The blocklist entry id (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "payeeVpa": { + "type": "string", + "description": "The blocked payee VPA (prefix@handle).", + "example": "merchant@otherbank" + }, + "status": { + "type": "string", + "description": "blocked or unblocked.", + "example": "blocked" + }, + "unblockedAt": { + "type": "string", + "description": "When the payee was unblocked (RFC3339); absent while blocked.", + "example": "2026-07-09T12:30:00Z" + } + }, + "example": { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + "required": [ + "id", + "payeeVpa", + "status" + ] + }, + "CheckVPARequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "programId": { + "type": "string", + "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$", + "description": "Optional program id (ULID). Validated when supplied.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "The full VPA to check (prefix@handle, e.g. 919999999999-acme@setu). Its prefix must start with the user's alias (by default, their mobile number).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "deviceId", + "mobile", + "vpa" + ] + }, + "ResolveVPARequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "geocode": { + "type": "string", + "maxLength": 64, + "pattern": "^-?[0-9]{1,3}(\\.[0-9]+)?,-?[0-9]{1,3}(\\.[0-9]+)?$", + "description": "Current device geocode as \"lat,long\"; forwarded as the payer device GEOCODE tag.", + "example": "12.9716,77.5946" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "The payee VPA to resolve (prefix@handle). Resolved locally when the handle is ours, otherwise via NPCI.", + "example": "someone@ybl" + } + }, + "example": { + "deviceId": "device-abc-123", + "geocode": "12.9716,77.5946", + "mobile": "919999999999", + "vpa": "someone@ybl" + }, + "required": [ + "deviceId", + "mobile", + "vpa" + ] + }, + "CreateProgramRequestBody": { + "type": "object", + "properties": { + "amountLimit": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "Per-transaction cap, in paise. Omit for no cap.", + "example": 500000 + }, + "code": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "minLength": 1, + "maxLength": 32, + "description": "The program's short code — its channel identifier.", + "example": "ACME" + }, + "isCreditAllowed": { + "type": "boolean", + "description": "May wallets on this program receive incoming UPI? Defaults true.", + "example": true + }, + "isDebitAllowed": { + "type": "boolean", + "description": "May wallets on this program pay outgoing UPI? Defaults true.", + "example": true + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z0-9 .'&-]+$", + "minLength": 1, + "maxLength": 99, + "description": "The program's display name.", + "example": "Acme Wallet" + } + }, + "example": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet" + }, + "required": [ + "name", + "code" + ] + }, + "CreateVPARequestBody": { + "type": "object", + "properties": { + "accountName": { + "type": "string", + "pattern": "^[A-Za-z0-9 .'&-]+$", + "minLength": 1, + "maxLength": 99, + "description": "The account-holder name.", + "example": "Alice Doe" + }, + "accountNo": { + "type": "string", + "pattern": "^[A-Z0-9]{6,18}$", + "description": "The wallet / pool account number.", + "example": "99887766554433" + }, + "accountProviderId": { + "type": "string", + "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$", + "description": "The account-provider id. Optional when a TPAP default is configured.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "defaultCredit": { + "type": "boolean", + "description": "Make this VPA the user's default credit account.", + "example": true + }, + "defaultDebit": { + "type": "boolean", + "description": "Make this VPA the user's default debit account.", + "example": true + }, + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "ifsc": { + "type": "string", + "pattern": "^[A-Z]{4}0[A-Z0-9]{6}$", + "description": "The IFSC of the account. Optional when a TPAP default is configured.", + "example": "SETU0000001" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "otpRequired": { + "type": "boolean", + "description": "Whether OTP verification is needed to activate the new VPA — set true for Android, false for iOS. When true, the VPA is created pending-verification until an OTP activates it; false (default) creates it active.", + "example": false + }, + "programId": { + "type": "string", + "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$", + "description": "Optional program id (ULID) — the program this VPA belongs to. Validated when supplied.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "The full VPA to create (prefix@handle, e.g. 919999999999-acme@setu). Its prefix must start with the user's alias (by default, their mobile number).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "defaultCredit": true, + "defaultDebit": true, + "deviceId": "device-abc-123", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "otpRequired": false, + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "deviceId", + "mobile", + "accountNo", + "vpa", + "accountName" + ] + }, + "CreateVPAResponse": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vpa": { + "$ref": "#/components/schemas/VpaView" + } + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + }, + "required": [ + "traceId", + "vpa" + ] + }, + "GetVPARequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "The full VPA to fetch (prefix@handle, e.g. 919999999999-acme@setu). Its prefix must start with the user's alias (by default, their mobile number).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "deviceId", + "mobile", + "vpa" + ] + }, + "ListVPAsRequestBody": { + "type": "object", + "properties": { + "cursor": { + "type": "string", + "description": "The nextCursor from the previous page. Omit for the first page.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "limit": { + "type": "integer", + "description": "Max entries to return this page. Omitted or non-positive defaults to 20; clamped to 100.", + "example": 20, + "format": "int64" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + } + }, + "example": { + "cursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "deviceId": "device-abc-123", + "limit": 20, + "mobile": "919999999999" + }, + "required": [ + "deviceId", + "mobile" + ] + }, + "OTPRequestResponse": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "required": [ + "traceId" + ] + }, + "OTPVerifyResponse": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "user": { + "$ref": "#/components/schemas/UserView" + }, + "vpa": { + "$ref": "#/components/schemas/VpaView" + } + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "alias": "919999999999", + "deviceId": "device-abc-123", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "mobile": "919999999999", + "status": "active" + }, + "vpa": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + }, + "required": [ + "traceId" + ] + }, + "PayeeBlockResponse": { + "type": "object", + "properties": { + "blockedPayee": { + "$ref": "#/components/schemas/BlockedPayeeView" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "example": { + "blockedPayee": { + "blockedAt": "2026-07-09T12:00:00Z", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "unblockedAt": "2026-07-09T12:30:00Z" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "required": [ + "traceId", + "blockedPayee" + ] + }, + "PollBindingStatusRequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999" + }, + "required": [ + "deviceId", + "mobile" + ] + }, + "ProgramResponse": { + "type": "object", + "properties": { + "program": { + "$ref": "#/components/schemas/ProgramView" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "example": { + "program": { + "amountLimit": 500000, + "code": "ACME", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet", + "status": "active" + }, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "required": [ + "traceId", + "program" + ] + }, + "ProgramView": { + "type": "object", + "properties": { + "amountLimit": { + "type": "integer", + "description": "Per-transaction cap, in paise; absent when there is no cap.", + "example": 500000, + "format": "int64" + }, + "code": { + "type": "string", + "description": "The program's short code.", + "example": "ACME" + }, + "id": { + "type": "string", + "description": "The program id (ULID); pass it as programId on onboarding calls.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "isCreditAllowed": { + "type": "boolean", + "description": "May wallets on this program receive incoming UPI?", + "example": true + }, + "isDebitAllowed": { + "type": "boolean", + "description": "May wallets on this program pay outgoing UPI?", + "example": true + }, + "name": { + "type": "string", + "description": "The program's display name.", + "example": "Acme Wallet" + }, + "status": { + "type": "string", + "description": "active or inactive.", + "example": "active" + } + }, + "example": { + "amountLimit": 500000, + "code": "ACME", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "isCreditAllowed": true, + "isDebitAllowed": true, + "name": "Acme Wallet", + "status": "active" + }, + "required": [ + "id", + "name", + "isCreditAllowed", + "isDebitAllowed", + "status" + ] + }, + "RequestBindingTokenRequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "os": { + "type": "string", + "enum": [ + "android", + "ios" + ], + "description": "The device OS: android or ios.", + "example": "android" + }, + "otpRequired": { + "type": "boolean", + "description": "Set true for the device-change case where an OTP is needed after SIM binding (e.g. Android). Set false for iOS, or for the new-onboarding / new-VPA case where the OTP happens after VPA creation (gate it on create-vpa's otpRequired instead).", + "example": true + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "os": "android", + "otpRequired": true + }, + "required": [ + "deviceId", + "mobile", + "os", + "otpRequired" + ] + }, + "RequestOTPRequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "Include the VPA to verify a new VPA (new onboarding / new VPA); omit for a device-change OTP (SIM re-binding).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "deviceId", + "mobile" + ] + }, + "UpdateProgramRequestBody": { + "type": "object", + "properties": { + "amountLimit": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "Per-transaction cap, in paise. Omit for no cap.", + "example": 500000 + }, + "code": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "minLength": 1, + "maxLength": 32, + "description": "The program's short code.", + "example": "ACME" + }, + "isCreditAllowed": { + "type": "boolean", + "description": "May wallets on this program receive incoming UPI?", + "example": true + }, + "isDebitAllowed": { + "type": "boolean", + "description": "May wallets on this program pay outgoing UPI?", + "example": false + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z0-9 .'&-]+$", + "minLength": 1, + "maxLength": 99, + "description": "The program's display name.", + "example": "Acme Wallet" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ], + "description": "active or inactive.", + "example": "inactive" + } + }, + "example": { + "amountLimit": 500000, + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": false, + "name": "Acme Wallet", + "status": "inactive" + } + }, + "UserView": { + "type": "object", + "properties": { + "alias": { + "type": "string", + "description": "The user's alias: the prefix of their VPAs. Defaults to the mobile.", + "example": "919999999999" + }, + "deviceId": { + "type": "string", + "description": "The user's device id.", + "example": "device-abc-123" + }, + "id": { + "type": "string", + "description": "The user id (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "mobile": { + "type": "string", + "description": "The user's mobile number, with country code.", + "example": "919999999999" + }, + "status": { + "type": "string", + "description": "binding-pending | device-bound | otp-pending | active.", + "example": "active" + } + }, + "example": { + "alias": "919999999999", + "deviceId": "device-abc-123", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "mobile": "919999999999", + "status": "active" + }, + "required": [ + "id", + "status" + ] + }, + "VerifyOTPRequestBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The user's device id.", + "example": "device-abc-123" + }, + "mobile": { + "type": "string", + "pattern": "^[0-9]{12}$", + "description": "The user's mobile number, with country code (e.g. 919999999999).", + "example": "919999999999" + }, + "otp": { + "type": "string", + "pattern": "^[0-9]{6}$", + "description": "The OTP the user entered.", + "example": "123456" + }, + "vpa": { + "type": "string", + "pattern": "^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$", + "minLength": 1, + "maxLength": 255, + "description": "Include the VPA to verify a new VPA (new onboarding / new VPA); omit for a device-change OTP (SIM re-binding).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "deviceId": "device-abc-123", + "mobile": "919999999999", + "otp": "123456", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "deviceId", + "mobile", + "otp" + ] + }, + "VpaCheckResponse": { + "type": "object", + "properties": { + "available": { + "type": "boolean", + "description": "false means the VPA is already taken by another user's active VPA. A taken VPA is not an error.", + "example": true + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vpa": { + "type": "string", + "description": "The full VPA that was checked (prefix@handle).", + "example": "919999999999-acme@setu" + } + }, + "example": { + "available": true, + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "traceId", + "vpa", + "available" + ] + }, + "VpaResolveResponse": { + "type": "object", + "properties": { + "accountType": { + "type": "string", + "description": "The payee's account type. Present in the API response in case of successful resolution.", + "example": "SAVINGS" + }, + "brandName": { + "type": "string", + "description": "Merchant brand name. Present for a resolved merchant VPA.", + "example": "Brand-X" + }, + "entityType": { + "type": "string", + "description": "The payee entity type: PERSON for a non-merchant VPA, ENTITY for a merchant VPA. Present in the API response in case of successful resolution.", + "example": "PERSON" + }, + "errorCode": { + "type": "string", + "description": "NPCI error code. Present on failed (e.g. ZH for an invalid address).", + "example": "ZH" + }, + "errorMessage": { + "type": "string", + "description": "Human-readable failure reason. Present on failed.", + "example": "invalid virtual address" + }, + "franchiseName": { + "type": "string", + "description": "Merchant franchise name. Present for a resolved merchant VPA.", + "example": "Brand-X Franchise" + }, + "genre": { + "type": "string", + "description": "Merchant genre: ONLINE or OFFLINE. Present for a resolved merchant VPA.", + "example": "ONLINE" + }, + "ifsc": { + "type": "string", + "description": "The payee's IFSC. Present in the API response in case of successful resolution.", + "example": "ICIC0000052" + }, + "legalName": { + "type": "string", + "description": "Merchant legal name. Present for a resolved merchant VPA.", + "example": "Acme Pvt Ltd" + }, + "mcc": { + "type": "string", + "description": "Merchant category code. Present for a resolved merchant VPA.", + "example": "5732" + }, + "merchantType": { + "type": "string", + "description": "Merchant type: SMALL or LARGE. Present for a resolved merchant VPA.", + "example": "LARGE" + }, + "name": { + "type": "string", + "description": "The payee's account-holder name. For a VPA on our handle this is the account name on record; for a foreign handle it is the name as reported by the payee PSP. Present in the API response in case of successful resolution.", + "example": "ANI********DEY" + }, + "ownership": { + "type": "string", + "description": "Merchant ownership type: PROPRIETARY, PARTNERSHIP, PRIVATE, PUBLIC or OTHERS. Present for a resolved merchant VPA.", + "example": "PUBLIC" + }, + "status": { + "type": "string", + "enum": [ + "resolved", + "pending", + "failed" + ], + "description": "Resolution outcome. The value resolved indicates that the payee VPA is valid and the API response carries the payee VPA details. The value pending indicates that the payee VPA resolution is in progress. The value failed indicates that the payee VPA is invalid and the API response indicates the failure reason.", + "example": "resolved" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "verified": { + "type": "boolean", + "description": "true when the resolved payee VPA is in the verified/whitelisted registry. Present only when the payee VPA is a whitelisted / verified VPA — omitted otherwise.", + "example": true + }, + "verifiedLogo": { + "type": "string", + "description": "The registered logo url/ref from the verified registry. Present when the payee VPA is a whitelisted / verified VPA.", + "example": "https://cdn.example/brandx.png" + }, + "verifiedName": { + "type": "string", + "description": "The registered display name from the verified registry. Present when the payee VPA is a whitelisted / verified VPA.", + "example": "Brand-X" + }, + "verifiedUrl": { + "type": "string", + "description": "The registered brand url from the verified registry. Present when the payee VPA is a whitelisted / verified VPA.", + "example": "https://brandx.example" + }, + "vpa": { + "type": "string", + "description": "The payee VPA that was resolved (echo of the request).", + "example": "someone@ybl" + } + }, + "example": { + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "resolved", + "vpa": "brandx@ybl", + "name": "BRAND****X", + "accountType": "CURRENT", + "ifsc": "ICIC0000052", + "entityType": "ENTITY", + "genre": "ONLINE", + "mcc": "5732", + "merchantType": "LARGE", + "brandName": "Brand-X", + "franchiseName": "Brand-X Koramangala", + "legalName": "Acme Pvt Ltd", + "ownership": "PUBLIC", + "verified": true, + "verifiedName": "Brand-X", + "verifiedLogo": "https://cdn.example/brandx.png", + "verifiedUrl": "https://brandx.example" + }, + "required": [ + "traceId", + "status", + "vpa" + ] + }, + "VpaListResponse": { + "type": "object", + "properties": { + "nextCursor": { + "type": "string", + "description": "Cursor for the next page; absent on the last page.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "traceId": { + "type": "string", + "description": "Trace handle for this call (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "vpas": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VpaView" + }, + "description": "This page of the user's live VPAs (newest-added first); empty when none.", + "example": [ + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + ] + } + }, + "example": { + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpas": [ + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + } + ] + }, + "required": [ + "traceId", + "vpas" + ] + }, + "VpaView": { + "type": "object", + "properties": { + "accountName": { + "type": "string", + "description": "The account-holder name.", + "example": "Alice Doe" + }, + "accountNo": { + "type": "string", + "description": "The wallet / pool account number.", + "example": "99887766554433" + }, + "accountProviderId": { + "type": "string", + "description": "The account-provider id.", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "accountType": { + "type": "string", + "description": "The account type (e.g. PPIWALLET).", + "example": "PPIWALLET" + }, + "id": { + "type": "string", + "description": "The VPA id (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "ifsc": { + "type": "string", + "description": "The IFSC of the account.", + "example": "SETU0000001" + }, + "mobile": { + "type": "string", + "description": "The owner's mobile number, with country code.", + "example": "919999999999" + }, + "programId": { + "type": "string", + "description": "The program the VPA belongs to (ULID).", + "example": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + }, + "status": { + "type": "string", + "description": "active | pending-verification | deregistered.", + "example": "active" + }, + "vpa": { + "type": "string", + "description": "The full VPA, prefix@handle.", + "example": "919999999999-acme@setu" + } + }, + "example": { + "accountName": "Alice Doe", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountType": "PPIWALLET", + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "vpa": "919999999999-acme@setu" + }, + "required": [ + "id", + "vpa" + ] + } + } + }, + "tags": [ + { + "name": "Device binding" + }, + { + "name": "OTP verification" + }, + { + "name": "VPA management" + }, + { + "name": "Programs" + }, + { + "name": "Payee blocklist" + }, + { + "name": "VPA resolution" + } + ] +} \ No newline at end of file diff --git a/content/endpoints.json b/content/endpoints.json index 2b27d444..7cba1db5 100644 --- a/content/endpoints.json +++ b/content/endpoints.json @@ -38,6 +38,12 @@ "path": "umap", "order": 7, "visible_in_sidebar": true + }, + { + "name": "UPI Issuance", + "path": "upi-issuance", + "order": 8, + "visible_in_sidebar": true } ] }, diff --git a/content/menuItems.json b/content/menuItems.json index 37baad02..0806ad73 100644 --- a/content/menuItems.json +++ b/content/menuItems.json @@ -1 +1 @@ -{"home":[{"name":"Payments","path":"payments","order":0,"visible_in_sidebar":true,"api_reference":true,"children":[{"name":"BBPS BillCollect","path":"bbps","order":0,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS API reference","path":"api-reference","order":9},{"name":"Axis BBPS","visible_in_sidebar":false,"page_title":"Axis BBPS API Approach Document","path":"axis","order":10},{"name":"Bill Structure","visible_in_sidebar":true,"page_title":"BBPS - Bill Structure","path":"bill-structure","order":5,"children":[{"name":"Special cases","visible_in_sidebar":true,"page_title":"BBPS - Bill Structure special cases","path":"special-cases","order":1}]},{"name":"Go live","visible_in_sidebar":true,"page_title":"BBPS - Go live","path":"go-live","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"BBPS - Notifications","path":"notifications","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS - Overview","path":"overview","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS - Quickstart","path":"quickstart","order":2,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS - API integration","path":"api-integration","order":2},{"name":"No-code CSV","visible_in_sidebar":true,"page_title":"BBPS - No-code CSV","path":"no-code-integration","order":1},{"name":"Share bills","visible_in_sidebar":false,"page_title":"BBPS - Share bills","path":"share-biils","order":1}]},{"name":"Reports API","visible_in_sidebar":true,"page_title":"BBPS - Reports API","path":"reports","order":6},{"name":"Additional resources","visible_in_sidebar":true,"page_title":"BBPS - Additional Resources","path":"resources","order":8,"children":[{"name":"Errors","visible_in_sidebar":true,"page_title":"BBPS error codes","path":"errors","order":4},{"name":"JWT authentication","visible_in_sidebar":true,"page_title":"UPI Deeplinks JWT authentication","path":"jwt","order":2},{"name":"OAuth 2.0","visible_in_sidebar":true,"page_title":"BBPS OAuth 2.0","path":"oauth","order":1},{"name":"Settlement object","visible_in_sidebar":true,"page_title":"UPI Deeplinks settlement object","path":"settlement-object","order":3}]}]},{"name":"BBPS BillPay","path":"billpay","order":1,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS Billpay API integration","path":"api-integration","order":1,"children":[{"name":"API payload encryption (optional)","visible_in_sidebar":true,"page_title":"BBPS COU - API payload encryption (AES-CBC)","path":"api-encryption","order":9},{"name":"API reference","visible_in_sidebar":true,"page_title":"COU Direct Connectivity API reference","path":"api-reference","order":10},{"name":"List of APIs","visible_in_sidebar":true,"page_title":"BBPS COU - List of APIs","path":"apis","order":2},{"name":"FX Retail","visible_in_sidebar":true,"page_title":"BBPS COU - FX Retail (Forex category)","path":"fx-retail","order":8},{"name":"Harmonization of TAT","visible_in_sidebar":true,"page_title":"Harmonization Of TAT (Disputes API)","path":"harmonization_of_tat","order":6},{"name":"Objects","visible_in_sidebar":true,"page_title":"BBPS COU - Objects","path":"objects","order":11},{"name":"Paying Bills","visible_in_sidebar":true,"page_title":"Paying Bills","path":"paying-bills","order":4,"children":[{"name":"Paying for options","visible_in_sidebar":true,"page_title":"Paying for alternative options","path":"bill-payment-options","order":4},{"name":"Passing CCF","visible_in_sidebar":true,"page_title":"Customer Convenience Fee (CCF) Integration Guide","path":"customer-convenience-fee","order":3},{"name":"Paying multiple bills","visible_in_sidebar":true,"page_title":"Paying for multiple bills","path":"multi-bill-processing","order":5},{"name":"Paying for plans","visible_in_sidebar":true,"page_title":"Paying for plans","path":"plan-mdm-integration","order":6},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Bill Payment Integration Guide","path":"quickstart","order":1},{"name":"Remitter Details","visible_in_sidebar":true,"page_title":"Passing Remitter Details","path":"remittance_flows_guide","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS COU - API integration","path":"quickstart","order":1},{"name":"Integrating with UPMS","visible_in_sidebar":true,"page_title":"BBPS COU - Integrating with UPMS","path":"upms","order":7},{"name":"Migration Guide to v2","visible_in_sidebar":true,"page_title":"BBPS COU - Migration Guide to v2","path":"v2-migration","order":5},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS COU - Webhooks","path":"webhooks","order":3}]},{"name":"API reference","visible_in_sidebar":false,"page_title":"BillPay API reference","path":"api-reference","order":5},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"mcp","order":null,"children":[{"name":"Integration Guide","visible_in_sidebar":false,"page_title":"MCP Server for Bill Payments - Integration Guide","path":"integration-guide","order":1},{"name":"Tools and Prompts","visible_in_sidebar":false,"page_title":"MCP Server for Bill Payments - Tools and Prompts","path":"tools-and-prompts","order":2}]},{"name":"Prepaid Recharge","visible_in_sidebar":true,"page_title":"BBPS Billpay Prepaid Recharge APIs","path":"mobile-prepaid-recharge","order":3,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge quickstart","path":"quickstart","order":1},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge Webhooks","path":"webhooks","order":3}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS Billpay Overview","path":"overview","order":0},{"name":"Pre-built screens","visible_in_sidebar":true,"page_title":"BBPS Billpay pre-built screens","path":"pre-built-screens","order":2,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"BBPS Billpay API reference","path":"api-reference-wl","order":4},{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS Billpay API reference","path":"api-reference","order":5},{"name":"Custom payment","visible_in_sidebar":true,"page_title":"BBPS Billpay custom payment","path":"custom-payment","order":2,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay android integration for custom payment","path":"android","order":3},{"name":"Required APIs","visible_in_sidebar":true,"page_title":"BBPS Billpay APIs for custom payment","path":"apis","order":1},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross-platform integration for custom payment","path":"cross-platform","order":3},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration for custom payment","path":"iOS","order":4},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website integration for custom payment","path":"website","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Billpay Quickstart","path":"quickstart","order":1,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay Android integration","path":"android","order":2},{"name":"API","visible_in_sidebar":true,"page_title":"BBPS Billpay API","path":"api","order":2},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross platform integration","path":"cross-platform","order":4},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration","path":"iOS","order":3},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website","path":"website","order":1}]},{"name":"Remitter Details","visible_in_sidebar":true,"page_title":"Remitter Details For Bill Payments Integration Guide","path":"remitter-details","order":4},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS Billpay webhooks","path":"webhooks","order":2}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS Billpay API integration","path":"api-integration","order":1,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"COU Direct Connectivity API reference","path":"api-reference","order":5},{"name":"List of APIs","visible_in_sidebar":true,"page_title":"BBPS COU - List of APIs","path":"apis","order":2},{"name":"Deprecated APIs","visible_in_sidebar":false,"page_title":"BBPS COU - API integration (deprecated)","path":"deprecated","order":4,"children":[{"name":"Mock environment","visible_in_sidebar":false,"page_title":"BBPS Billpay Mock environment","path":"mock-environment","order":2},{"name":"Polling","visible_in_sidebar":false,"page_title":"BBPS Billpay polling","path":"polling","order":2}]},{"name":"Objects","visible_in_sidebar":true,"page_title":"BBPS COU - Objects","path":"objects","order":3},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS COU - API integration","path":"quickstart","order":1},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS COU - Webhooks","path":"webhooks","order":4}]},{"name":"API reference","visible_in_sidebar":false,"page_title":"BillPay API reference","path":"api-reference","order":5},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS Billpay Overview","path":"overview","order":0},{"name":"Pre-built screens","visible_in_sidebar":true,"page_title":"BBPS Billpay pre-built screens","path":"pre-built-screens","order":2,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"BBPS Billpay API reference","path":"api-reference-wl","order":4},{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS Billpay API reference","path":"api-reference","order":4},{"name":"Custom payment","visible_in_sidebar":true,"page_title":"BBPS Billpay custom payment","path":"custom-payment","order":2,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay android integration for custom payment","path":"android","order":3},{"name":"Required APIs","visible_in_sidebar":true,"page_title":"BBPS Billpay APIs for custom payment","path":"apis","order":1},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross-platform integration for custom payment","path":"cross-platform","order":3},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration for custom payment","path":"iOS","order":4},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website integration for custom payment","path":"website","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Billpay Quickstart","path":"quickstart","order":1,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay Android integration","path":"android","order":2},{"name":"API","visible_in_sidebar":true,"page_title":"BBPS Billpay API","path":"api","order":2},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross platform integration","path":"cross-platform","order":4},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration","path":"iOS","order":3},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website","path":"website","order":1}]},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS Billpay webhooks","path":"webhooks","order":2}]}]}]},{"name":"WhatsApp Collect","path":"whatsapp-collect","order":3,"visible_in_sidebar":true,"children":[{"name":"API Integration","visible_in_sidebar":true,"page_title":"WhatsApp Collect API Integration","path":"api-integration","order":3},{"name":"API reference","visible_in_sidebar":true,"page_title":"WhatsApp Collect API reference","path":"api-reference","order":5},{"name":"Error codes","visible_in_sidebar":true,"page_title":"WhatsApp Collect error codes","path":"errors","order":4},{"name":"Collection journey","visible_in_sidebar":true,"page_title":"WhatsApp Collect Journey","path":"journey","order":1},{"name":"Overview","visible_in_sidebar":true,"page_title":"WhatsApp Collect Overview","path":"overview","order":0},{"name":"Collection reminders","visible_in_sidebar":true,"page_title":"WhatsApp Collect reminders","path":"reminders","order":2}]},{"name":"UPI DeepLinks","path":"upi-deeplinks","order":4,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Deeplinks API reference","path":"api-reference","order":8},{"name":"Notifications","visible_in_sidebar":true,"page_title":"UPI Deeplinks Notifications","path":"notifications","order":6},{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Deeplinks Overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Deeplinks quickstart","path":"quickstart","order":1,"children":[{"name":"Go Live","visible_in_sidebar":true,"page_title":"UPI Deeplinks go live","path":"go-live","order":1}]},{"name":"Refunds","visible_in_sidebar":true,"page_title":"UPI Deeplinks Refunds","path":"refunds","order":4},{"name":"Reports API","visible_in_sidebar":true,"page_title":"UPI Deeplinks Reports API","path":"reports","order":5},{"name":"Additional resources","visible_in_sidebar":true,"page_title":"UPI Deeplinks additonal resources","path":"resources","order":6,"children":[{"name":"JWT authentication","visible_in_sidebar":true,"page_title":"UPI Deeplinks JWT authentication","path":"jwt","order":2},{"name":"OAuth 2.0","visible_in_sidebar":true,"page_title":"UPI Deeplinks OAuth 2.0","path":"oauth","order":1},{"name":"Settlement object","visible_in_sidebar":true,"page_title":"UPI Deeplinks settlement object","path":"settlement-object","order":3}]},{"name":"SDKs","visible_in_sidebar":true,"page_title":"UPI Deeplinks SDKs","path":"sdks","order":3},{"name":"Third party verification","visible_in_sidebar":true,"page_title":"UPI Deeplinks third party verification","path":"third-party-verification","order":3}]},{"name":"UPI Setu","path":"umap","order":7,"visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"UPI Setu - API integration","path":"api-integration","order":2,"children":[{"name":"Aggregators","visible_in_sidebar":true,"page_title":"UPI Setu - API integration for aggregators","path":"aggregators","order":1},{"name":"Merchants","visible_in_sidebar":true,"page_title":"UPI Setu - API integration for merchants","path":"merchants","order":2}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Setu - API reference","path":"api-reference","order":8},{"name":"UPI mandates","visible_in_sidebar":true,"page_title":"UPI mandates","path":"mandates","order":4,"children":[{"name":"Mandate operations","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Mandate operations","path":"generic","order":5,"children":[{"name":"Pause","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Pause","path":"pause","order":3},{"name":"Revoke","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Revoke","path":"revoke","order":2},{"name":"Unpause","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Unpause","path":"unpause","order":4},{"name":"Update","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Update","path":"update","order":1}]},{"name":"OneShot","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - OneShot","path":"one-shot","order":1,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create One Time Mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute One Time Mandate","path":"execute","order":3},{"name":"Pre Debit Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Send One Time Mandate Pre Debit Notification","path":"pre-debit-notify","order":2}]},{"name":"Recur","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Recur","path":"recur","order":3,"children":[{"name":"Check payment status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create recurring mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute mandate","path":"execute","order":3},{"name":"Pre Debit Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Send Recurring Mandate Pre Debit Notification","path":"pre-debit-notify","order":2}]},{"name":"Reserve","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Reserve","path":"reserve","order":2,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create Reserve Mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute Reserve Mandate","path":"execute","order":3}]},{"name":"ReservePlus","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - ReservePlus","path":"reserve-plus","order":4,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":3},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create single block multi-debit","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute single block multi-debit","path":"execute","order":2}]}]},{"name":"Merchant on-boarding","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant onboarding","path":"merchant-onboarding","order":2,"children":[{"name":"Check VPA availability","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Check VPA availability","path":"check-vpa-availability-api","order":2},{"name":"Setup a merchant","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Setup merchant","path":"create-merchant-api","order":1},{"name":"Registering VPA","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Registering a VPA","path":"create-vpa-api","order":3}]},{"name":"Notifications and alerts","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts","path":"notifications","order":7,"children":[{"name":"VPA verification","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Customer VPA verification","path":"customer-vpa-verification","order":6},{"name":"Mandates","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandates","path":"mandates","order":3,"children":[{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Creation of mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandate execution","path":"execute","order":7},{"name":"Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandate pre-debit notifications","path":"notify","order":6},{"name":"Pause","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Pausing mandate","path":"pause","order":4},{"name":"Revoke","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Revoking mandate","path":"revoke","order":3},{"name":"Unpause","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Unpausing mandate","path":"unpause","order":5},{"name":"Update","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Updating mandate","path":"update","order":2}]},{"name":"Payments","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Payments","path":"payments","order":2},{"name":"Refunds","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Refunds","path":"refunds","order":4},{"name":"Verify signature","visible_in_sidebar":true,"page_title":"UMAP - Events and notifications","path":"verify-signature","order":1}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Setu - Overview","path":"overview","order":0},{"name":"UPI payments","visible_in_sidebar":true,"page_title":"UPI payments","path":"payments","order":3,"children":[{"name":"Collect","visible_in_sidebar":true,"page_title":"UPI payments - Collect","path":"collect","order":2,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu payments - Collect request - Check payment status","path":"check-status","order":3},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create collect request","path":"create-collect-request","order":2},{"name":"Verify customer VPA","visible_in_sidebar":true,"page_title":"UPI Setu payments - Verify customer VPA","path":"verify-customer-vpa-api","order":1}]},{"name":"Flash","visible_in_sidebar":true,"page_title":"UPI payments - Flash","path":"flash","order":1,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu payments - Intent/QR - Check payment status","path":"check-status","order":2},{"name":"Dynamic QR","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create Dynamic QR","path":"create-dqr","order":1},{"name":"Static QR","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create Static QR","path":"create-sqr","order":1}]},{"name":"TPV","visible_in_sidebar":true,"page_title":"UPI payments - TPV","path":"tpv","order":3,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Payments - TPV - Check payment status","path":"check-status","order":2},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Payments - Create TPV API","path":"create-tpv","order":1},{"name":"Payments","visible_in_sidebar":true,"page_title":"UMAP - Notifications and alerts - Payments","path":"life-cycle","order":1}]}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart","path":"quickstart","order":1,"children":[{"name":"Aggregators","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart for aggregators","path":"aggregators","order":1},{"name":"Merchants","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart for merchants","path":"merchants","order":2}]},{"name":"Refunds and disputes","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes","path":"refunds-disputes","order":6,"children":[{"name":"Check refund status","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Check refund status API","path":"check-refund-status-api","order":2},{"name":"Create refund","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Create refund API","path":"create-refund-api","order":1},{"name":"Fetch dispute","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Fetch dispute API","path":"fetch-dispute-api","order":3}]},{"name":"Transaction Monitoring","visible_in_sidebar":false,"page_title":"UPI Setu - Transaction Monitoring","path":"transaction-monitoring","order":5,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Check status API","path":"check-status-api","order":1},{"name":"Check status history","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Check status sistory API","path":"check-status-history-api","order":2},{"name":"Fetch payment","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Fetch payment API","path":"fetch-payment-api","order":3}]}]},{"name":"UPI Issuance","path":"upi-issuance","order":8,"visible_in_sidebar":true,"children":[{"name":"The API envelope","visible_in_sidebar":true,"page_title":"UPI Issuance API envelope","path":"api-envelope","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Issuance API reference","path":"api-reference","order":6},{"name":"User Onboarding","visible_in_sidebar":true,"page_title":"UPI Issuance User Onboarding","path":"onboarding","order":3,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"UPI Issuance API integration","path":"api-integration","order":1,"children":[{"name":"Device binding","visible_in_sidebar":true,"page_title":"UPI Issuance device binding","path":"device-binding","order":0},{"name":"Programs","visible_in_sidebar":true,"page_title":"UPI Issuance programs","path":"programs","order":3},{"name":"OTP verification","visible_in_sidebar":true,"page_title":"UPI Issuance OTP verification","path":"user-otp","order":1},{"name":"VPA management","visible_in_sidebar":true,"page_title":"UPI Issuance VPA management","path":"vpa-management","order":2}]},{"name":"Onboarding states","visible_in_sidebar":true,"page_title":"UPI Issuance onboarding states","path":"onboarding-states","order":0}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Issuance Overview","path":"overview","order":0},{"name":"Payee blocklist","visible_in_sidebar":true,"page_title":"UPI Issuance payee blocklist","path":"payee-blocklist","order":4},{"name":"Testing on QA env","visible_in_sidebar":true,"page_title":"UPI Issuance QA env testing","path":"qa-testing","order":5},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Issuance Quickstart","path":"quickstart","order":1}]}]},{"name":"Data","path":"data","order":1,"visible_in_sidebar":true,"children":[{"name":"KYC","path":"kyc","order":0,"visible_in_sidebar":true,"children":[{"name":"Secure Data Add-On","visible_in_sidebar":true,"page_title":"Setu Encrypted APIs","path":"encryption","order":2},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu KYC Overview","path":"overview","order":1}]},{"name":"PAN verification","path":"pan","order":0,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"PAN verification API reference","path":"api-reference","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"PAN verification quickstart","path":"quickstart","order":0}]},{"name":"Aadhaar eSign","path":"esign","order":2,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Aadhaar eSign API reference","path":"api-reference","order":9},{"name":"Error codes","visible_in_sidebar":true,"page_title":"Aadhaar eSign error codes","path":"error-codes","order":8},{"name":"eStamp overview","visible_in_sidebar":true,"page_title":"eStamp overview","path":"estamp","order":2},{"name":"Flexible eSign guide","visible_in_sidebar":true,"page_title":"Integration guide with flexible signature coordinates","path":"flexi-esign","order":4},{"name":"eSign Name Match","visible_in_sidebar":true,"page_title":"Aadhaar eSign Name Match","path":"name-match","order":6},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Aadhaar eSign Notifications","path":"notifications","order":7},{"name":"Overview","visible_in_sidebar":true,"page_title":"Aadhaar eSign overview","path":"overview","order":1},{"name":"PDF templates","visible_in_sidebar":true,"page_title":"Integration guide with pdf templating API's","path":"pdf-templating","order":5},{"name":"Integration guide","visible_in_sidebar":true,"page_title":"Aadhaar eSign integration guide","path":"quickstart","order":3}]},{"name":"DigiLocker","path":"digilocker","order":3,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Digilocker API reference","path":"api-reference","order":3},{"name":"Error codes","visible_in_sidebar":true,"page_title":"DigiLocker error codes","path":"error-codes","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"Digilocker overview","path":"overview","order":0},{"name":"Integration guide","visible_in_sidebar":true,"page_title":"Digilocker quickstart","path":"quickstart","order":1}]},{"name":"AA Gateway","path":"account-aggregator","order":4,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Account Aggregator API integration","path":"api-integration","order":3,"children":[{"name":"Account Availability","visible_in_sidebar":true,"page_title":"Account Aggregator Account Availability","path":"account-availability-apis","order":5},{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-apis","order":2},{"name":"Active FIPs","visible_in_sidebar":true,"page_title":"Account Aggregator Active FIPs","path":"fip-apis","order":4},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"Account Aggregator API reference","path":"api-reference","order":10},{"name":"Consent object","visible_in_sidebar":true,"page_title":"Account Aggregator consent object","path":"consent-object","order":4},{"name":"Embed Setu screens","visible_in_sidebar":true,"page_title":"Account Aggregator Embed Setu screens","path":"embed-setu-aa","order":7},{"name":"FI data types","visible_in_sidebar":true,"page_title":"Account Aggregator FI data types","path":"fi-data-types","order":5},{"name":"Licenses and go live","visible_in_sidebar":true,"page_title":"Account Aggregator license and go live process","path":"licenses-and-go-live","order":8,"children":[{"name":"Go live","visible_in_sidebar":true,"page_title":"FIU go live process","path":"go-live","order":2},{"name":"Licenses","visible_in_sidebar":true,"page_title":"Licenses required to participate in AA","path":"licenses","order":1},{"name":"Participants in AA","visible_in_sidebar":true,"page_title":"Participants in AA","path":"participants-in-aa","order":0}]},{"name":"Multi AA gateway","visible_in_sidebar":true,"page_title":"Account Aggregator multi-AA gateway","path":"multi-aa-gateway","order":2},{"name":"Overview","visible_in_sidebar":true,"page_title":"Account Aggregator overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Account Aggregator quickstart","path":"quickstart","order":1},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Account Aggregator API integration","path":"api-integration","order":3,"children":[{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-apis","order":2},{"name":"Active FIPs","visible_in_sidebar":true,"page_title":"Account Aggregator Active FIPs","path":"fip-apis","order":4},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"Account Aggregator API reference","path":"api-reference","order":10},{"name":"Consent object","visible_in_sidebar":true,"page_title":"Account Aggregator Consent object","path":"consent-object","order":4},{"name":"Embed Setu screens","visible_in_sidebar":true,"page_title":"Account Aggregator Embed Setu screens","path":"embed-setu-aa","order":7},{"name":"End-to-end encryption","visible_in_sidebar":false,"page_title":"Account Aggregator End-to-end encryption","path":"encryption","order":1},{"name":"FI data types","visible_in_sidebar":true,"page_title":"Account Aggregator FI data types","path":"fi-data-types","order":5},{"name":"Get started","visible_in_sidebar":false,"page_title":"Account Aggregator getting started","path":"get-started","order":0},{"name":"Licenses and go live","visible_in_sidebar":true,"page_title":"Account Aggregator license and go live process","path":"licenses-and-go-live","order":8,"children":[{"name":"Go live","visible_in_sidebar":true,"page_title":"FIU go live process","path":"go-live","order":2},{"name":"Licenses","visible_in_sidebar":true,"page_title":"Licenses required to participate in AA","path":"licenses","order":1},{"name":"Participants in AA","visible_in_sidebar":true,"page_title":"Participants in AA","path":"participants-in-aa","order":0}]},{"name":"Migration guide","visible_in_sidebar":true,"page_title":"Account Aggregator Migration Guide","path":"migration-guide","order":6,"children":[{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-flow","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"Account Aggregator overview","path":"overview","order":0},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Account Aggregator Postman integration","path":"postman","order":2},{"name":"Quickstart","visible_in_sidebar":false,"page_title":"Account Aggregator quickstart","path":"quickstart-v1","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Account Aggregator quickstart","path":"quickstart","order":1},{"name":"Request signing","visible_in_sidebar":false,"page_title":"Account Aggregator Request signing","path":"request-signing","order":1}]}]},{"name":"Bank account verification","path":"bav","order":5,"visible_in_sidebar":false,"children":[{"name":"Bundled BAV","visible_in_sidebar":true,"page_title":"Bundled Bank Account Verification","path":"bundled-bav","order":2,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Bundled BAV API integration","path":"api-integration","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"Bundled BAV API reference","path":"api-reference","order":3},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for Bundled BAV","path":"quickstart","order":1}]},{"name":"Penny drop","visible_in_sidebar":true,"page_title":"BAV using penny drop","path":"penny-drop","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BAV API integration","path":"api-integration","order":1,"children":[{"name":"Async API","visible_in_sidebar":true,"page_title":"BAV Async API integration","path":"async","order":2},{"name":"Sync API","visible_in_sidebar":true,"page_title":"BAV Sync API integration","path":"sync","order":1}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV API reference","path":"api-reference","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"BAV Async Penny drop Notifications","path":"notifications","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BAV quickstart","path":"quickstart","order":0}]},{"name":"Penny drop + PennyLess","visible_in_sidebar":true,"page_title":"Bank account verification using Penny drop + PennyLess","path":"pennydrop-pennyless","order":2,"children":[{"name":"API Integration","visible_in_sidebar":true,"page_title":"Penny drop + PennyLess API Integration","path":"api-integration","order":1},{"name":"API reference","visible_in_sidebar":true,"page_title":"Pennydrop-pennyless API reference","path":"api-reference","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Penny drop + PennyLess Notifications","path":"notifications","order":2}]},{"name":"PennyLess Drop","visible_in_sidebar":true,"page_title":"BAV using PennyLess Drop API","path":"pennyless-drop","order":4,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV Pennyless API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for PennyLess drop API","path":"quickstart","order":1}]},{"name":"Reverse Penny drop","visible_in_sidebar":true,"page_title":"BAV using reverse penny drop","path":"reverse-penny-drop","order":3,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"RPD API integration","path":"api-integration","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV RPD API reference","path":"api-reference","order":4},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for reverse penny drop","path":"quickstart","order":1},{"name":"Webhook Auth","visible_in_sidebar":true,"page_title":"Webhook Authentication","path":"webhook-authentication","order":3}]}]},{"name":"Insights","path":"insights","order":5,"versions":["v1","v2","v3"],"default_version":"v3","visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"Error codes","visible_in_sidebar":true,"page_title":"Setu Insights error codes","path":"error-code","order":5},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"api-integration","order":1},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"postman","order":0}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"api-integration","order":1},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"postman","order":0}]}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v2","order":null,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1}]}]},{"name":"Signal IQ","path":"signal-iq","order":6,"visible_in_sidebar":true,"children":[{"name":"AA Flow","visible_in_sidebar":true,"page_title":"Signal IQ - AA Flow","path":"aa-flow","order":1},{"name":"Bring Your Own FI Data","visible_in_sidebar":true,"page_title":"Signal IQ - Bring Your Own FI Data","path":"bring-your-own-fi-data","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"Signal IQ overview","path":"overview","order":0},{"name":"PDF Flow","visible_in_sidebar":true,"page_title":"Signal IQ - PDF Flow","path":"pdf-flow","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"Signal IQ - API reference","path":"report-apis","order":5,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"Signal IQ - API reference","path":"api-reference","order":1}]}]},{"name":"ULI","path":"uli","order":6,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"GSTIN verification API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"GST Verification quickstart","path":"quickstart","order":1}]},{"name":"GST verification","path":"gst","order":6,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"GSTIN verification API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"GST Verification quickstart","path":"quickstart","order":1}]},{"name":"Match APIs","path":"match-apis","order":7,"visible_in_sidebar":false,"children":[{"name":"Name match","visible_in_sidebar":true,"page_title":"Name match APIs","path":"name-match","order":1,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Name Match API reference","path":"api-reference","order":4},{"name":"Examples","visible_in_sidebar":true,"page_title":"Name Match API response examples","path":"examples","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Name Match API overview","path":"overview","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Name Match API quickstart","path":"quickstart","order":2}]}]},{"name":"eKYC","path":"ekyc","order":8,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"eKYC API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"PAN verification quickstart","path":"quickstart","order":1}]}]},{"name":"Dev tools","path":"dev-tools","order":2,"visible_in_sidebar":true,"children":[{"name":"The Bridge","path":"bridge","order":0,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"Analytics and reports","visible_in_sidebar":true,"page_title":"Bridge analytics and reports","path":"analytics-and-reports","order":3},{"name":"Configure products","visible_in_sidebar":true,"page_title":"Bridge explore and configure products","path":"explore-and-configure-products","order":2},{"name":"Glossary","visible_in_sidebar":true,"page_title":"Bridge glossary","path":"glossary","order":1},{"name":"Overview","visible_in_sidebar":true,"page_title":"Bridge overview","path":"overview","order":0},{"name":"Settings","visible_in_sidebar":true,"page_title":"Bridge settings","path":"settings","order":4},{"name":"User profile","visible_in_sidebar":true,"page_title":"Bridge user profile","path":"user-profile","order":5},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"Bridge configuration","visible_in_sidebar":false,"page_title":"Bridge configuration","path":"configure","order":6},{"name":"Generate Token","visible_in_sidebar":false,"page_title":"Bridge generate token","path":"generate-token","order":4},{"name":"Org settings","visible_in_sidebar":true,"page_title":"Bridge org settings","path":"org-settings","order":3,"children":[{"name":"API keys","visible_in_sidebar":true,"page_title":"API keys","path":"api-keys","order":2,"children":[{"name":"JWT Auth","visible_in_sidebar":false,"page_title":"JWT Auth","path":"jwt-auth","order":3},{"name":"JWT","visible_in_sidebar":true,"page_title":"JWT","path":"jwt","order":1},{"name":"OAuth","visible_in_sidebar":true,"page_title":"OAuth","path":"oauth","order":2}]},{"name":"People","visible_in_sidebar":true,"page_title":"People","path":"people","order":1}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"Bridge overview","path":"overview","order":0},{"name":"Reports","visible_in_sidebar":true,"page_title":"Bridge reports","path":"reports","order":1,"children":[{"name":"Types","visible_in_sidebar":false,"page_title":"Report types","path":"types","order":1}]},{"name":"Reports API","visible_in_sidebar":false,"page_title":"Reports API","path":"reports-api","order":5}]}]}]},{"name":"Sample Category","path":"sample-category","order":3,"visible_in_sidebar":false,"children":[{"name":"Sample Product","path":"sample-product","order":0,"visible_in_sidebar":false,"children":[{"name":"Sample Page","visible_in_sidebar":false,"page_title":"Docs sample page","path":"sample-page","order":0}]}]}]} +{"home":[{"name":"Payments","path":"payments","order":0,"visible_in_sidebar":true,"api_reference":true,"children":[{"name":"BBPS BillCollect","path":"bbps","order":0,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS API reference","path":"api-reference","order":9},{"name":"Axis BBPS","visible_in_sidebar":false,"page_title":"Axis BBPS API Approach Document","path":"axis","order":10},{"name":"Bill Structure","visible_in_sidebar":true,"page_title":"BBPS - Bill Structure","path":"bill-structure","order":5,"children":[{"name":"Special cases","visible_in_sidebar":true,"page_title":"BBPS - Bill Structure special cases","path":"special-cases","order":1}]},{"name":"Go live","visible_in_sidebar":true,"page_title":"BBPS - Go live","path":"go-live","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"BBPS - Notifications","path":"notifications","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS - Overview","path":"overview","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS - Quickstart","path":"quickstart","order":2,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS - API integration","path":"api-integration","order":2},{"name":"No-code CSV","visible_in_sidebar":true,"page_title":"BBPS - No-code CSV","path":"no-code-integration","order":1},{"name":"Share bills","visible_in_sidebar":false,"page_title":"BBPS - Share bills","path":"share-biils","order":1}]},{"name":"Reports API","visible_in_sidebar":true,"page_title":"BBPS - Reports API","path":"reports","order":6},{"name":"Additional resources","visible_in_sidebar":true,"page_title":"BBPS - Additional Resources","path":"resources","order":8,"children":[{"name":"Errors","visible_in_sidebar":true,"page_title":"BBPS error codes","path":"errors","order":4},{"name":"JWT authentication","visible_in_sidebar":true,"page_title":"UPI Deeplinks JWT authentication","path":"jwt","order":2},{"name":"OAuth 2.0","visible_in_sidebar":true,"page_title":"BBPS OAuth 2.0","path":"oauth","order":1},{"name":"Settlement object","visible_in_sidebar":true,"page_title":"UPI Deeplinks settlement object","path":"settlement-object","order":3}]}]},{"name":"BBPS BillPay","path":"billpay","order":1,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS Billpay API integration","path":"api-integration","order":1,"children":[{"name":"API payload encryption (optional)","visible_in_sidebar":true,"page_title":"BBPS COU - API payload encryption (AES-CBC)","path":"api-encryption","order":9},{"name":"API reference","visible_in_sidebar":true,"page_title":"COU Direct Connectivity API reference","path":"api-reference","order":10},{"name":"List of APIs","visible_in_sidebar":true,"page_title":"BBPS COU - List of APIs","path":"apis","order":2},{"name":"FX Retail","visible_in_sidebar":true,"page_title":"BBPS COU - FX Retail (Forex category)","path":"fx-retail","order":8},{"name":"Harmonization of TAT","visible_in_sidebar":true,"page_title":"Harmonization Of TAT (Disputes API)","path":"harmonization_of_tat","order":6},{"name":"Objects","visible_in_sidebar":true,"page_title":"BBPS COU - Objects","path":"objects","order":11},{"name":"Paying Bills","visible_in_sidebar":true,"page_title":"Paying Bills","path":"paying-bills","order":4,"children":[{"name":"Paying for options","visible_in_sidebar":true,"page_title":"Paying for alternative options","path":"bill-payment-options","order":4},{"name":"Passing CCF","visible_in_sidebar":true,"page_title":"Customer Convenience Fee (CCF) Integration Guide","path":"customer-convenience-fee","order":3},{"name":"Paying multiple bills","visible_in_sidebar":true,"page_title":"Paying for multiple bills","path":"multi-bill-processing","order":5},{"name":"Paying for plans","visible_in_sidebar":true,"page_title":"Paying for plans","path":"plan-mdm-integration","order":6},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Bill Payment Integration Guide","path":"quickstart","order":1},{"name":"Remitter Details","visible_in_sidebar":true,"page_title":"Passing Remitter Details","path":"remittance_flows_guide","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS COU - API integration","path":"quickstart","order":1},{"name":"Integrating with UPMS","visible_in_sidebar":true,"page_title":"BBPS COU - Integrating with UPMS","path":"upms","order":7},{"name":"Migration Guide to v2","visible_in_sidebar":true,"page_title":"BBPS COU - Migration Guide to v2","path":"v2-migration","order":5},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS COU - Webhooks","path":"webhooks","order":3}]},{"name":"API reference","visible_in_sidebar":false,"page_title":"BillPay API reference","path":"api-reference","order":5},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"mcp","order":null,"children":[{"name":"Integration Guide","visible_in_sidebar":false,"page_title":"MCP Server for Bill Payments - Integration Guide","path":"integration-guide","order":1},{"name":"Tools and Prompts","visible_in_sidebar":false,"page_title":"MCP Server for Bill Payments - Tools and Prompts","path":"tools-and-prompts","order":2}]},{"name":"Prepaid Recharge","visible_in_sidebar":true,"page_title":"BBPS Billpay Prepaid Recharge APIs","path":"mobile-prepaid-recharge","order":3,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge quickstart","path":"quickstart","order":1},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"Mobile Prepaid Recharge Webhooks","path":"webhooks","order":3}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS Billpay Overview","path":"overview","order":0},{"name":"Pre-built screens","visible_in_sidebar":true,"page_title":"BBPS Billpay pre-built screens","path":"pre-built-screens","order":2,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"BBPS Billpay API reference","path":"api-reference-wl","order":4},{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS Billpay API reference","path":"api-reference","order":5},{"name":"Custom payment","visible_in_sidebar":true,"page_title":"BBPS Billpay custom payment","path":"custom-payment","order":2,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay android integration for custom payment","path":"android","order":3},{"name":"Required APIs","visible_in_sidebar":true,"page_title":"BBPS Billpay APIs for custom payment","path":"apis","order":1},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross-platform integration for custom payment","path":"cross-platform","order":3},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration for custom payment","path":"iOS","order":4},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website integration for custom payment","path":"website","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Billpay Quickstart","path":"quickstart","order":1,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay Android integration","path":"android","order":2},{"name":"API","visible_in_sidebar":true,"page_title":"BBPS Billpay API","path":"api","order":2},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross platform integration","path":"cross-platform","order":4},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration","path":"iOS","order":3},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website","path":"website","order":1}]},{"name":"Remitter Details","visible_in_sidebar":true,"page_title":"Remitter Details For Bill Payments Integration Guide","path":"remitter-details","order":4},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS Billpay webhooks","path":"webhooks","order":2}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BBPS Billpay API integration","path":"api-integration","order":1,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"COU Direct Connectivity API reference","path":"api-reference","order":5},{"name":"List of APIs","visible_in_sidebar":true,"page_title":"BBPS COU - List of APIs","path":"apis","order":2},{"name":"Deprecated APIs","visible_in_sidebar":false,"page_title":"BBPS COU - API integration (deprecated)","path":"deprecated","order":4,"children":[{"name":"Mock environment","visible_in_sidebar":false,"page_title":"BBPS Billpay Mock environment","path":"mock-environment","order":2},{"name":"Polling","visible_in_sidebar":false,"page_title":"BBPS Billpay polling","path":"polling","order":2}]},{"name":"Objects","visible_in_sidebar":true,"page_title":"BBPS COU - Objects","path":"objects","order":3},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS COU - API integration","path":"quickstart","order":1},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS COU - Webhooks","path":"webhooks","order":4}]},{"name":"API reference","visible_in_sidebar":false,"page_title":"BillPay API reference","path":"api-reference","order":5},{"name":"Overview","visible_in_sidebar":true,"page_title":"BBPS Billpay Overview","path":"overview","order":0},{"name":"Pre-built screens","visible_in_sidebar":true,"page_title":"BBPS Billpay pre-built screens","path":"pre-built-screens","order":2,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"BBPS Billpay API reference","path":"api-reference-wl","order":4},{"name":"API reference","visible_in_sidebar":true,"page_title":"BBPS Billpay API reference","path":"api-reference","order":4},{"name":"Custom payment","visible_in_sidebar":true,"page_title":"BBPS Billpay custom payment","path":"custom-payment","order":2,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay android integration for custom payment","path":"android","order":3},{"name":"Required APIs","visible_in_sidebar":true,"page_title":"BBPS Billpay APIs for custom payment","path":"apis","order":1},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross-platform integration for custom payment","path":"cross-platform","order":3},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration for custom payment","path":"iOS","order":4},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website integration for custom payment","path":"website","order":2}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BBPS Billpay Quickstart","path":"quickstart","order":1,"children":[{"name":"Android","visible_in_sidebar":true,"page_title":"BBPS Billpay Android integration","path":"android","order":2},{"name":"API","visible_in_sidebar":true,"page_title":"BBPS Billpay API","path":"api","order":2},{"name":"Cross platform","visible_in_sidebar":true,"page_title":"BBPS Billpay cross platform integration","path":"cross-platform","order":4},{"name":"iOS","visible_in_sidebar":true,"page_title":"BBPS Billpay iOS integration","path":"iOS","order":3},{"name":"Website","visible_in_sidebar":true,"page_title":"BBPS Billpay website","path":"website","order":1}]},{"name":"Webhooks","visible_in_sidebar":true,"page_title":"BBPS Billpay webhooks","path":"webhooks","order":2}]}]}]},{"name":"WhatsApp Collect","path":"whatsapp-collect","order":3,"visible_in_sidebar":true,"children":[{"name":"API Integration","visible_in_sidebar":true,"page_title":"WhatsApp Collect API Integration","path":"api-integration","order":3},{"name":"API reference","visible_in_sidebar":true,"page_title":"WhatsApp Collect API reference","path":"api-reference","order":5},{"name":"Error codes","visible_in_sidebar":true,"page_title":"WhatsApp Collect error codes","path":"errors","order":4},{"name":"Collection journey","visible_in_sidebar":true,"page_title":"WhatsApp Collect Journey","path":"journey","order":1},{"name":"Overview","visible_in_sidebar":true,"page_title":"WhatsApp Collect Overview","path":"overview","order":0},{"name":"Collection reminders","visible_in_sidebar":true,"page_title":"WhatsApp Collect reminders","path":"reminders","order":2}]},{"name":"UPI DeepLinks","path":"upi-deeplinks","order":4,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Deeplinks API reference","path":"api-reference","order":8},{"name":"Notifications","visible_in_sidebar":true,"page_title":"UPI Deeplinks Notifications","path":"notifications","order":6},{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Deeplinks Overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Deeplinks quickstart","path":"quickstart","order":1,"children":[{"name":"Go Live","visible_in_sidebar":true,"page_title":"UPI Deeplinks go live","path":"go-live","order":1}]},{"name":"Refunds","visible_in_sidebar":true,"page_title":"UPI Deeplinks Refunds","path":"refunds","order":4},{"name":"Reports API","visible_in_sidebar":true,"page_title":"UPI Deeplinks Reports API","path":"reports","order":5},{"name":"Additional resources","visible_in_sidebar":true,"page_title":"UPI Deeplinks additonal resources","path":"resources","order":6,"children":[{"name":"JWT authentication","visible_in_sidebar":true,"page_title":"UPI Deeplinks JWT authentication","path":"jwt","order":2},{"name":"OAuth 2.0","visible_in_sidebar":true,"page_title":"UPI Deeplinks OAuth 2.0","path":"oauth","order":1},{"name":"Settlement object","visible_in_sidebar":true,"page_title":"UPI Deeplinks settlement object","path":"settlement-object","order":3}]},{"name":"SDKs","visible_in_sidebar":true,"page_title":"UPI Deeplinks SDKs","path":"sdks","order":3},{"name":"Third party verification","visible_in_sidebar":true,"page_title":"UPI Deeplinks third party verification","path":"third-party-verification","order":3}]},{"name":"UPI Setu","path":"umap","order":7,"visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"UPI Setu - API integration","path":"api-integration","order":2,"children":[{"name":"Aggregators","visible_in_sidebar":true,"page_title":"UPI Setu - API integration for aggregators","path":"aggregators","order":1},{"name":"Merchants","visible_in_sidebar":true,"page_title":"UPI Setu - API integration for merchants","path":"merchants","order":2}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Setu - API reference","path":"api-reference","order":8},{"name":"UPI mandates","visible_in_sidebar":true,"page_title":"UPI mandates","path":"mandates","order":4,"children":[{"name":"Mandate operations","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Mandate operations","path":"generic","order":5,"children":[{"name":"Pause","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Pause","path":"pause","order":3},{"name":"Revoke","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Revoke","path":"revoke","order":2},{"name":"Unpause","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Unpause","path":"unpause","order":4},{"name":"Update","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Update","path":"update","order":1}]},{"name":"OneShot","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - OneShot","path":"one-shot","order":1,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create One Time Mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute One Time Mandate","path":"execute","order":3},{"name":"Pre Debit Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Send One Time Mandate Pre Debit Notification","path":"pre-debit-notify","order":2}]},{"name":"Recur","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Recur","path":"recur","order":3,"children":[{"name":"Check payment status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create recurring mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute mandate","path":"execute","order":3},{"name":"Pre Debit Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Send Recurring Mandate Pre Debit Notification","path":"pre-debit-notify","order":2}]},{"name":"Reserve","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Reserve","path":"reserve","order":2,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":4},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create Reserve Mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute Reserve Mandate","path":"execute","order":3}]},{"name":"ReservePlus","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - ReservePlus","path":"reserve-plus","order":4,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Check payment status","path":"check-status","order":3},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Create single block multi-debit","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Mandates - Execute single block multi-debit","path":"execute","order":2}]}]},{"name":"Merchant on-boarding","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant onboarding","path":"merchant-onboarding","order":2,"children":[{"name":"Check VPA availability","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Check VPA availability","path":"check-vpa-availability-api","order":2},{"name":"Setup a merchant","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Setup merchant","path":"create-merchant-api","order":1},{"name":"Registering VPA","visible_in_sidebar":true,"page_title":"UPI Setu - Merchant on-boarding - Registering a VPA","path":"create-vpa-api","order":3}]},{"name":"Notifications and alerts","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts","path":"notifications","order":7,"children":[{"name":"VPA verification","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Customer VPA verification","path":"customer-vpa-verification","order":6},{"name":"Mandates","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandates","path":"mandates","order":3,"children":[{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Creation of mandate","path":"create","order":1},{"name":"Execute","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandate execution","path":"execute","order":7},{"name":"Notify","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Mandate pre-debit notifications","path":"notify","order":6},{"name":"Pause","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Pausing mandate","path":"pause","order":4},{"name":"Revoke","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Revoking mandate","path":"revoke","order":3},{"name":"Unpause","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Unpausing mandate","path":"unpause","order":5},{"name":"Update","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Updating mandate","path":"update","order":2}]},{"name":"Payments","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Payments","path":"payments","order":2},{"name":"Refunds","visible_in_sidebar":true,"page_title":"UPI Setu - Notifications and alerts - Refunds","path":"refunds","order":4},{"name":"Verify signature","visible_in_sidebar":true,"page_title":"UMAP - Events and notifications","path":"verify-signature","order":1}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Setu - Overview","path":"overview","order":0},{"name":"UPI payments","visible_in_sidebar":true,"page_title":"UPI payments","path":"payments","order":3,"children":[{"name":"Collect","visible_in_sidebar":true,"page_title":"UPI payments - Collect","path":"collect","order":2,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu payments - Collect request - Check payment status","path":"check-status","order":3},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create collect request","path":"create-collect-request","order":2},{"name":"Verify customer VPA","visible_in_sidebar":true,"page_title":"UPI Setu payments - Verify customer VPA","path":"verify-customer-vpa-api","order":1}]},{"name":"Flash","visible_in_sidebar":true,"page_title":"UPI payments - Flash","path":"flash","order":1,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu payments - Intent/QR - Check payment status","path":"check-status","order":2},{"name":"Dynamic QR","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create Dynamic QR","path":"create-dqr","order":1},{"name":"Static QR","visible_in_sidebar":true,"page_title":"UPI Setu payments - Create Static QR","path":"create-sqr","order":1}]},{"name":"TPV","visible_in_sidebar":true,"page_title":"UPI payments - TPV","path":"tpv","order":3,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Payments - TPV - Check payment status","path":"check-status","order":2},{"name":"Create","visible_in_sidebar":true,"page_title":"UPI Setu - Payments - Create TPV API","path":"create-tpv","order":1},{"name":"Payments","visible_in_sidebar":true,"page_title":"UMAP - Notifications and alerts - Payments","path":"life-cycle","order":1}]}]},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart","path":"quickstart","order":1,"children":[{"name":"Aggregators","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart for aggregators","path":"aggregators","order":1},{"name":"Merchants","visible_in_sidebar":true,"page_title":"UPI Setu - Quickstart for merchants","path":"merchants","order":2}]},{"name":"Refunds and disputes","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes","path":"refunds-disputes","order":6,"children":[{"name":"Check refund status","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Check refund status API","path":"check-refund-status-api","order":2},{"name":"Create refund","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Create refund API","path":"create-refund-api","order":1},{"name":"Fetch dispute","visible_in_sidebar":true,"page_title":"UPI Setu - Refunds and disputes - Fetch dispute API","path":"fetch-dispute-api","order":3}]},{"name":"Transaction Monitoring","visible_in_sidebar":false,"page_title":"UPI Setu - Transaction Monitoring","path":"transaction-monitoring","order":5,"children":[{"name":"Check status","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Check status API","path":"check-status-api","order":1},{"name":"Check status history","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Check status sistory API","path":"check-status-history-api","order":2},{"name":"Fetch payment","visible_in_sidebar":true,"page_title":"UPI Setu - Transaction monitoring - Fetch payment API","path":"fetch-payment-api","order":3}]}]},{"name":"UPI Issuance","path":"upi-issuance","order":8,"visible_in_sidebar":true,"children":[{"name":"Overview","visible_in_sidebar":true,"page_title":"UPI Issuance Overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"UPI Issuance Quickstart","path":"quickstart","order":1},{"name":"The API envelope","visible_in_sidebar":true,"page_title":"UPI Issuance API envelope","path":"api-envelope","order":2},{"name":"User Onboarding","visible_in_sidebar":true,"page_title":"UPI Issuance User Onboarding","path":"onboarding","order":3,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"UPI Issuance API integration","path":"api-integration","order":1,"children":[{"name":"Device binding","visible_in_sidebar":true,"page_title":"UPI Issuance device binding","path":"device-binding","order":0},{"name":"Programs","visible_in_sidebar":true,"page_title":"UPI Issuance programs","path":"programs","order":3},{"name":"OTP verification","visible_in_sidebar":true,"page_title":"UPI Issuance OTP verification","path":"user-otp","order":1},{"name":"VPA management","visible_in_sidebar":true,"page_title":"UPI Issuance VPA management","path":"vpa-management","order":2}]},{"name":"Onboarding states","visible_in_sidebar":true,"page_title":"UPI Issuance onboarding states","path":"onboarding-states","order":0}]},{"name":"Payee blocklist","visible_in_sidebar":true,"page_title":"UPI Issuance payee blocklist","path":"payee-blocklist","order":4},{"name":"VPA resolution","visible_in_sidebar":true,"page_title":"UPI Issuance VPA resolution","path":"vpa-resolution","order":5},{"name":"Testing on QA env","visible_in_sidebar":true,"page_title":"UPI Issuance QA env testing","path":"qa-testing","order":6},{"name":"API reference","visible_in_sidebar":true,"page_title":"UPI Issuance API reference","path":"api-reference","order":7}]}]},{"name":"Data","path":"data","order":1,"visible_in_sidebar":true,"children":[{"name":"KYC","path":"kyc","order":0,"visible_in_sidebar":true,"children":[{"name":"Secure Data Add-On","visible_in_sidebar":true,"page_title":"Setu Encrypted APIs","path":"encryption","order":2},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu KYC Overview","path":"overview","order":1}]},{"name":"PAN verification","path":"pan","order":0,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"PAN verification API reference","path":"api-reference","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"PAN verification quickstart","path":"quickstart","order":0}]},{"name":"Aadhaar eSign","path":"esign","order":2,"visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Aadhaar eSign API reference","path":"api-reference","order":9},{"name":"Error codes","visible_in_sidebar":true,"page_title":"Aadhaar eSign error codes","path":"error-codes","order":8},{"name":"eStamp overview","visible_in_sidebar":true,"page_title":"eStamp overview","path":"estamp","order":2},{"name":"Flexible eSign guide","visible_in_sidebar":true,"page_title":"Integration guide with flexible signature coordinates","path":"flexi-esign","order":4},{"name":"eSign Name Match","visible_in_sidebar":true,"page_title":"Aadhaar eSign Name Match","path":"name-match","order":6},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Aadhaar eSign Notifications","path":"notifications","order":7},{"name":"Overview","visible_in_sidebar":true,"page_title":"Aadhaar eSign overview","path":"overview","order":1},{"name":"PDF templates","visible_in_sidebar":true,"page_title":"Integration guide with pdf templating API's","path":"pdf-templating","order":5},{"name":"Integration guide","visible_in_sidebar":true,"page_title":"Aadhaar eSign integration guide","path":"quickstart","order":3}]},{"name":"DigiLocker","path":"digilocker","order":3,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Digilocker API reference","path":"api-reference","order":3},{"name":"Error codes","visible_in_sidebar":true,"page_title":"DigiLocker error codes","path":"error-codes","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"Digilocker overview","path":"overview","order":0},{"name":"Integration guide","visible_in_sidebar":true,"page_title":"Digilocker quickstart","path":"quickstart","order":1}]},{"name":"AA Gateway","path":"account-aggregator","order":4,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Account Aggregator API integration","path":"api-integration","order":3,"children":[{"name":"Account Availability","visible_in_sidebar":true,"page_title":"Account Aggregator Account Availability","path":"account-availability-apis","order":5},{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-apis","order":2},{"name":"Active FIPs","visible_in_sidebar":true,"page_title":"Account Aggregator Active FIPs","path":"fip-apis","order":4},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"Account Aggregator API reference","path":"api-reference","order":10},{"name":"Consent object","visible_in_sidebar":true,"page_title":"Account Aggregator consent object","path":"consent-object","order":4},{"name":"Embed Setu screens","visible_in_sidebar":true,"page_title":"Account Aggregator Embed Setu screens","path":"embed-setu-aa","order":7},{"name":"FI data types","visible_in_sidebar":true,"page_title":"Account Aggregator FI data types","path":"fi-data-types","order":5},{"name":"Licenses and go live","visible_in_sidebar":true,"page_title":"Account Aggregator license and go live process","path":"licenses-and-go-live","order":8,"children":[{"name":"Go live","visible_in_sidebar":true,"page_title":"FIU go live process","path":"go-live","order":2},{"name":"Licenses","visible_in_sidebar":true,"page_title":"Licenses required to participate in AA","path":"licenses","order":1},{"name":"Participants in AA","visible_in_sidebar":true,"page_title":"Participants in AA","path":"participants-in-aa","order":0}]},{"name":"Multi AA gateway","visible_in_sidebar":true,"page_title":"Account Aggregator multi-AA gateway","path":"multi-aa-gateway","order":2},{"name":"Overview","visible_in_sidebar":true,"page_title":"Account Aggregator overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Account Aggregator quickstart","path":"quickstart","order":1},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Account Aggregator API integration","path":"api-integration","order":3,"children":[{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-apis","order":2},{"name":"Active FIPs","visible_in_sidebar":true,"page_title":"Account Aggregator Active FIPs","path":"fip-apis","order":4},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"Account Aggregator API reference","path":"api-reference","order":10},{"name":"Consent object","visible_in_sidebar":true,"page_title":"Account Aggregator Consent object","path":"consent-object","order":4},{"name":"Embed Setu screens","visible_in_sidebar":true,"page_title":"Account Aggregator Embed Setu screens","path":"embed-setu-aa","order":7},{"name":"End-to-end encryption","visible_in_sidebar":false,"page_title":"Account Aggregator End-to-end encryption","path":"encryption","order":1},{"name":"FI data types","visible_in_sidebar":true,"page_title":"Account Aggregator FI data types","path":"fi-data-types","order":5},{"name":"Get started","visible_in_sidebar":false,"page_title":"Account Aggregator getting started","path":"get-started","order":0},{"name":"Licenses and go live","visible_in_sidebar":true,"page_title":"Account Aggregator license and go live process","path":"licenses-and-go-live","order":8,"children":[{"name":"Go live","visible_in_sidebar":true,"page_title":"FIU go live process","path":"go-live","order":2},{"name":"Licenses","visible_in_sidebar":true,"page_title":"Licenses required to participate in AA","path":"licenses","order":1},{"name":"Participants in AA","visible_in_sidebar":true,"page_title":"Participants in AA","path":"participants-in-aa","order":0}]},{"name":"Migration guide","visible_in_sidebar":true,"page_title":"Account Aggregator Migration Guide","path":"migration-guide","order":6,"children":[{"name":"Consent flow","visible_in_sidebar":true,"page_title":"Account Aggregator Consent flow","path":"consent-flow","order":1},{"name":"Data flow","visible_in_sidebar":true,"page_title":"Account Aggregator Data flow","path":"data-flow","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Account Aggregator Notifications","path":"notifications","order":3}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"Account Aggregator overview","path":"overview","order":0},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Account Aggregator Postman integration","path":"postman","order":2},{"name":"Quickstart","visible_in_sidebar":false,"page_title":"Account Aggregator quickstart","path":"quickstart-v1","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Account Aggregator quickstart","path":"quickstart","order":1},{"name":"Request signing","visible_in_sidebar":false,"page_title":"Account Aggregator Request signing","path":"request-signing","order":1}]}]},{"name":"Bank account verification","path":"bav","order":5,"visible_in_sidebar":false,"children":[{"name":"Bundled BAV","visible_in_sidebar":true,"page_title":"Bundled Bank Account Verification","path":"bundled-bav","order":2,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Bundled BAV API integration","path":"api-integration","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"Bundled BAV API reference","path":"api-reference","order":3},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for Bundled BAV","path":"quickstart","order":1}]},{"name":"Penny drop","visible_in_sidebar":true,"page_title":"BAV using penny drop","path":"penny-drop","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"BAV API integration","path":"api-integration","order":1,"children":[{"name":"Async API","visible_in_sidebar":true,"page_title":"BAV Async API integration","path":"async","order":2},{"name":"Sync API","visible_in_sidebar":true,"page_title":"BAV Sync API integration","path":"sync","order":1}]},{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV API reference","path":"api-reference","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"BAV Async Penny drop Notifications","path":"notifications","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"BAV quickstart","path":"quickstart","order":0}]},{"name":"Penny drop + PennyLess","visible_in_sidebar":true,"page_title":"Bank account verification using Penny drop + PennyLess","path":"pennydrop-pennyless","order":2,"children":[{"name":"API Integration","visible_in_sidebar":true,"page_title":"Penny drop + PennyLess API Integration","path":"api-integration","order":1},{"name":"API reference","visible_in_sidebar":true,"page_title":"Pennydrop-pennyless API reference","path":"api-reference","order":3},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Penny drop + PennyLess Notifications","path":"notifications","order":2}]},{"name":"PennyLess Drop","visible_in_sidebar":true,"page_title":"BAV using PennyLess Drop API","path":"pennyless-drop","order":4,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV Pennyless API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for PennyLess drop API","path":"quickstart","order":1}]},{"name":"Reverse Penny drop","visible_in_sidebar":true,"page_title":"BAV using reverse penny drop","path":"reverse-penny-drop","order":3,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"RPD API integration","path":"api-integration","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"BAV RPD API reference","path":"api-reference","order":4},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Quickstart for reverse penny drop","path":"quickstart","order":1},{"name":"Webhook Auth","visible_in_sidebar":true,"page_title":"Webhook Authentication","path":"webhook-authentication","order":3}]}]},{"name":"Insights","path":"insights","order":5,"versions":["v1","v2","v3"],"default_version":"v3","visible_in_sidebar":true,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"Error codes","visible_in_sidebar":true,"page_title":"Setu Insights error codes","path":"error-code","order":5},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"api-integration","order":1},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"postman","order":0}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1,"children":[{"name":"API integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"api-integration","order":1},{"name":"Postman integration","visible_in_sidebar":true,"page_title":"Setu Insights Postman integration","path":"postman","order":0}]}]},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v2","order":null,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Setu Insights API reference","path":"api-reference","order":4},{"name":"List of insights","visible_in_sidebar":true,"page_title":"All Setu insights","path":"insights","order":2},{"name":"Notifications","visible_in_sidebar":true,"page_title":"Setu Insights notifications","path":"notifications","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Setu Insights overview","path":"overview","order":0},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Setu Insights quickstart","path":"quickstart","order":1}]}]},{"name":"Signal IQ","path":"signal-iq","order":6,"visible_in_sidebar":true,"children":[{"name":"AA Flow","visible_in_sidebar":true,"page_title":"Signal IQ - AA Flow","path":"aa-flow","order":1},{"name":"Bring Your Own FI Data","visible_in_sidebar":true,"page_title":"Signal IQ - Bring Your Own FI Data","path":"bring-your-own-fi-data","order":4},{"name":"Overview","visible_in_sidebar":true,"page_title":"Signal IQ overview","path":"overview","order":0},{"name":"PDF Flow","visible_in_sidebar":true,"page_title":"Signal IQ - PDF Flow","path":"pdf-flow","order":2},{"name":"API reference","visible_in_sidebar":true,"page_title":"Signal IQ - API reference","path":"report-apis","order":5,"children":[{"name":"API reference","visible_in_sidebar":false,"page_title":"Signal IQ - API reference","path":"api-reference","order":1}]}]},{"name":"ULI","path":"uli","order":6,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"GSTIN verification API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"GST Verification quickstart","path":"quickstart","order":1}]},{"name":"GST verification","path":"gst","order":6,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"GSTIN verification API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"GST Verification quickstart","path":"quickstart","order":1}]},{"name":"Match APIs","path":"match-apis","order":7,"visible_in_sidebar":false,"children":[{"name":"Name match","visible_in_sidebar":true,"page_title":"Name match APIs","path":"name-match","order":1,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"Name Match API reference","path":"api-reference","order":4},{"name":"Examples","visible_in_sidebar":true,"page_title":"Name Match API response examples","path":"examples","order":3},{"name":"Overview","visible_in_sidebar":true,"page_title":"Name Match API overview","path":"overview","order":1},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"Name Match API quickstart","path":"quickstart","order":2}]}]},{"name":"eKYC","path":"ekyc","order":8,"visible_in_sidebar":false,"children":[{"name":"API reference","visible_in_sidebar":true,"page_title":"eKYC API reference","path":"api-reference","order":2},{"name":"Quickstart","visible_in_sidebar":true,"page_title":"PAN verification quickstart","path":"quickstart","order":1}]}]},{"name":"Dev tools","path":"dev-tools","order":2,"visible_in_sidebar":true,"children":[{"name":"The Bridge","path":"bridge","order":0,"versions":["v1","v2"],"default_version":"v2","visible_in_sidebar":true,"children":[{"name":"Analytics and reports","visible_in_sidebar":true,"page_title":"Bridge analytics and reports","path":"analytics-and-reports","order":3},{"name":"Configure products","visible_in_sidebar":true,"page_title":"Bridge explore and configure products","path":"explore-and-configure-products","order":2},{"name":"Glossary","visible_in_sidebar":true,"page_title":"Bridge glossary","path":"glossary","order":1},{"name":"Overview","visible_in_sidebar":true,"page_title":"Bridge overview","path":"overview","order":0},{"name":"Settings","visible_in_sidebar":true,"page_title":"Bridge settings","path":"settings","order":4},{"name":"User profile","visible_in_sidebar":true,"page_title":"Bridge user profile","path":"user-profile","order":5},{"name":null,"visible_in_sidebar":null,"page_title":null,"path":"v1","order":null,"children":[{"name":"Bridge configuration","visible_in_sidebar":false,"page_title":"Bridge configuration","path":"configure","order":6},{"name":"Generate Token","visible_in_sidebar":false,"page_title":"Bridge generate token","path":"generate-token","order":4},{"name":"Org settings","visible_in_sidebar":true,"page_title":"Bridge org settings","path":"org-settings","order":3,"children":[{"name":"API keys","visible_in_sidebar":true,"page_title":"API keys","path":"api-keys","order":2,"children":[{"name":"JWT Auth","visible_in_sidebar":false,"page_title":"JWT Auth","path":"jwt-auth","order":3},{"name":"JWT","visible_in_sidebar":true,"page_title":"JWT","path":"jwt","order":1},{"name":"OAuth","visible_in_sidebar":true,"page_title":"OAuth","path":"oauth","order":2}]},{"name":"People","visible_in_sidebar":true,"page_title":"People","path":"people","order":1}]},{"name":"Overview","visible_in_sidebar":true,"page_title":"Bridge overview","path":"overview","order":0},{"name":"Reports","visible_in_sidebar":true,"page_title":"Bridge reports","path":"reports","order":1,"children":[{"name":"Types","visible_in_sidebar":false,"page_title":"Report types","path":"types","order":1}]},{"name":"Reports API","visible_in_sidebar":false,"page_title":"Reports API","path":"reports-api","order":5}]}]}]},{"name":"Sample Category","path":"sample-category","order":3,"visible_in_sidebar":false,"children":[{"name":"Sample Product","path":"sample-product","order":0,"visible_in_sidebar":false,"children":[{"name":"Sample Page","visible_in_sidebar":false,"page_title":"Docs sample page","path":"sample-page","order":0}]}]}]} \ No newline at end of file diff --git a/content/payments/upi-issuance/api-envelope.mdx b/content/payments/upi-issuance/api-envelope.mdx new file mode 100644 index 00000000..24a765aa --- /dev/null +++ b/content/payments/upi-issuance/api-envelope.mdx @@ -0,0 +1,111 @@ +--- +sidebar_title: The API envelope +page_title: UPI Issuance API envelope +order: 2 +visible_in_sidebar: true +--- + +## The API envelope + +Every request and response body is encrypted, keeping PII (`deviceId`, `mobile`, account details) out of plain text on the wire. The scheme is a standard hybrid encryption envelope. + +### What travels where + +- **PII in the body.** `deviceId` and `mobile` are in the encrypted body, never in headers or the URL. +- **`idempotencyKey` in a header.** It stays a plain header, outside the encrypted body. +- **Reads are POSTs.** Even pure reads (`binding-status`, `list-vpas`) are `POST` so their PII body can be encrypted. +- **The TPAP / Issuing App's credentials sign the request.** Setu issues the TPAP / Issuing App a `clientId` and a `clientSecret`. Every request carries that `clientId` and a `sig` (an HMAC of the request payload keyed by the client secret) so Setu can authenticate that the call is really from the TPAP / Issuing App — see [Signing the request](#signing-the-request). + +
+ +### Request format + +The TPAP / Issuing App encrypts the request body into an envelope object. Alongside the three base64 encryption fields, it carries the TPAP / Issuing App's `clientId` and the request signature `sig`: + + + {`{ + "ct": "", + "sk": "", + "iv": "", + "clientId": "", + "sig": "" +}`} + + +For each request, the TPAP / Issuing App generates a random **32-byte AES-256 key** and **16-byte IV**, encrypts the JSON body with **AES-256-CBC** and PKCS#7 padding, and wraps the session key with **RSA-OAEP (SHA-1)** under Setu's public key. The TPAP / Issuing App keeps the session key and IV to decrypt the response. + +
+ +### Signing the request + +Setu issues the TPAP / Issuing App two credentials: a **`clientId`** and a **`clientSecret`**. On every request, the TPAP / Issuing App: + +1. Sets `clientId` to the value Setu issued. +2. Computes `sig` = **base64( HMAC-SHA256( clientSecret, plaintext JSON body ) )** — signing the *same* JSON string that is encrypted, **before** encryption, using the `clientSecret` as the HMAC key. + +Setu verifies the signature after decrypting the request. A missing signature, a signature that does not verify, or a `clientId` that does not belong to the TPAP / Issuing App is rejected with **`401`** and the error code **`invalid-signature`**. + + + Keep the clientSecret secret: it lives only on the TPAP / Issuing App's backend, never in a mobile app or browser. Sign on the server, then send the envelope. If the TPAP / Issuing App rotates the secret with Setu, the previous secret keeps working through the overlap window, so the cut-over happens without downtime. + + +### Response format + +The response is encrypted under the same session key and IV the TPAP / Issuing App generated: + + + {`{ + "ct": "", + "oha": "" +}`} + + +The TPAP / Issuing App decrypts `ct` with the session key and IV, then verifies `oha` matches the SHA-256 of the decrypted plaintext. + + + The wire suite is fixed: RSA-OAEP-SHA1 for the key wrap, AES-256-CBC with PKCS#7 + for the body, SHA-256 for the response integrity hash. + + +
+ +### Reference implementation (Python) + + + {`import os, json, base64, hashlib, hmac +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes, serialization +def _pad(b, k=16): n = k - len(b) % k; return b + bytes([n]) * n +def _unpad(b): return b[:-b[-1]] +def encrypt(pub_pem: str, client_id: str, client_secret: str, body: dict): + pub = serialization.load_pem_public_key(pub_pem.encode()) + key, iv = os.urandom(32), os.urandom(16) + plaintext = json.dumps(body).encode() + sk = pub.encrypt(key, padding.OAEP(mgf=padding.MGF1(hashes.SHA1()), + algorithm=hashes.SHA1(), label=None)) + enc = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor() + ct = enc.update(_pad(plaintext)) + enc.finalize() + sig = hmac.new(client_secret.encode(), plaintext, hashlib.sha256).digest() + env = {"ct": base64.b64encode(ct).decode(), + "sk": base64.b64encode(sk).decode(), + "iv": base64.b64encode(iv).decode(), + "clientId": client_id, + "sig": base64.b64encode(sig).decode()} + return env, key, iv +def decrypt_response(key, iv, enc: dict): + ct = base64.b64decode(enc["ct"]) + dec = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor() + plain = _unpad(dec.update(ct) + dec.finalize()) + assert enc["oha"] == hashlib.sha256(plain).hexdigest(), "integrity mismatch" + return json.loads(plain)`} + + +`encrypt()` signs the plaintext body with the `clientSecret`, then returns the request envelope `env` — the `{ct, sk, iv, clientId, sig}` object — along with the session `key` and `iv`. The TPAP / Issuing App sends `env` as the request body with `Content-Type: application/json`, and passes the same `key` and `iv` to `decrypt_response()` to read the response. The signature is computed over the exact `json.dumps(body)` bytes that are encrypted, so encrypt and sign use the *same* serialization. + +### Next + +- **[User Onboarding](/payments/upi-issuance/onboarding)** — bind a device, create a VPA, and verify OTPs. +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every scenario. + + diff --git a/content/payments/upi-issuance/api-reference.mdx b/content/payments/upi-issuance/api-reference.mdx new file mode 100644 index 00000000..bf960bf7 --- /dev/null +++ b/content/payments/upi-issuance/api-reference.mdx @@ -0,0 +1,25 @@ +--- +sidebar_title: API reference +page_title: UPI Issuance API reference +order: 7 +visible_in_sidebar: true +--- + +## API reference + +The complete, spec-backed API reference (request and response schemas, every field, every error) is being finalised and will render here from the OpenAPI definition. + +In the meantime, each operation is documented with its request, response, and error table under [API integration](/payments/upi-issuance/onboarding/api-integration): + +- [Device binding](/payments/upi-issuance/onboarding/api-integration/device-binding) — generate binding token, `binding-status` +- [OTP verification](/payments/upi-issuance/onboarding/api-integration/user-otp) — `otp/request`, `otp/verify` +- [VPA management](/payments/upi-issuance/onboarding/api-integration/vpa-management) — `vpa/check`, `create-vpa`, `get-vpa`, `list-vpas`, `vpa/deregister` +- [VPA resolution](/payments/upi-issuance/vpa-resolution) — `vpa/resolve` +- [Payee blocklist](/payments/upi-issuance/payee-blocklist) — `block-vpa`, `unblock-vpa`, `list-blocked-vpas` +- [Programs](/payments/upi-issuance/onboarding/api-integration/programs) — `POST /programs`, `PATCH /programs/{id}` + +### Next + +- **[User Onboarding](/payments/upi-issuance/onboarding)** — the onboarding flow and a step-by-step API walkthrough. + + diff --git a/content/payments/upi-issuance/onboarding.mdx b/content/payments/upi-issuance/onboarding.mdx new file mode 100644 index 00000000..88a31036 --- /dev/null +++ b/content/payments/upi-issuance/onboarding.mdx @@ -0,0 +1,49 @@ +--- +sidebar_title: User Onboarding +page_title: UPI Issuance User Onboarding +order: 3 +visible_in_sidebar: true +--- + +## User Onboarding + +Onboarding gets a user ready to transact on UPI. It runs in three steps. + +**1. Device binding.** The TPAP / Issuing App requests a binding token from Setu, which generates one and returns it with a Virtual Mobile Number (VMN). The token is sent as a silent SMS from the user's SIM to the VMN. The telecom then notifies Setu of the VMN, the sending mobile, and the SMS body, and Setu checks all three to bind the device to the user. + +**2. VPA creation.** The TPAP / Issuing App creates a VPA — the user's UPI address — over the user's account. + +**3. OTP verification.** The TPAP / Issuing App requests an OTP from Setu, Setu delivers it to the user's mobile by SMS, and the TPAP / Issuing App submits it back to Setu to activate the VPA. Today this is **required on Android** and **not needed on iOS**. + +New user onboarding and VPA creation: device binding, VPA creation, and the OTP that activates the new VPA (Android) + +

New user onboarding / VPA creation for an existing user

+ +Once onboarded, the user has an active VPA and can transact on UPI. + +On a **device change**, only device binding is repeated — a device change does not require VPA creation. As at onboarding, an OTP is required on Android and not on iOS. + +On **iOS**, a successful SIM binding moves the user status straight to `active`. + +On **Android**, a successful SIM binding moves the user status to `device-bound`. Requesting an OTP then moves the user to `otp-pending`. A successful OTP verification moves the user status to `active`. + +Device change: device binding on the new device, with an OTP on Android moving the user from device-bound to active and iOS activating directly, no VPA creation + +

Device change — OTP verification, no VPA creation

+ + + NPCI is not involved in onboarding for PPI Issuing Apps: the account provider is + already set, and PPI accounts do not need a UPI PIN to be set up. + + +### In this section + +- **[Onboarding states](/payments/upi-issuance/onboarding/onboarding-states)** — the user and VPA state machines, and what moves them. +- **[API integration](/payments/upi-issuance/onboarding/api-integration)** — a step-by-step walkthrough of each onboarding operation. + +### Next + +- **[Payee blocklist](/payments/upi-issuance/payee-blocklist)** — block, unblock, and list blocked payee VPAs. +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every scenario. + + diff --git a/content/payments/upi-issuance/onboarding/api-integration.mdx b/content/payments/upi-issuance/onboarding/api-integration.mdx new file mode 100644 index 00000000..ca2b8fd5 --- /dev/null +++ b/content/payments/upi-issuance/onboarding/api-integration.mdx @@ -0,0 +1,46 @@ +--- +sidebar_title: API integration +page_title: UPI Issuance API integration +order: 1 +visible_in_sidebar: true +--- + +## API integration + +A step-by-step walkthrough of each onboarding operation. All routes are under `/api/v1`, all body-carrying routes use [the envelope](/payments/upi-issuance/api-envelope), and `idempotencyKey` is always a header. + +- **[Device binding](/payments/upi-issuance/onboarding/api-integration/device-binding)** — generate binding token, the silent SMS, and the `binding-status` poll. +- **[OTP verification](/payments/upi-issuance/onboarding/api-integration/user-otp)** — `otp/request` and `otp/verify` to activate a VPA on Android. +- **[VPA management](/payments/upi-issuance/onboarding/api-integration/vpa-management)** — check, create, fetch, list, and deregister VPAs. +- **[Programs](/payments/upi-issuance/onboarding/api-integration/programs)** — create and update program configuration. + +
+ + + Testing any of these? Every scenario is reproducible on the QA env — see + Testing on QA env. + + +## Error responses + +Every error, on every endpoint, returns the **same shape** — encrypted in the envelope like any response: + + + {`{ + "traceId": "01J...", + "code": "invalid-user-state", + "message": "A human-readable description of what went wrong." +}`} + + +- The **HTTP status is authoritative** — branch on it first (`4xx`/`5xx` = error). +- `code` is a **stable machine string** to switch on; `message` is descriptive and may change. +- `traceId` is on **every** response, success or error. Log it and quote it to Setu when raising an issue. + +A `500` with `code: internal-error` means something failed on Setu's side; retry with the same `idempotencyKey`. Each operation's page lists the specific `4xx` codes it can return. + +### Next + +- **[Device binding](/payments/upi-issuance/onboarding/api-integration/device-binding)** — generate a binding token and prove the SIM. + + diff --git a/content/payments/upi-issuance/onboarding/api-integration/device-binding.mdx b/content/payments/upi-issuance/onboarding/api-integration/device-binding.mdx new file mode 100644 index 00000000..4ab4e588 --- /dev/null +++ b/content/payments/upi-issuance/onboarding/api-integration/device-binding.mdx @@ -0,0 +1,173 @@ +--- +sidebar_title: Device binding +page_title: UPI Issuance device binding +order: 0 +visible_in_sidebar: true +--- + +## Device binding + +Device binding proves that the user's SIM, mobile number, and device belong together. It is the entry point on any device — a fresh install, a new phone, or a re-login. + +Device binding sequence: binding token, silent SMS from the SIM to the VMN, and the binding-status poll to active + +### 1. Generate a binding token + +`POST /api/v1/onboarding/binding-token` (enveloped) + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `os` | string | yes | `android` or `ios`. | +| `otpRequired` | boolean | yes | Whether a successful SIM binding fully activates the user. `false` — the user goes straight to `active`. `true` — the user stops at `device-bound` until an [OTP](/payments/upi-issuance/onboarding/api-integration/user-otp) is verified. Setu does not validate this against `os` — the flow follows whatever the TPAP / Issuing App sends. See [Choosing the value](#choosing-the-otprequired-value). | + +
+ +`idempotencyKey` is an optional request header. + +**When OTP verification happens is controlled by the TPAP / Issuing App, and Setu respects it.** There are two options: + +- To verify OTP **after SIM binding and before VPA creation**, generate the binding token with `otpRequired: true`. The user stays at `device-bound` until an OTP is verified. This is the **device change** case, where no new VPA is created. +- To verify OTP **after VPA creation**, generate the binding token with `otpRequired: false` and set `otpRequired: true` on [`create-vpa`](/payments/upi-issuance/onboarding/api-integration/vpa-management) instead. The new VPA stays `pending-verification` until an OTP is verified. This is the **new onboarding / new VPA** case. + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "os": "android", + "otpRequired": false +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vmn` | string | The Virtual Mobile Number to send the SMS to. | +| `smsBody` | string | The ready-to-send silent-SMS body. The token is embedded here and is never returned separately. | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vmn": "919900000001", + "smsBody": "VERIFY SETqxf8aemkxxdrhbifd4n4zldq7guvxb2k" +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | A required field is missing (`deviceId`, `mobile`, `os`, `otpRequired`), a field fails format validation (e.g. `mobile` is not 12 digits, `os` is not `android`/`ios`), or the request body is malformed. | +| 429 | `device-binding-cap-exceeded` | Too many binding attempts for this device today. | +| 429 | `mobile-binding-cap-exceeded` | Too many binding attempts for this mobile today. | +| 500 | `internal-error` | Something went wrong on Setu's side. Retry with the same `idempotencyKey`. | + +### 2. Send the silent SMS + +`vmn` is a **Virtual Mobile Number** — a receive-only number that the binding SMS is sent to. The TPAP / Issuing App sends `smsBody` as a silent SMS from the user's SIM to `vmn`. The telecom provider then calls a webhook to Setu, indicating the VMN that received the SMS, the mobile it was sent from, and the SMS body. Setu checks all three — the SMS body must carry a token for a live binding, the VMN must be the one that binding was issued for, and the sending mobile must match the claimed `mobile`. When all three match, the SIM is proven and the binding advances. The TPAP / Issuing App does not call anything for this step. + +The binding token is short-lived — it expires **45 seconds** after it is generated. The silent SMS (and the telecom's webhook back to Setu) must complete within that window; if it lapses, generate a fresh binding token. + + + On the QA env there is no telecom. Drive the outcome with a + sim.bind-* directive in the idempotencyKey of the + generate binding token call — see + Testing on QA env. + + +### 3. Poll the binding status + +`POST /api/v1/onboarding/binding-status` (enveloped) + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | + +
+ +`idempotencyKey` is an optional request header (opaque, up to 128 characters). + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `user` | object | The user in its current state (see [The user object](#the-user-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "deviceId": "device-abc-123", + "mobile": "919999999999", + "alias": "919999999999" + } +}`} + + +Poll until the status settles. With `otpRequired: false` a successful SIM binding lands the user on `active`, and the TPAP / Issuing App can [create a VPA](/payments/upi-issuance/onboarding/api-integration/vpa-management). With `otpRequired: true` the user settles at `device-bound` and needs an [OTP](/payments/upi-issuance/onboarding/api-integration/user-otp) to reach `active` — the OTP is valid for **60 seconds** after it is requested. + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | A required field is missing (`deviceId`, `mobile`), a field fails format validation (e.g. `mobile` is not 12 digits), or the request body is malformed. | +| 404 | `binding-not-found` | No binding for this device — polled before generating a binding token, or from a device that is not the bound one. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +### The user object + +The [binding status](#3-poll-the-binding-status) and a device-change [OTP verification](/payments/upi-issuance/onboarding/api-integration/user-otp) return the user in this shape: + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `id` | string | The user's id (ULID). | +| `status` | string | `binding-pending`, `device-bound`, `otp-pending`, or `active`. | +| `deviceId` | string | The user's device id. | +| `mobile` | string | The user's mobile number, with country code (e.g. `919999999999`). | +| `alias` | string | The prefix of the user's VPAs — they are minted as `[-suffix]@handle`. Defaults to the user's `mobile`. | + +
+ +### Choosing the otpRequired value + +`otpRequired` is a flag the TPAP / Issuing App sets, not a property Setu derives. **Setu does not validate it against `os`** — the onboarding flow follows whatever value is sent, for either OS. + +These are the values Setu expects the TPAP / Issuing App to send, per current understanding of the OS behaviour: + +| `os` | Expected `otpRequired` | Why | +| :--- | :--- | :--- | +| `android` | `true` | The OTP is needed to confirm the user is present on the device. | +| `ios` | `false` | A successful SIM binding is sufficient. | + +Sending a value that differs from the table is accepted and changes the flow accordingly — so the TPAP / Issuing App should send the value that matches the device's OS. + +
+ +### On a device change + +When the user moves to a new phone or re-installs, only device binding is repeated — the three steps above, against the new `deviceId`. A device change does not require VPA creation. The OTP is OS-driven, just as at onboarding. On **iOS** (`otpRequired: false`) a successful SIM binding moves the user to `active`. On **Android** (`otpRequired: true`) a successful SIM binding moves the user to `device-bound`, and an [OTP](/payments/upi-issuance/onboarding/api-integration/user-otp) then moves the user to `active` before the account is usable on the new device. + +### Next + +- **[OTP verification](/payments/upi-issuance/onboarding/api-integration/user-otp)** — request and verify OTPs. +- **[VPA management](/payments/upi-issuance/onboarding/api-integration/vpa-management)** — create and manage VPAs. + + diff --git a/content/payments/upi-issuance/onboarding/api-integration/programs.mdx b/content/payments/upi-issuance/onboarding/api-integration/programs.mdx new file mode 100644 index 00000000..b41ec474 --- /dev/null +++ b/content/payments/upi-issuance/onboarding/api-integration/programs.mdx @@ -0,0 +1,144 @@ +--- +sidebar_title: Programs +page_title: UPI Issuance programs +order: 3 +visible_in_sidebar: true +--- + +## Programs + +A program is an **engagement channel** through which users are onboarded onto the TPAP / Issuing App and issued a wallet — for example a distinct branded app or offering built on the stack. Each program carries its own configuration: whether wallets on it may pay and receive, and an optional per-transaction limit. A `programId` is optional on the onboarding calls that accept it. It matters most on `create-vpa`, which ties each VPA to the program it was created under so the program's rules apply to that wallet. + +Setu can provision a program, or the TPAP / Issuing App can manage its own through these endpoints. Program routes are enveloped like the rest of the onboarding surface. + +### The program object + +Both endpoints return the program under `program` in this shape: + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `id` | string | The program id (ULID); pass it as `programId` on onboarding calls. | +| `name` | string | The program's display name. | +| `code` | string | The program's short code — its channel identifier. | +| `isCreditAllowed` | boolean | May wallets on this program receive incoming UPI? | +| `isDebitAllowed` | boolean | May wallets on this program pay outgoing UPI? | +| `amountLimit` | integer | Per-transaction cap, in paise; absent when there is no cap. | +| `status` | string | `active` or `inactive`. | + +
+ +### Create a program + +`POST /api/v1/programs` (enveloped) + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `name` | string | yes | The program's display name. 1–99 characters; letters, digits, spaces and `. ' & -`. | +| `code` | string | yes | The program's short code — its channel identifier. 1–32 characters; letters, digits, `_` and `-`. | +| `isCreditAllowed` | boolean | no | May wallets on this program receive incoming UPI? Defaults `true`. | +| `isDebitAllowed` | boolean | no | May wallets on this program pay outgoing UPI? Defaults `true`. | +| `amountLimit` | integer | no | Per-transaction cap, in paise. Omit for no cap. Integer `≥ 0`. | + +##### Sample request + + + {`{ + "name": "Acme Wallet", + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "amountLimit": 2000000 +}`} + + +##### Success response — `201` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `program` | object | The created program (see [The program object](#the-program-object)). Use its `id` (passed as `programId`) in onboarding calls. | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "program": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "name": "Acme Wallet", + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "amountLimit": 2000000, + "status": "active" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `name` or `code` is missing or fails format validation, or the request body is malformed. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### Update a program + +`PATCH /api/v1/programs/{programId}` (enveloped) — `programId` is a path parameter. + +Every field below is optional and **any field that is omitted keeps its current value** — there are no defaults applied on update. Send only the fields that should change. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `name` | string | no | The program's display name. 1–99 characters; letters, digits, spaces and `. ' & -`. | +| `code` | string | no | The program's short code. 1–32 characters; letters, digits, `_` and `-`. | +| `isCreditAllowed` | boolean | no | May wallets on this program receive incoming UPI? | +| `isDebitAllowed` | boolean | no | May wallets on this program pay outgoing UPI? | +| `amountLimit` | integer | no | Per-transaction cap, in paise. Integer `≥ 0`. | +| `status` | string | no | `active` or `inactive`. An `inactive` program is rejected at the onboarding gate (`400 invalid-program`). | + +##### Sample request + +For example, deactivate a program: + + + {`{ + "status": "inactive" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `program` | object | The updated program (see [The program object](#the-program-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "program": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "name": "Acme Wallet", + "code": "ACME", + "isCreditAllowed": true, + "isDebitAllowed": true, + "amountLimit": 2000000, + "status": "inactive" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `programId` is not a valid ULID, a body field fails format validation, or the request body is malformed. | +| 404 | `program-not-found` | No program with that (well-formed) `programId`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +### Next + +- **[Device binding](/payments/upi-issuance/onboarding/api-integration/device-binding)** — generate a binding token and start onboarding a user. + + diff --git a/content/payments/upi-issuance/onboarding/api-integration/user-otp.mdx b/content/payments/upi-issuance/onboarding/api-integration/user-otp.mdx new file mode 100644 index 00000000..b4010b7f --- /dev/null +++ b/content/payments/upi-issuance/onboarding/api-integration/user-otp.mdx @@ -0,0 +1,186 @@ +--- +sidebar_title: OTP verification +page_title: UPI Issuance OTP verification +order: 1 +visible_in_sidebar: true +--- + +## OTP verification + +Onboarding involves two kinds of OTP. Both use the same `otp/request` + `otp/verify` pair, but they unlock different things and are triggered by different calls: + +| Use case | What the OTP unlocks | Triggered by | `vpa` in the request | Verify returns | +| :--- | :--- | :--- | :--- | :--- | +| **New onboarding / new VPA** | transacting on the new VPA (`pending-verification → active`) | `otpRequired: true` on `create-vpa` | yes | the now-`active` VPA | +| **Device change** | transacting from the new device (`device-bound → active`) | `otpRequired: true` on generate binding token | no | the now-`active` user | + +
+ +The OTP for a new VPA never changes the user's status. The OTP for a device change never changes a VPA's status. Whether an `otp/request` or `otp/verify` call applies to a VPA or to the user is decided by whether a `vpa` is present in the body. + +
+ +## New onboarding / new VPA + +A newly created VPA can require an OTP before it can transact. The TPAP / Issuing App controls this by setting `otpRequired` on [`create-vpa`](/payments/upi-issuance/onboarding/api-integration/vpa-management): `true` creates the VPA `pending-verification`, `false` creates it `active`. + +The expected value is **OS-driven** — `true` on **Android**, `false` on **iOS** — but Setu does not validate `otpRequired` against `os`. The TPAP / Issuing App should send the value that matches the device's OS; the flow follows whatever is sent. See [Choosing the value](/payments/upi-issuance/onboarding/api-integration/device-binding#choosing-the-otprequired-value). + +New VPA activation: on Android create-vpa creates the VPA pending-verification and an OTP promotes it to active, on iOS it is created active directly + +## Device change + +When `otpRequired: true` is sent on the [generate binding token](/payments/upi-issuance/onboarding/api-integration/device-binding) call, a successful SIM binding leaves the user at `device-bound` rather than `active`. Requesting the OTP moves the user to `otp-pending`, and a successful verification moves the user to `active`, letting them transact from the new device. This confirms the user is present on the new device. + +The expected value is **OS-driven** — `true` on **Android** (the user stops at `device-bound`) and `false` on **iOS** (a successful SIM binding activates the user directly) — but Setu does not validate `otpRequired` against `os`. + +Device change: on Android otpRequired true leaves the user device-bound and an OTP promotes them to active, on iOS the SIM binding activates the user directly + +
+ +## Request an OTP + +`POST /api/v1/onboarding/otp/request` (enveloped) — asynchronous, returns `202`. Setu sends the OTP to the user's mobile by SMS. The OTP is valid for **60 seconds** after it is requested; once it lapses, `otp/verify` returns `410 otp-expired-or-exhausted` and a fresh `otp/request` is needed. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | conditional | Include it for the **new onboarding / new VPA** case (the VPA being activated). Omit it for the **device change** case. When present: `prefix@handle`, max 255 characters — `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. | + +
+ +`idempotencyKey` is an optional request header (opaque, up to 128 characters). + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" +}`} + + +Omit `vpa` for the **device change** case. + +##### Success response — `202` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId` or `mobile` is missing or fails format validation (e.g. `mobile` not 12 digits, a malformed `vpa`), or the request body is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 404 | `vpa-not-found` | A `vpa` was sent but the user does not own a VPA with that string. | +| 409 | `invalid-user-state` | Not in a state that owes an OTP. The **new onboarding / new VPA** case needs the user `active` and the VPA `pending-verification`. The **device change** case needs the user at `device-bound` or `otp-pending`. | +| 429 | `otp-cap-exceeded` | Too many OTP requests. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +## Verify an OTP + +`POST /api/v1/onboarding/otp/verify` (enveloped) + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `otp` | string | yes | The 6-digit code the user entered — `^[0-9]{6}$`. | +| `vpa` | string | conditional | Include it for the **new onboarding / new VPA** case (the VPA being activated). Omit it for the **device change** case. When present: `prefix@handle`, max 255 characters — `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. | + +
+ +`idempotencyKey` is an optional request header. One attempt per OTP — a wrong guess consumes it, so a retry must `otp/request` again. + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "otp": "123456", + "vpa": "919999999999-acme@setu" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpa` | object | Present for the **new onboarding / new VPA** case — the now-`active` VPA (see [The VPA object](/payments/upi-issuance/onboarding/api-integration/vpa-management#the-vpa-object)). | +| `user` | object | Present for the **device change** case — the now-`active` user (see [The user object](/payments/upi-issuance/onboarding/api-integration/device-binding#the-user-object)). | + +Exactly one of `vpa` or `user` is present. + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "status": "active", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "accountType": "PPIWALLET", + "accountName": "Alice Doe", + "mobile": "919999999999" + } +}`} + + +The **device change** case returns the user instead: + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "user": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "active", + "deviceId": "device-abc-123", + "mobile": "919999999999", + "alias": "919999999999" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `otp` is missing or fails format validation (`otp` must be 6 digits, `mobile` 12 digits), or the request body is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 401 | `invalid-otp` | The OTP is incorrect. The attempt is consumed. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | Not in a state that owes an OTP. The **new onboarding / new VPA** case needs the user `active` and the VPA `pending-verification`. The **device change** case needs the user at `device-bound` or `otp-pending`. | +| 410 | `otp-expired-or-exhausted` | No live OTP to verify — never requested, expired, or already consumed. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ + + On the QA env there is no telecom, so no OTP SMS is sent. Set idempotencyKey: sim.otp-ok + on otp/verify to pass with any code — see + Testing on QA env. + + +### Next + +- **[VPA management](/payments/upi-issuance/onboarding/api-integration/vpa-management)** — create and manage VPAs. + + diff --git a/content/payments/upi-issuance/onboarding/api-integration/vpa-management.mdx b/content/payments/upi-issuance/onboarding/api-integration/vpa-management.mdx new file mode 100644 index 00000000..fb785e4a --- /dev/null +++ b/content/payments/upi-issuance/onboarding/api-integration/vpa-management.mdx @@ -0,0 +1,360 @@ +--- +sidebar_title: VPA management +page_title: UPI Issuance VPA management +order: 2 +visible_in_sidebar: true +--- + +## VPA management + +Once a user is `active`, the TPAP / Issuing App creates and manages their VPAs. All routes are enveloped and under `/api/v1`. `idempotencyKey` is an optional request header on every call. + +### The VPA object + +`create-vpa`, `get-vpa`, `list-vpas`, and `vpa/deregister` return the VPA in this shape: + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `id` | string | The VPA id (ULID). | +| `vpa` | string | The full VPA, `prefix@handle`. | +| `status` | string | `active`, `pending-verification`, or `deregistered`. | +| `programId` | string | The program the VPA belongs to (ULID). | +| `accountNo` | string | The wallet / pool account number. | +| `accountProviderId` | string | The account-provider id. | +| `ifsc` | string | The IFSC of the account. | +| `accountType` | string | The account type (e.g. `PPIWALLET`). | +| `accountName` | string | The account-holder name. | +| `mobile` | string | The owner's mobile number, with country code (e.g. `919999999999`). | + +
+ +### Check availability + +`POST /api/v1/onboarding/vpa/check` (enveloped) — is a chosen VPA free? + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | yes | The VPA to check — the full VPA `prefix@handle` (e.g. `919999999999-acme@setu`), max 255 characters, `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. Its VPA prefix must start with the user's `alias` — by default, the user's mobile number. | +| `programId` | string | no | A 26-character ULID — `^[0-9A-HJKMNP-TV-Z]{26}$`. Validated for existence when supplied. **Omit** to check without a program. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpa` | string | The full VPA that was checked (`prefix@handle`). | +| `available` | boolean | `false` means the VPA is already taken by another user's active VPA. A taken VPA is not an error. | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "available": true +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `vpa` is missing or fails format validation, or the request body is malformed. | +| 400 | `invalid-program` | A well-formed `programId` that is unknown or inactive. | +| 400 | `invalid-vpa` | The VPA prefix does not start with the user's alias (by default, their mobile number). | +| 400 | `invalid-handle` | The VPA carries a handle that is not the one configured for this TPAP. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### Create a VPA + +`POST /api/v1/onboarding/create-vpa` (enveloped) + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | yes | The VPA to create — the full VPA `prefix@handle` (e.g. `919999999999-acme@setu`), max 255 characters, `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. Its VPA prefix must start with the user's `alias` — by default, the user's mobile number. | +| `accountNo` | string | yes | The wallet / pool account number. 6–18 characters, uppercase letters and digits — `^[A-Z0-9]{6,18}$`. | +| `accountName` | string | yes | The account-holder name. 1–99 characters; letters, digits, spaces and `. ' & -`. | +| `otpRequired` | boolean | no | `true` (expected for Android) — the VPA is created `pending-verification` and an [OTP](/payments/upi-issuance/onboarding/api-integration/user-otp) activates it. `false` (expected for iOS) — the VPA is created `active`. Not validated against the device OS. **Defaults to `false`** when omitted. | +| `ifsc` | string | no | The IFSC of the account — `^[A-Z]{4}0[A-Z0-9]{6}$` (e.g. `SETU0000001`). Optional when a TPAP default is configured. | +| `accountProviderId` | string | no | The account-provider id — a 26-character ULID (`^[0-9A-HJKMNP-TV-Z]{26}$`). Optional when a TPAP default is configured. | +| `programId` | string | no | A 26-character ULID — `^[0-9A-HJKMNP-TV-Z]{26}$`. Validated for existence when supplied. **Omit** and the VPA is created without a program. | +| `defaultDebit` | boolean | no | Make this the user's default debit account. **Defaults to `false`** when omitted. | +| `defaultCredit` | boolean | no | Make this the user's default credit account. **Defaults to `false`** when omitted. | + +The account details are taken from the request. There is no bank round-trip at onboarding. + +**Re-linking an existing VPA to a different account.** Re-sending `create-vpa` for a VPA the user already holds is idempotent **only when the account details match**. If an existing active VPA is re-created with a **different account**, the call is rejected with `vpa-account-mismatch` (`409`) — deregister the VPA first, then create it against the new account. Setu never silently re-points an active VPA at a different account. This matters when a mobile number is reassigned to a new user: it is the app's / CBS's responsibility to clean up the prior user's VPAs, and this guard ensures an existing VPA cannot be quietly moved to a different account. + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "accountNo": "99887766554433", + "accountName": "Alice Doe", + "otpRequired": false +}`} + + +##### Success response — `200` + +Returns the created VPA; its `status` is `active` or `pending-verification`. + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpa` | object | The created VPA (see [The VPA object](#the-vpa-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "status": "active", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "accountType": "PPIWALLET", + "accountName": "Alice Doe", + "mobile": "919999999999" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, `vpa`, `accountNo`, or `accountName` is missing or fails format validation, or the request body is malformed. | +| 400 | `missing-parameter` | `ifsc` or `accountProviderId` is required (no TPAP default configured) and was not supplied. | +| 400 | `invalid-program` | Unknown or inactive `programId`. | +| 400 | `invalid-vpa` | The VPA prefix does not start with the user's alias (by default, their mobile number). | +| 400 | `invalid-handle` | The VPA carries a handle that is not the one configured for this TPAP. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 409 | `vpa-taken` | That VPA is already in use. | +| 409 | `vpa-account-mismatch` | This VPA is already active on a **different account**. Deregister it first to re-link it to another account. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ + + A VPA prefix is scoped to the owner's mobile, so a caller cannot collide + with another user's VPA (that request is rejected as invalid-vpa), + and re-creating the caller's own VPA is idempotent (returns 200). + + +
+ +### Get a VPA + +`POST /api/v1/onboarding/get-vpa` (enveloped) — fetch one of the user's VPAs by its VPA string. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | yes | The VPA to fetch — the full VPA `prefix@handle` (e.g. `919999999999-acme@setu`), max 255 characters, `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. Its VPA prefix must start with the user's `alias` — by default, the user's mobile number. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" +}`} + + +##### Success response — `200` + +Returns the VPA under `vpa`. + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpa` | object | The requested VPA (see [The VPA object](#the-vpa-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "status": "active", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "accountType": "PPIWALLET", + "accountName": "Alice Doe", + "mobile": "919999999999" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `vpa` is missing or fails format validation, or the request body is malformed. | +| 400 | `invalid-vpa` | The VPA prefix does not start with the user's alias (by default, their mobile number). | +| 400 | `invalid-handle` | The VPA carries a handle that is not the one configured for this TPAP. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 404 | `vpa-not-found` | The user does not own a VPA with that string. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### List VPAs + +`POST /api/v1/onboarding/list-vpas` (enveloped) — the user's live VPAs (`active` + `pending-verification`), newest-added first, keyset-paginated. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `limit` | integer | no | Max entries this page. Omitted or non-positive defaults to `20`, clamped to `100`. | +| `cursor` | string | no | The `nextCursor` from the previous page (opaque). Omit for the first page. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "limit": 20 +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpas` | array | This page of the user's live VPAs (see [The VPA object](#the-vpa-object)); empty when none. | +| `nextCursor` | string | Cursor for the next page; absent on the last page. | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpas": [ + { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "status": "active", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "accountType": "PPIWALLET", + "accountName": "Alice Doe", + "mobile": "919999999999" + } + ], + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV" +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId` or `mobile` is missing or fails format validation, the request body is malformed, or `cursor` is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### Deregister a VPA + +`POST /api/v1/onboarding/vpa/deregister` (enveloped) — soft-delete a VPA. The row is preserved for audit and re-registration, and the prefix becomes reusable. Idempotent for an already-deregistered VPA. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | yes | The VPA to deregister — the full VPA `prefix@handle` (e.g. `919999999999-acme@setu`), max 255 characters, `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. Its VPA prefix must start with the user's `alias` — by default, the user's mobile number. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "919999999999-acme@setu" +}`} + + +##### Success response — `200` + +Returns the deregistered VPA; its `status` is `deregistered`. + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `vpa` | object | The deregistered VPA (see [The VPA object](#the-vpa-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "vpa": "919999999999-acme@setu", + "status": "deregistered", + "programId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "accountNo": "99887766554433", + "accountProviderId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "ifsc": "SETU0000001", + "accountType": "PPIWALLET", + "accountName": "Alice Doe", + "mobile": "919999999999" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `vpa` is missing or fails format validation, or the request body is malformed. | +| 400 | `invalid-vpa` | The VPA prefix does not start with the user's alias (by default, their mobile number). | +| 400 | `invalid-handle` | The VPA carries a handle that is not the one configured for this TPAP. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 404 | `vpa-not-found` | The user does not own a VPA with that string. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +### Next + +- **[Programs](/payments/upi-issuance/onboarding/api-integration/programs)** — provision and manage engagement channels that VPAs are created under. + + diff --git a/content/payments/upi-issuance/onboarding/onboarding-states.mdx b/content/payments/upi-issuance/onboarding/onboarding-states.mdx new file mode 100644 index 00000000..2df25b60 --- /dev/null +++ b/content/payments/upi-issuance/onboarding/onboarding-states.mdx @@ -0,0 +1,59 @@ +--- +sidebar_title: Onboarding states +page_title: UPI Issuance onboarding states +order: 0 +visible_in_sidebar: true +--- + +## Onboarding states + +Onboarding tracks two things, both driven by the TPAP / Issuing App's API calls: + +- the **user** — is the device active / is OTP verification pending / is the user active? +- the onboarded **VPA** — is it usable? + +### User states + +| Status | Meaning | +| :--- | :--- | +| `binding-pending` | The user exists (created at token generation) but the SIM is not yet proven. | +| `device-bound` | The SIM binding succeeded, but OTP verification is still owed before the user is active. Only reached when `otpRequired: true` was sent during token generation. | +| `otp-pending` | The OTP has been sent and is awaiting verification. | +| `active` | The user is fully onboarded. Required before any VPA can be created or used. | + +### VPA states + +| Status | Meaning | +| :--- | :--- | +| `pending-verification` | The VPA is created but OTP verification is still owed before it can transact. | +| `active` | The VPA is verified and usable. | +| `deregistered` | The VPA was removed. Its prefix becomes reusable. | + +### When OTP verification happens + +OTP verification is **expected for Android** and not needed on iOS. Both **whether** and **when** it happens are controlled by the TPAP / Issuing App through the `otpRequired` flag, and Setu respects it — the flag is not validated against `os`, so the flow follows whatever the TPAP / Issuing App sends: + +- To verify OTP **after SIM binding and before VPA creation**, set `otpRequired: true` on the [generate binding token](/payments/upi-issuance/onboarding/api-integration/device-binding) call. The user stays at `device-bound` until an OTP is verified. Use case: a **device change** (no new VPA is created). +- To verify OTP **after VPA creation**, set `otpRequired: true` on [`create-vpa`](/payments/upi-issuance/onboarding/api-integration/vpa-management). The new VPA stays `pending-verification` until an OTP is verified. Use case: a **new onboarding / new VPA**. + +
+ +### The two scenarios in detail + +#### New onboarding + +OTP verification is performed after VPA creation and blocks VPA activation, preventing any transactions on the new VPA until OTP verification is done. + +New onboarding: on Android create-vpa creates the VPA pending-verification and an OTP promotes it to active, on iOS the VPA is created active directly + +#### Device change + +OTP verification is performed at the user level, right after device binding on the new device. A successful SIM binding leaves the user at `device-bound`, and OTP verification moves the user to `active`. No new VPA is created. + +Device change: on Android binding on the new device leaves the user device-bound and an OTP promotes them to active, on iOS the SIM binding activates the user directly + +### Next + +- **[API integration](/payments/upi-issuance/onboarding/api-integration)** — a step-by-step walkthrough of each operation. + + diff --git a/content/payments/upi-issuance/overview.mdx b/content/payments/upi-issuance/overview.mdx new file mode 100644 index 00000000..597a081b --- /dev/null +++ b/content/payments/upi-issuance/overview.mdx @@ -0,0 +1,39 @@ +--- +sidebar_title: Overview +page_title: UPI Issuance Overview +order: 0 +visible_in_sidebar: true +--- + +## UPI Issuance + +Setu UPI Issuance is a **PSP stack** that enables TPAPs and Issuing Apps to allow their users to make and receive UPI payments. Setu provides the PSP roles and the UPI-network integration, exposed as a set of APIs. + +The stack spans the full UPI journey, starting with onboarding. These docs cover the onboarding APIs today, and grow with the rest of the stack. + +### How to read these docs + + + + +

START HERE

+ Quickstart — the first call -> +
+
+ + +

TRY EVERY SCENARIO

+ Testing on QA env -> +
+
+
+ +- **[Quickstart](/payments/upi-issuance/quickstart)** — the base URL, the envelope public key, and how requests are authenticated. +- **[The API envelope](/payments/upi-issuance/api-envelope)** — how every request and response is encrypted. +- **[User Onboarding](/payments/upi-issuance/onboarding)** — the onboarding flow, its state machines, and a step-by-step API walkthrough. +- **[VPA resolution](/payments/upi-issuance/vpa-resolution)** — resolve a payee VPA before paying it. +- **[Payee blocklist](/payments/upi-issuance/payee-blocklist)** — block, unblock, and list blocked payee VPAs. +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every success and error scenario on the QA env. +- **[API reference](/payments/upi-issuance/api-reference)** — the full endpoint reference. + + diff --git a/content/payments/upi-issuance/payee-blocklist.mdx b/content/payments/upi-issuance/payee-blocklist.mdx new file mode 100644 index 00000000..6f5890ac --- /dev/null +++ b/content/payments/upi-issuance/payee-blocklist.mdx @@ -0,0 +1,183 @@ +--- +sidebar_title: Payee blocklist +page_title: UPI Issuance payee blocklist +order: 4 +visible_in_sidebar: true +--- + +## Payee blocklist + +Let a user block payee VPAs they never want to transact with. All routes are enveloped, under `/api/v1`, and require an `active` user. `idempotencyKey` is an optional request header on every call. + +### The blocklist entry object + +`block-vpa` and `unblock-vpa` return the entry (under `blockedPayee`) in this shape: + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `id` | string | The blocklist entry id (ULID). | +| `payeeVpa` | string | The payee VPA, `prefix@handle`. | +| `status` | string | `blocked` or `unblocked`. | +| `blockedAt` | string | When the payee was most recently blocked (RFC3339). | +| `unblockedAt` | string | When the payee was unblocked (RFC3339); absent while blocked. | + +
+ +### Block a payee + +`POST /api/v1/onboarding/block-vpa` (enveloped) — idempotent; blocking an already-blocked payee is a no-op. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `payeeVpa` | string | yes | The external payee VPA to block (`prefix@handle`; any handle). Max 255 characters — `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `blockedPayee` | object | The blocklist entry, now `blocked` (see [the entry object](#the-blocklist-entry-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "blockedPayee": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "blockedAt": "2026-07-09T12:00:00Z" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `payeeVpa` is missing or fails format validation (`payeeVpa` must be `prefix@handle`, max 255 characters), or the request body is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### Unblock a payee + +`POST /api/v1/onboarding/unblock-vpa` (enveloped) — a soft flip to `unblocked`; the entry is preserved for audit. Idempotent for an already-unblocked payee. The request shape matches [Block a payee](#block-a-payee). + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "payeeVpa": "merchant@otherbank" +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `blockedPayee` | object | The blocklist entry, now `unblocked` (see [the entry object](#the-blocklist-entry-object)). | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "blockedPayee": { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "unblocked", + "blockedAt": "2026-07-09T12:00:00Z", + "unblockedAt": "2026-07-09T12:30:00Z" + } +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `payeeVpa` is missing or fails format validation (`payeeVpa` must be `prefix@handle`, max 255 characters), or the request body is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 404 | `payee-not-blocked` | That payee VPA is not on the user's blocklist. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### List blocked payees + +`POST /api/v1/onboarding/list-blocked-vpas` (enveloped) — the user's currently-blocked payees, newest-first, keyset-paginated. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `limit` | integer | no | Max entries this page. Omitted or non-positive defaults to `20`, clamped to `100`. | +| `cursor` | string | no | The `nextCursor` from the previous page (opaque). Omit for the first page. | + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "limit": 20 +}`} + + +##### Success response — `200` + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `blockedPayees` | array | This page of currently-blocked payees (see [the entry object](#the-blocklist-entry-object)); empty when none. | +| `nextCursor` | string | Cursor for the next page; absent on the last page. | + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "blockedPayees": [ + { + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "payeeVpa": "merchant@otherbank", + "status": "blocked", + "blockedAt": "2026-07-09T12:00:00Z" + } + ], + "nextCursor": "01ARZ3NDEKTSV4RRFFQ69G5FAV" +}`} + + +##### Errors + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId` or `mobile` is missing or fails format validation, the request body is malformed, or `cursor` is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +### Next + +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every scenario. +- **[API reference](/payments/upi-issuance/api-reference)** — the full endpoint reference. + + diff --git a/content/payments/upi-issuance/qa-testing.mdx b/content/payments/upi-issuance/qa-testing.mdx new file mode 100644 index 00000000..77e18d27 --- /dev/null +++ b/content/payments/upi-issuance/qa-testing.mdx @@ -0,0 +1,131 @@ +--- +sidebar_title: Testing on QA env +page_title: UPI Issuance QA env testing +order: 6 +visible_in_sidebar: true +--- + +## Testing on QA env + +The TPAP / Issuing App can reproduce every onboarding scenario — success, and every error and precondition — against the **real QA env endpoints**. + +A few outcomes depend on an external event the TPAP / Issuing App cannot trigger on the QA env (the SIM-proof callback from the telecom, and the OTP SMS). For those, put a **directive in the `idempotencyKey`** of the initiating call. + +
+ + + Simulation directives work on the QA env only. In production a + sim.* value is treated as an ordinary idempotency key and changes + nothing. + + +
+ +### Simulated scenarios + +Only two things need a directive, because the TPAP / Issuing App cannot otherwise produce them on the QA env. + +#### Device binding + +On production, binding completes when the telecom delivers the silent SMS. On the QA env, set the `idempotencyKey` on `POST /onboarding/binding-token` to drive the outcome — no webhook, no real SMS: + +| `idempotencyKey` | Result | +| :--- | :--- | +| `sim.bind-ok` | The SIM proof succeeds and the user advances to `active`. | +| `sim.bind-fail-mobile` | The proven SIM does not match the claimed mobile. Binding does not complete. | +| `sim.bind-fail-vmn` | The callback carries a token that does not match a live binding. Binding does not complete. | + +After the call, poll `binding-status` as usual to observe the result. To test **reclaim** (the same mobile on a new device), just call `sim.bind-ok` again with a different `deviceId` — no special directive needed. + +#### OTP verification + +On the QA env there is no telecom, so no OTP SMS is sent. To pass verification, set the `idempotencyKey` on `POST /onboarding/otp/verify`: + +| `idempotencyKey` | Result | +| :--- | :--- | +| `sim.otp-ok` | The OTP verifies regardless of the code submitted, activating what it was requested for — the user, or a VPA. | + + + Use sim.otp-ok for the success path. To see a failure, + request the OTP and submit a wrong code (no directive) while it is live — that + returns 401 invalid-otp. + + +#### VPA resolution + +On production, resolving a foreign-handle payee returns whatever the payee PSP reports. On the QA env the resolved values can be pinned, so every shape — a person, a merchant, verified or not, and failures — is reproducible deterministically. Send an optional `X-Sim-Resolution` header on `POST /vpa/resolve` whose value is a JSON object of the expected values. Send it on every poll (it is read on the poll that returns `resolved`). + +Every field is optional; anything left out falls back to a default. Failures carry no payee detail, so `entityType`, the merchant fields and `verified` apply only to a success. + +| field | meaning | default | +| :--- | :--- | :--- | +| `status` | `resolved` or `failed` — the same vocabulary the response carries | `resolved` | +| `errorCode` | error code returned for a `failed` resolution | `ZH` | +| `entityType` | `PERSON` or `ENTITY` (a `merchant` block also implies `ENTITY`) | `PERSON` | +| `name` | account-holder name | `MOCK****PAYEE`, or `BRAND****X` for an `ENTITY` | +| `accountType` | e.g. `SAVINGS` / `CURRENT` | `SAVINGS`, or `CURRENT` for an `ENTITY` | +| `ifsc` | payee IFSC | `ICIC0000052` | +| `merchant.mcc` / `.brandName` / `.legalName` / `.franchiseName` / `.merchantType` / `.genre` / `.ownership` | merchant detail for an `ENTITY` payee | `5732` / `Brand-X` / `Acme Pvt Ltd` / `Brand-X Franchise` / `LARGE` / `ONLINE` / `PUBLIC` | +| `verified.name` / `.url` / `.logo` | verified/whitelisted enrichment; its presence makes the payee resolve `verified: true` | not verified | + +
+ +With **no header at all**, the outcome is driven by the payee VPA itself: an address containing `fail` or `invalid` resolves `failed` with `ZH`, one containing `merchant` or `brand` resolves as an `ENTITY`, and anything else resolves as a `PERSON` — each with the defaults above. + +A verified merchant with pinned values: + + + {`curl https:///api/v1/vpa/resolve -H 'Content-Type: application/json' -H 'X-Sim-Resolution: {"entityType":"ENTITY","name":"ACME****PVT","accountType":"CURRENT","ifsc":"HDFC0000123","merchant":{"mcc":"5411","brandName":"MyBrand","legalName":"Acme Pvt Ltd","merchantType":"SMALL"},"verified":{"name":"MyBrand","url":"https://mybrand.example"}}' -d ''`} + + +A forced failure: + + + {`X-Sim-Resolution: {"status":"failed","errorCode":"ZH"}`} + + + + X-Sim-Resolution is honoured only on the QA env. On production the + header is ignored entirely and the payee resolves for real. + + +
+ +### Natural scenarios + +Everything else is reproduced with ordinary requests. A sampling: + +| To see this | Do this | +| :--- | :--- | +| `device-binding-cap-exceeded` (429) | Generate binding tokens past the per-device daily cap. | +| `otp-cap-exceeded` (429) | Call `otp/request` past the cap. | +| `invalid-otp` (401) | `otp/request`, then `otp/verify` with a wrong code (no directive) while the OTP is live. | +| `otp-expired-or-exhausted` (410) | `otp/verify` with no live OTP (never requested, or the window passed). | +| `device-not-linked` (401) | Call a VPA operation from a device not bound to the user. | +| `user-not-found` (404) | Use a mobile that was never onboarded. | +| `invalid-user-state` (409) | Call `create-vpa` before the user is `active`. | +| `invalid-vpa` (400) | Use a prefix not scoped to the caller's mobile (incl. another user's VPA). | +| `binding-not-found` (404) | Poll `binding-status` before generating a binding token. | +| `invalid-request` (400) | Send a malformed or incomplete body. | +| An abandoned binding | Request a token and never complete it — it expires on its own. | + +
+ +### A full QA env run + +| # | Call | Key inputs | Result | +| :--- | :--- | :--- | :--- | +| 1 | `binding-token` | `idempotencyKey: sim.bind-ok`, `otpRequired: false` | User → `active` | +| 2 | `binding-status` | — | `active` | +| 3 | `vpa/check` | — | Available | +| 4 | `create-vpa` | `otpRequired: true` (the Android path) | VPA → `pending-verification` | +| 5 | `otp/request` | With the VPA | `202` accepted | +| 6 | `otp/verify` | `idempotencyKey: sim.otp-ok`, with the VPA | VPA → `active` | +| 7 | `vpa/resolve` | A payee on our handle | `resolved` with the payee details | +| 8 | `vpa/resolve` | A foreign-handle payee, `X-Sim-Resolution` pinning the values | `pending` on the first call, then `resolved` with the pinned values | + +### Next + +- **[API reference](/payments/upi-issuance/api-reference)** — the full endpoint reference. + + diff --git a/content/payments/upi-issuance/quickstart.mdx b/content/payments/upi-issuance/quickstart.mdx new file mode 100644 index 00000000..ee996251 --- /dev/null +++ b/content/payments/upi-issuance/quickstart.mdx @@ -0,0 +1,70 @@ +--- +sidebar_title: Quickstart +page_title: UPI Issuance Quickstart +order: 1 +visible_in_sidebar: true +--- + +## Quickstart + +This guide covers the one-time setup the TPAP / Issuing App needs before it can call the APIs. Once these are in place, head to [User Onboarding](/payments/upi-issuance/onboarding) for the full API journey. + +
+ +### Base URL + +- QA env — `https://upi-issuance-qa.setu.co/api/v1` + +
+ +### Keys and credentials + +Every request and response body is encrypted with the **API envelope**, and every request is signed with the TPAP / Issuing App's client secret. + +The TPAP / Issuing App reaches out to Setu, which provides three things: the **envelope public key** (to encrypt requests and decrypt responses), a **`clientId`**, and a **`clientSecret`** (to sign each request). + +The [API envelope](/payments/upi-issuance/api-envelope) page has the full wire format, the [signing steps](/payments/upi-issuance/api-envelope#signing-the-request), and a copy-paste reference implementation. + +For example, a generate binding token request and its response look like this on the wire — the encrypted body (`ct`), the request's one-time key encrypted under Setu's public key (`sk`), the IV (`iv`), the `clientId`, and the request signature (`sig`): + +##### Sample request + + + {`{ + "ct": "HBAHaU47cY2pu9+9AkzlAYKr8s4CEDhz…Bn1uIfe9ufHgu1", + "sk": "hLGbHiL+TbnjYJhCgWtZM8m/yoWb0bHO…DOENEuzVbdFrX4a70A==", + "iv": "OTLfPjWZQhMRCIoAsEBAtg==", + "clientId": "01KTVREBB5BM15AFA8SQBJGRS5", + "sig": "k7mS0mHiSx8s0cJ2c3Yb1xkZ0oJc9m8s5eJ8p8b2w4A=" +}`} + + +The response comes back encrypted under the same key — the encrypted body (`ct`) and an integrity hash (`oha`): + +##### Sample response + + + {`{ + "ct": "VbtoxGJjpmPaj40Fo4rhGCxCvN246N7Y…7UZvM24sM0+Q=", + "oha": "7c2eae6a112e43f2b99f16ee9b5a2cd910b88c5463dca683b5a5ad053dbe7c30" +}`} + + +
+ +### Authentication + +Requests are authenticated two ways, together: + +1. **IP whitelisting.** The TPAP / Issuing App shares its outbound IP address with Setu, and Setu whitelists it. Requests must be server-to-server. +2. **Client-secret signature.** Each request carries the `clientId` and a `sig` — an HMAC of the request payload keyed by the `clientSecret` — so Setu can verify the call is genuinely from the TPAP / Issuing App. See [Signing the request](/payments/upi-issuance/api-envelope#signing-the-request). Keep the `clientSecret` on the TPAP / Issuing App's backend only. + +
+ +### Next + +- **[The API envelope](/payments/upi-issuance/api-envelope)** — encrypt requests and decrypt responses. +- **[User Onboarding](/payments/upi-issuance/onboarding)** — bind a device, create a VPA, and verify OTPs. +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every scenario on the QA env. + + diff --git a/content/payments/upi-issuance/vpa-resolution.mdx b/content/payments/upi-issuance/vpa-resolution.mdx new file mode 100644 index 00000000..4e5e12c4 --- /dev/null +++ b/content/payments/upi-issuance/vpa-resolution.mdx @@ -0,0 +1,275 @@ +--- +sidebar_title: VPA resolution +page_title: UPI Issuance VPA resolution +order: 5 +visible_in_sidebar: true +--- + +## VPA resolution + +Before an `active` user pays a payee, the TPAP / Issuing App resolves the payee's VPA to confirm it is valid and to show who the payment is going to. + +### How resolution works + +Resolution is a **single API, client-polled** call. A payee VPA on Setu's own handle resolves immediately; a **foreign-handle** VPA (any other PSP, e.g. `someone@ybl`) is resolved with the UPI network, which is asynchronous — so the first call returns `pending` and the app **polls by sending the same request again** until it settles. + +The `status` field carries the outcome; the HTTP status stays `200` for all three: + +| `status` | Meaning | +| :--- | :--- | +| `resolved` | The payee VPA is valid, and the API response carries the payee VPA details. | +| `pending` | The payee VPA resolution is in progress — send the same request again to poll. | +| `failed` | The payee VPA is invalid, and the API response indicates the failure reason. | + +
+ +### Resolve a payee VPA + +`POST /api/v1/vpa/resolve` (enveloped) — requires an `active` user. + +| Field | Type | Required | Notes | +| :--- | :--- | :--- | :--- | +| `deviceId` | string | yes | The user's device id. Non-empty, up to 128 characters. | +| `mobile` | string | yes | The user's mobile number, with country code — 12 digits, `^[0-9]{12}$` (e.g. `919999999999`). | +| `vpa` | string | yes | The payee VPA to resolve (`prefix@handle`; any handle). Max 255 characters — `^([A-Za-z0-9.-]+)@([A-Za-z0-9.-]+)$`. | +| `geocode` | string | no | The device geocode as `lat,long` (e.g. `12.9716,77.5946`); forwarded with the network request. | + +
+ +`idempotencyKey` is an optional request header. + +##### Sample request + + + {`{ + "deviceId": "device-abc-123", + "mobile": "919999999999", + "vpa": "someone@ybl", + "geocode": "12.9716,77.5946" +}`} + + +##### Success response — `200` + +`status` is always present; the other fields depend on the outcome. + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `traceId` | string | Trace handle for this call (ULID). | +| `status` | string | `resolved`, `pending`, or `failed`. | +| `vpa` | string | The payee VPA that was resolved (echo of the request). | +| `name` | string | The payee's account-holder name — the name on record for a VPA on our handle, or the name as reported by the payee PSP for a foreign handle. Present in the API response in case of successful resolution. | +| `accountType` | string | The payee's account type (e.g. `SAVINGS`). Present in the API response in case of successful resolution. | +| `ifsc` | string | The payee's IFSC. Present in the API response in case of successful resolution. | +| `entityType` | string | `PERSON` for a non-merchant VPA, `ENTITY` for a merchant VPA. Present in the API response in case of successful resolution. | +| `verified` | boolean | `true` when the payee VPA is a whitelisted / verified VPA. Present only when the payee VPA is verified — omitted otherwise. | +| `verifiedName` | string | The registered display name from the verified registry. Present when the payee VPA is a whitelisted / verified VPA. | +| `verifiedLogo` | string | The registered logo url. Present when the payee VPA is a whitelisted / verified VPA. | +| `verifiedUrl` | string | The registered brand url. Present when the payee VPA is a whitelisted / verified VPA. | +| `errorCode` | string | VPA resolution failure error code (e.g. `ZH`). Present on `failed`. | +| `errorMessage` | string | Human-readable failure reason. Present on `failed`. | + +
+ +A **merchant** payee (`entityType: ENTITY`) additionally carries a merchant block. These fields are present for a resolved merchant VPA: + +| Field | Type | Notes | +| :--- | :--- | :--- | +| `genre` | string | Merchant genre: `ONLINE` or `OFFLINE`. | +| `mcc` | string | Merchant category code. | +| `merchantType` | string | `SMALL` or `LARGE`. | +| `brandName` | string | Merchant brand name. | +| `franchiseName` | string | Merchant franchise name. | +| `legalName` | string | Merchant legal name. | +| `ownership` | string | `PROPRIETARY`, `PARTNERSHIP`, `PRIVATE`, `PUBLIC`, or `OTHERS`. | + +##### Resolved (person) + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "resolved", + "vpa": "someone@ybl", + "name": "ANI********DEY", + "accountType": "SAVINGS", + "ifsc": "ICIC0000052", + "entityType": "PERSON" +}`} + + +##### Resolved (verified merchant) + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "resolved", + "vpa": "brandx@ybl", + "name": "BRAND****X", + "accountType": "CURRENT", + "ifsc": "ICIC0000052", + "entityType": "ENTITY", + "genre": "ONLINE", + "mcc": "5732", + "merchantType": "LARGE", + "brandName": "Brand-X", + "franchiseName": "Brand-X Koramangala", + "legalName": "Acme Pvt Ltd", + "ownership": "PUBLIC", + "verified": true, + "verifiedName": "Brand-X", + "verifiedLogo": "https://cdn.example/brandx.png", + "verifiedUrl": "https://brandx.example" +}`} + + +##### Pending — poll again + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "pending", + "vpa": "someone@ybl" +}`} + + +##### Failed + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "failed", + "vpa": "someone@ybl", + "errorCode": "ZH", + "errorMessage": "invalid virtual address" +}`} + + +##### Errors + +`pending` and `failed` are **outcomes**, not HTTP errors — they come back as `200`. The HTTP error statuses are: + +| Status | Code | When | +| :--- | :--- | :--- | +| 400 | `invalid-request` | `deviceId`, `mobile`, or `vpa` is missing or fails format validation (`vpa` must be `prefix@handle`, max 255 characters), or the request body is malformed. | +| 401 | `device-not-linked` | This device is not bound for this user. | +| 404 | `user-not-found` | No user for this mobile. | +| 409 | `invalid-user-state` | The user is not `active`, or has no active VPA to resolve from. | +| 500 | `internal-error` | Something went wrong on Setu's side. | + +
+ +### Resolution notification (webhook) + +Polling works, but it makes the app wait. Setu can also **push** the outcome of a foreign-handle resolution to an endpoint the TPAP / Issuing App hosts, the moment the UPI network answers — so the app can react at once instead of on its next poll. + +Polling `POST /api/v1/vpa/resolve` remains the source of truth: if a notification is missed, the next poll still returns the same result. Nothing is lost, so there is no need to build a recovery path around a webhook that did not arrive. + +#### What the TPAP / Issuing App supplies + +The notification is configured **per event**, and the TPAP / Issuing App supplies everything about it. Setu does not generate any of it. + +| What | Notes | +| :--- | :--- | +| **Target** | The absolute HTTPS URL Setu POSTs the event to, e.g. `https://tpap.example.com/webhooks/upi/vpa-resolve`. One complete URL, not a base plus a path. | +| **Public key** | An RSA public key, PEM-encoded (PKIX or PKCS#1), min 2048-bit. **The TPAP / Issuing App generates the keypair and keeps the private key** — Setu only ever holds the public half and never sees the private one. Setu encrypts every notification to this key. | +| **Signing key** | A shared secret **chosen by the TPAP / Issuing App**. Setu signs every notification with it, and the same value verifies the signature. Because the TPAP / Issuing App already holds it, no further exchange is needed. Treat it as a credential: min 32 bytes of entropy, and rotate it by sending Setu a new one. | +| **Client id** | The identifier the TPAP / Issuing App knows Setu by. It is echoed on every notification as `clientId`, so the TPAP / Issuing App can select which verification secret to use — useful once it accepts notifications from more than one source. | + +
+ + + This is the mirror image of the inbound direction. For calls to Setu, Setu issues the key material (the envelope public key and the client secret). For Setu's calls to the TPAP / Issuing App, the TPAP / Issuing App issues it. Same crypto, opposite roles — so the TPAP / Issuing App runs the code already written for the envelope, with the keys swapped. + + +
+ +Send these to the Setu point of contact for each environment. The signing key is stored encrypted and is bound to that environment, so **it cannot be copied between environments**: sandbox and production each need their own. + +#### The event + +`POST ` + +| Header | Value | +| :--- | :--- | +| `X-Setu-Event` | `vpa.resolve` | +| `Content-Type` | `application/json` | + +
+ +**The body is the `POST /api/v1/vpa/resolve` response body, field for field.** That is deliberate: the notification exists to save the wait, not to introduce a second contract. It deserialises with the **same type** as the poll response, so the code that renders a resolved payee works unchanged either way. + +Two differences from a poll response, both because a notification is only ever sent once there is something to say: + +- `status` is only ever `resolved` or `failed`. **`pending` is never pushed.** +- It is sent only for a **foreign-handle** payee. A VPA on Setu's own handle resolves synchronously on the original call, so a notification is not needed. + +##### Sample notification (decrypted payload) + + + {`{ + "traceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "status": "resolved", + "vpa": "someone@ybl", + "name": "ANI********DEY", + "accountType": "SAVINGS", + "ifsc": "ICIC0000052", + "entityType": "PERSON" +}`} + + +#### What the TPAP / Issuing App must do + +The body arrives as the **same crypto envelope** as inbound requests, with the key roles reversed: Setu encrypts to the TPAP / Issuing App's public key and signs with its signing key. + + + {`{ + "ct": "", + "sk": "", + "iv": "", + "api": "vpa.resolve", + "clientId": "", + "sig": "" +}`} + + +Validate in this order, and reject as soon as a step fails: + +1. **Check the event.** Read `X-Setu-Event`. Ignore an unhandled event name. +2. **Select the secret.** Read `clientId` from the envelope and pick the matching verification secret. Reject an unknown `clientId`. +3. **Recover the session key.** RSA-OAEP-SHA1 decrypt `sk` with the private key. It must yield exactly **32 bytes**. +4. **Decrypt.** AES-256-CBC decrypt `ct` with that key and the base64-decoded `iv` (which must be exactly **16 bytes**), then strip PKCS#7 padding. +5. **Verify the signature.** Compute HMAC-SHA256 of the **decrypted plaintext** under the signing key, base64-encode it, and compare with `sig` in **constant time**. Do not skip this when `sig` is absent — treat a missing signature as a failed verification. +6. **Parse the payload** and act on it. +7. **Acknowledge.** Respond `2xx` with an empty body. Setu does not read the response body. + + + Verify the signature over the decrypted plaintext, never over the ciphertext, and always after decrypting. This is the same rule Setu applies to inbound requests. + + +
+ +**Be idempotent on `(X-Setu-Event, traceId)`.** Delivery is at-least-once: a network failure between Setu sending and recording success causes a retry, so the same notification can arrive more than once. It always carries the same terminal answer, so de-duplicating on that pair is sufficient. + +#### How Setu handles the response + +| TPAP / Issuing App response | What Setu does | +| :--- | :--- | +| `2xx` | Delivered. Done. | +| `5xx`, `408`, `429` | Retries with exponential backoff, up to **3 attempts total** (500 ms, then 1 s). | +| Any other `4xx` | **Does not retry.** A rejected signature or a malformed payload would be rejected identically on a second attempt, so Setu stops and raises an internal alert. | +| No response / timeout | Treated as a transient failure and retried, within the same 3-attempt budget. Each attempt has a 5 s timeout. | + +
+ +If every attempt fails, Setu raises an operational alert and **the notification is not redelivered later**. This is safe precisely because polling is authoritative: the result is still available from `POST /api/v1/vpa/resolve` at any time. If the TPAP / Issuing App has been down, poll for anything missed. + + + Until a target is supplied, no notification is sent and nothing changes — polling alone is a complete integration. Add the webhook to remove the wait. + + +### Next + +- **[Payee blocklist](/payments/upi-issuance/payee-blocklist)** — block payees a user never wants to transact with. +- **[Testing on QA env](/payments/upi-issuance/qa-testing)** — reproduce every scenario. + +