🔴 Live Interactive Docs (Swagger): https://vaultpay-uvf2.onrender.com/docs
All endpoints return a StandardResponse[T] envelope:
{
"success": true,
"message": "Wallet retrieved successfully",
"data": { ... },
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}On error:
{
"success": false,
"message": "Wallet not found",
"error_code": "WALLET_NOT_FOUND",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}All authenticated endpoints require:
Authorization: Bearer <jwt_token>
Create a wallet for the authenticated user.
Auth: user+
Request:
{ "currency": "INR" }Response 201: Wallet object
Errors: WALLET_ALREADY_EXISTS (409)
Get the authenticated user's wallet.
Auth: user+
Response 200:
{
"wallet_id": "VPY-A1B2C3",
"balance": "1250.00",
"currency": "INR",
"status": "active",
"kyc_verified": true,
"created_at": "2024-01-15T10:30:00Z"
}Errors: WALLET_NOT_FOUND (404)
Look up any wallet by its public wallet ID (e.g. for P2P recipient lookup).
Auth: user+
Response 200: Public wallet info (no balance)
Errors: WALLET_NOT_FOUND (404)
Get the authenticated user's current balance.
Auth: user+
Response 200: { "balance": "1250.00", "currency": "INR" }
Freeze own wallet (self-service).
Auth: user+
Response 200: Updated wallet object
Errors: WALLET_ALREADY_FROZEN (409), WALLET_ALREADY_CLOSED (409)
Unfreeze own wallet (self-service).
Auth: user+
Response 200: Updated wallet object
Errors: WALLET_NOT_FROZEN (409)
Close the authenticated user's wallet (soft delete, sets status to closed).
Auth: user+
Constraints: Balance must be 0 before closing.
Response 200: Confirmation message
Errors: WALLET_HAS_BALANCE (409), WALLET_ALREADY_CLOSED (409)
Top up wallet with external funds.
Auth: user+ + KYC required
Request:
{
"amount": "500.00",
"payment_method": "upi",
"metadata": { "upi_ref": "UPI123456" }
}Response 201: Transaction object
Errors: KYC_NOT_VERIFIED (403), WALLET_FROZEN (403), DAILY_LIMIT_EXCEEDED (429), PER_TRANSACTION_LIMIT_EXCEEDED (429)
Send money to another VaultPay wallet.
Auth: user+ + KYC required + PIN required
Request:
{
"recipient_wallet_id": "VPY-X9Y8Z7",
"amount": "200.00",
"description": "Lunch split",
"pin": "1234"
}Response 201: Transaction objects for both sender (debit) and receiver (credit)
Errors: INVALID_PIN (401), INSUFFICIENT_BALANCE (402), WALLET_FROZEN (403), RECIPIENT_WALLET_NOT_FOUND (404), DAILY_LIMIT_EXCEEDED (429)
Withdraw funds to an external account.
Auth: user+ + KYC required + PIN required
Request:
{
"amount": "1000.00",
"bank_account": "XXXXXXXXXXXX",
"ifsc": "SBIN0001234",
"pin": "1234"
}Response 201: Transaction object
Errors: INSUFFICIENT_BALANCE (402), INVALID_PIN (401), DAILY_LIMIT_EXCEEDED (429)
Paginated list of the authenticated user's transactions.
Auth: user+
Query params: page, per_page, type (credit|debit), category, start_date, end_date
Response 200: PaginatedResponse[Transaction]
Get a specific transaction by UUID.
Auth: user+ (own transactions only)
Response 200: Transaction object
Errors: TRANSACTION_NOT_FOUND (404), INSUFFICIENT_PERMISSIONS (403)
Look up a transaction by its human-readable reference (e.g. VP-TXN-A1B2C3).
Auth: user+
Response 200: Transaction object
Get the authenticated user's transaction limits.
Auth: user+
Response 200: Array of limit objects per action type
Set a custom limit for a specific action.
Auth: user+
Request:
{
"action": "send_money",
"daily_limit": "5000.00",
"per_transaction_limit": "2000.00"
}Response 201: Created limit object
Errors: LIMIT_ALREADY_EXISTS (409) — use PATCH to update
Update an existing limit.
Auth: user+
Request: (partial update — any of the limit fields)
Response 200: Updated limit object
Remove a custom limit (reverts to system defaults).
Auth: user+
Response 200: Confirmation message
Set PIN for the first time.
Auth: user+
Request: { "pin": "1234", "confirm_pin": "1234" }
Response 201: Success
Errors: PIN_ALREADY_SET (409) — use /pin/change
Verify PIN (used before sensitive operations).
Auth: user+
Request: { "pin": "1234" }
Response 200: { "valid": true }
Errors: INVALID_PIN (401), PIN_LOCKED (423) — after 5 failed attempts
Change existing PIN.
Auth: user+
Request: { "current_pin": "1234", "new_pin": "5678", "confirm_new_pin": "5678" }
Response 200: Success
Errors: INVALID_PIN (401)
Reset PIN via OTP verification (no current PIN needed).
Auth: user+
Request: { "otp": "123456", "new_pin": "5678", "confirm_new_pin": "5678" }
Response 200: Success
Submit KYC document for verification.
Auth: user+
Request:
{
"doc_type": "aadhar",
"doc_number": "1234 5678 9012"
}doc_number is encrypted server-side before storage.
Response 201: KYC submission object
Errors: KYC_ALREADY_SUBMITTED (409), KYC_ALREADY_VERIFIED (409)
Check the authenticated user's KYC verification status.
Auth: user+
Response 200: { "status": "pending|verified|rejected", "rejection_reason": null }
Raise a dispute against a transaction.
Auth: user+
Request:
{
"transaction_id": "uuid-here",
"reason": "Did not authorize this transaction"
}Response 201: Dispute object
Errors: TRANSACTION_NOT_FOUND (404), DISPUTE_ALREADY_EXISTS (409)
List the authenticated user's disputes.
Auth: user+
Response 200: PaginatedResponse[Dispute]
Get a specific dispute.
Auth: user+ (own disputes only)
Response 200: Dispute object
Get all notifications for the authenticated user.
Auth: user+
Query params: page, per_page, unread_only (bool)
Response 200: PaginatedResponse[Notification]
Mark a notification as read.
Auth: user+
Response 200: Updated notification
Mark all unread notifications as read.
Auth: user+
Response 200: Count of notifications marked
Fast endpoint returning the unread notification count.
Auth: user+
Response 200: { "count": 3 }
All admin endpoints require moderator, admin, or super_admin role as specified.
List all wallets with filters.
Auth: moderator+
Query params: page, per_page, status, kyc_verified, user_id
Response 200: PaginatedResponse[Wallet]
Get full wallet details including balance history.
Auth: moderator+
Response 200: Extended wallet object
Freeze any wallet.
Auth: moderator+
Request: { "reason": "Suspicious activity detected" }
Response 200: Updated wallet + audit log entry created
Unfreeze any wallet.
Auth: moderator+
Response 200: Updated wallet + audit log entry created
List all transactions across all wallets.
Auth: moderator+
Query params: page, per_page, type, category, status, wallet_id, start_date, end_date
Response 200: PaginatedResponse[Transaction]
List all KYC submissions with filters.
Auth: moderator+
Query params: page, per_page, status
Response 200: PaginatedResponse[KYCSubmission]
Get a specific KYC submission. Decrypts doc_number in response.
Auth: moderator+
Response 200: Full KYC submission with decrypted document number
Approve a KYC submission. Sets wallet's kyc_verified = true.
Auth: moderator+
Response 200: Updated submission + notification sent to user
Reject a KYC submission with a reason.
Auth: moderator+
Request: { "rejection_reason": "Document expired" }
Response 200: Updated submission + notification sent to user
List all disputes.
Auth: moderator+
Response 200: PaginatedResponse[Dispute]
Resolve a dispute.
Auth: moderator+
Request: { "resolution_notes": "Transaction confirmed as authorized. Dispute rejected." }
Response 200: Updated dispute
View the immutable audit log.
Auth: admin+
Query params: page, per_page, actor_id, target_type, action, start_date, end_date
Response 200: PaginatedResponse[AuditLog]
View all custom transaction limits across all users.
Auth: admin+
Response 200: PaginatedResponse[TransactionLimit]
Override transaction limits for a specific wallet.
Auth: admin+
Response 201: Created limit object
View all system settings.
Auth: super_admin
Response 200: Array of system settings
Update a system setting.
Auth: super_admin
Request: { "value": "10000.00" }
Response 200: Updated setting
Deactivate a user account by calling AuthShield's admin API.
Auth: admin+
Request: { "reason": "Policy violation" }
Response 200: Confirmation (AuthShield handles the actual deactivation)
High-level system statistics.
Auth: super_admin
Response 200:
{
"total_wallets": 1500,
"active_wallets": 1432,
"total_transaction_volume": "45230000.00",
"pending_kyc_count": 23,
"open_disputes": 7
}| Code | HTTP Status | Description |
|---|---|---|
WALLET_NOT_FOUND |
404 | No wallet exists for this user/ID |
WALLET_ALREADY_EXISTS |
409 | User already has a wallet |
WALLET_FROZEN |
403 | Wallet is frozen — operation blocked |
WALLET_ALREADY_FROZEN |
409 | Wallet is already frozen |
WALLET_NOT_FROZEN |
409 | Cannot unfreeze a non-frozen wallet |
WALLET_ALREADY_CLOSED |
409 | Wallet has been closed |
WALLET_HAS_BALANCE |
409 | Cannot close wallet with remaining balance |
INSUFFICIENT_BALANCE |
402 | Balance too low for this transaction |
KYC_NOT_VERIFIED |
403 | Operation requires verified KYC |
KYC_ALREADY_SUBMITTED |
409 | KYC submission already exists |
KYC_ALREADY_VERIFIED |
409 | KYC already verified |
INVALID_PIN |
401 | Incorrect PIN |
PIN_ALREADY_SET |
409 | Use /pin/change to update |
PIN_NOT_SET |
400 | PIN must be set before this operation |
PIN_LOCKED |
423 | Too many failed PIN attempts |
DAILY_LIMIT_EXCEEDED |
429 | Transaction exceeds daily limit |
PER_TRANSACTION_LIMIT_EXCEEDED |
429 | Transaction exceeds per-transaction limit |
DISPUTE_ALREADY_EXISTS |
409 | Dispute already filed for this transaction |
TOKEN_EXPIRED |
401 | JWT has expired |
TOKEN_INVALID |
401 | JWT is malformed or tampered |
ACCOUNT_DISABLED |
403 | User account is deactivated |
INSUFFICIENT_PERMISSIONS |
403 | Role not authorized for this operation |
AUTHSHIELD_UNAVAILABLE |
503 | AuthShield service not responding |