Payflow API — REST API Specification¶
Document Metadata
- Title: Payflow REST API Interface & Schema Specification
- Author: Payflow Engineering (shashakchandel@gmail.com)
- Status: Approved / Living Specification
- Created Date: 2026-08-01
- Last Updated: 2026-09-30
- Authoritative Location: API_SPECIFICATION.md
- Related Documents: System Architecture | Security Architecture | Architecture Decisions (ADRs) | Phased Roadmap | Engineering Conventions
This document details the REST API endpoints, request/response models, input validation rules, and error handling behaviors for the Payflow API service.
Global Conventions¶
- API Base Prefix: All endpoints are versioned and prefixed with
/api/v1. - Content-Type: All request and response bodies use
application/json. - Monetary Currency & Precision: All monetary values are strictly denominated in Indian Rupees (INR, symbol: ₹) and encoded as exact base-10 decimals with up to 4 decimal places (e.g.
100.0000= ₹100.00). - Pagination: Default page size is 10, with a hard maximum of 100 per page (
@Min(1) @Max(100)). - Authentication: Mutation and secure history endpoints require a cryptographically signed JWT token passed via the
Authorization: Bearer <token>header. - Idempotency: All mutation write operations require a unique identifier passed in the
Idempotency-Keyheader.
Interactive OpenAPI & Swagger Documentation¶
Payflow API auto-generates live, interactive OpenAPI 3.0 documentation using Springdoc OpenAPI 3.1.1:
- Swagger UI (Interactive Playground): http://localhost:8080/swagger-ui.html
- OpenAPI 3.0 JSON Specification: http://localhost:8080/v3/api-docs
Global Error Response Model (RFC 9457 / RFC 7807)¶
When an API error occurs (validation error, resource not found, conflict, etc.), the service returns a standardized error payload in compliance with RFC 9457 (which obsoletes RFC 7807 for Problem Details for HTTP APIs):
{
"type": "https://api.payflow.com/errors/invalid-request",
"title": "Invalid Request Content",
"status": 400,
"detail": "Validation failed for request parameters.",
"instance": "/api/v1/users",
"timestamp": "2026-08-01T16:59:46Z",
"errors": {
"phoneNumber": "Phone number must be exactly 10 digits",
"balance": "Balance must be non-negative"
}
}
Endpoints¶
0. User Authentication (Login)¶
Authenticates a registered user by UPI ID and issues a cryptographically signed JWT access token.
- HTTP Method:
POST - Path:
/api/v1/auth/login - Authentication: None (Public Endpoint)
- Request Body DTO (
LoginRequest): upiId: String, required (@NotBlank), valid UPI format (@Pattern).
401 Unauthorized: Invalid UPI ID or user account does not exist.422 Unprocessable Entity: UPI ID format validation failure.
1. Register User¶
Registers a new client profile with an initial balance.
- HTTP Method:
POST - Path:
/api/v1/users - Authentication: None (Public Registration)
- Request Body DTO (
CreateUserRequest): name: String, required (@NotBlank), max 100 chars (@Size(max = 100)).upiId: String, required (@NotBlank), max 100 chars (@Size(max = 100)), valid UPI format (@Pattern(regexp = "^[a-zA-Z0-9.\\-_]{2,64}@[a-zA-Z]{2,32}$")).phoneNumber: String, required (@NotBlank), exactly 10 digits (@Pattern(regexp = "^\\d{10}$")).balance: BigDecimal, required (@NotNull), non-negative (@DecimalMin("0.0")), denominated in Indian Rupees (INR, ₹).
Headers: Location: /api/v1/users/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d
409 Conflict: User with the requested UPI ID already exists (DuplicateUpiIdException).422 Unprocessable Entity: Input validation failure, or external UPI verification rejected the UPI ID (InvalidUpiException, type:https://api.payflow.com/errors/invalid-upi-id).
2. List Users (Paginated, Admin Only)¶
Retrieves a paginated list of registered users. Requires administrative privileges (ROLE_ADMIN).
- HTTP Method:
GET - Path:
/api/v1/users - Authentication:
Authorization: Bearer <token>(RequiresROLE_ADMIN) - Query Parameters:
page: Integer, optional. Page index (0-based,@Min(0)). Default:0.size: Integer, optional. Page size (@Min(1) @Max(100)). Default:10.sortBy: String, optional. Column name to sort. Default:userId.
{
"content": [
{
"referenceId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"name": "Aarav Sharma",
"upiId": "aarav@upi",
"phoneNumber": "9876543210",
"balance": 1000.0000,
"createdAt": "2026-08-01T16:00:00Z",
"updatedAt": "2026-08-01T16:00:00Z"
}
],
"page": 0,
"size": 10,
"totalElements": 1,
"totalPages": 1,
"first": true,
"last": true
}
401 Unauthorized: Missing, expired, or invalid JWT token.403 Forbidden: Caller lacks administrative role privileges (ForbiddenOperationException, type:https://api.payflow.com/errors/forbidden-operation).422 Unprocessable Entity: Query parameter validation failure (e.g.size < 1orsize > 100).
3. Retrieve User by Reference ID¶
Fetches a single user record by their unique UUID reference ID.
- HTTP Method:
GET - Path:
/api/v1/users/{id} - Authentication:
Authorization: Bearer <token>(User can only inspect their own profile)
401 Unauthorized: Missing or invalid JWT bearer token.403 Forbidden: Authenticated user cannot view another user's profile.404 Not Found: User reference ID not found.
4. Retrieve User by UPI ID¶
Fetches a single user record by their unique UPI ID.
- HTTP Method:
GET - Path:
/api/v1/users/upi/{upiId} - Authentication:
Authorization: Bearer <token>(User can only inspect their own profile)
401 Unauthorized: Missing or invalid JWT bearer token.404 Not Found: User UPI ID not found.
5. Retrieve User Balance Ledger History¶
Retrieves paginated double-entry balance ledger audit entries for a user by UUID reference ID.
- HTTP Method:
GET - Path:
/api/v1/users/{id}/ledger - Query Parameters:
page: Integer, optional (default0), min0.size: Integer, optional (default10), min1, max100.- Authentication:
Authorization: Bearer <token>(User can only view their own ledger)
{
"content": [
{
"ledgerId": 101,
"userReferenceId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"transactionReferenceId": "f9e8d7c6-b5a4-3f2e-1d0c-9b8a7f6e5d4c",
"entryType": "DEBIT",
"amount": 100.0000,
"balanceBefore": 500.0000,
"balanceAfter": 400.0000,
"createdAt": "2026-08-09T14:00:00Z"
}
],
"page": 0,
"size": 10,
"totalElements": 1,
"totalPages": 1,
"first": true,
"last": true
}
401 Unauthorized: Missing or invalid JWT bearer token.403 Forbidden: Authenticated user not authorized to inspect this ledger.404 Not Found: User not found.
6. Filter Users by Minimum Balance (Admin Only)¶
Retrieves a list of users whose balance exceeds the specified minimum threshold. Requires administrative privileges (ROLE_ADMIN).
- HTTP Method:
GET - Path:
/api/v1/users/balance/{amount} - Authentication:
Authorization: Bearer <token>(RequiresROLE_ADMIN) - Path Variables:
amount: BigDecimal, required. Minimum balance threshold denominated in INR (₹).
400 Bad Request: Parameter type mismatch (non-numeric amount).401 Unauthorized: Missing or invalid JWT token.403 Forbidden: Caller lacks administrative role privileges.
7. Create Money Transfer¶
Executes a peer-to-peer fund transfer request with guaranteed exactly-once idempotency and sender verification.
- HTTP Method:
POST - Path:
/api/v1/transactions - Authentication:
Authorization: Bearer <token>(Authenticated JWT principal must matchsenderUpiId) - Headers:
Idempotency-Key: String / UUID, required. Must be 1-255 characters matching^[A-Za-z0-9_.:-]+$. Prevents duplicate debits and replays cached responses on retry.- Request Body DTO (
TransferMoneyRequest): senderUpiId: String, required (@NotBlank), max 100 chars (@Size(max = 100)), valid UPI format (@Pattern(...)).receiverUpiId: String, required (@NotBlank), max 100 chars (@Size(max = 100)), valid UPI format (@Pattern(...)).amount: BigDecimal, required (@NotNull), minimum ₹0.01 (@DecimalMin("0.01")), maximum ₹10,00,000 (@DecimalMax("1000000.00")), denominated in Indian Rupees (INR, ₹).note: String, optional, max 255 characters (@Size(max = 255)).
curl -X POST https://api.payflow.com/api/v1/transactions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" \
-d '{
"senderUpiId": "aarav@upi",
"receiverUpiId": "priya@upi",
"amount": 150.00,
"note": "Dinner bill split"
}'
Headers: Location: /api/v1/transactions/550e8400-e29b-41d4-a716-446655440000
400 Bad Request: MissingIdempotency-Keyheader, or key reused with a mismatched payload.401 Unauthorized: Missing or invalid JWT bearer token.403 Forbidden: Authenticated user is not authorized to transfer from requested sender UPI.409 Conflict: A request with the sameIdempotency-Keyis currently in-flight.422 Unprocessable Entity: Validation constraint failure or insufficient sender balance.429 Too Many Requests: Per-user rate limit exceeded (maximum 10 requests per second; response includesRetry-After: 1header).503 Service Unavailable: Downstream dependency circuit breaker is open (upiValidation) or external provider failure.
8. Retrieve Transaction by Reference ID¶
Fetches details of a single transaction by its unique UUID reference ID.
- HTTP Method:
GET - Path:
/api/v1/transactions/{id} - Authentication:
Authorization: Bearer <token>(Only sender or receiver participants can view)
401 Unauthorized: Missing or invalid JWT bearer token.403 Forbidden: Authenticated principal was neither the sender nor the receiver.404 Not Found: Transaction reference ID does not exist.
9. List Transactions for a User (Paginated)¶
Retrieves a paginated list of all transactions where the specified UPI ID is either the sender or receiver.
- HTTP Method:
GET - Path:
/api/v1/transactions/user/{upiId} - Authentication:
Authorization: Bearer <token> - Path Variables:
upiId: String, required. Registered UPI handle (e.g.aarav@upi). Must match authenticated principal or caller must be a participant.- Query Parameters:
page: Integer, optional. Page index (0-based,@Min(0)). Default:0.size: Integer, optional. Page size (@Min(1) @Max(100)). Default:10.sortBy: String, optional. Column name to sort. Default:createdAt.
{
"content": [
{
"transactionId": 1,
"referenceId": "550e8400-e29b-41d4-a716-446655440000",
"senderUpiId": "aarav@upi",
"receiverUpiId": "priya@upi",
"amount": 150.0000,
"status": "COMPLETED",
"type": "TRANSFER",
"note": "Dinner bill split",
"createdAt": "2026-08-01T16:05:00Z"
}
],
"page": 0,
"size": 10,
"totalElements": 1,
"totalPages": 1,
"first": true,
"last": true
}
401 Unauthorized: Missing or invalid JWT bearer token.403 Forbidden: Authenticated principal is not authorized to view transactions for this UPI ID.
10. Generate Spend Insights for Transaction¶
Analyzes transaction metadata and recipient UPI handle using Spring AI structured prompt engineering to classify the expenditure into personal finance categories and generate actionable budget insights.
- HTTP Method:
POST - Path:
/api/v1/transactions/{id}/insights - Authentication: Principal-Bound (
Bearer JWT— caller must be transaction sender or receiver) - Feature Toggle: Controlled by
payflow.ai.enabled: true(returns503 Service UnavailablewithFeature DisabledProblemDetail when disabled) - Path Parameters:
id: UUID (Transaction Reference ID)- Response Model (
SpendInsightResponse): transactionReferenceId: UUIDcategory: String (FOOD_AND_DINING,SHOPPING,TRANSPORTATION,UTILITIES,ENTERTAINMENT,HEALTHCARE,INVESTMENTS,TRANSFER)amount: BigDecimal (Transaction amount)summary: String (Contextual transaction summary)budgetingTip: String (Actionable budgeting advice)confidenceScore: Double (0.0 to 1.0)source: String (AI_MODELorRULE_BASED_FALLBACK)
{
"transactionReferenceId": "550e8400-e29b-41d4-a716-446655440000",
"category": "FOOD_AND_DINING",
"amount": 450.00,
"summary": "Dining or grocery expenditure with swiggy@upi.",
"budgetingTip": "Set a dedicated dining out ceiling to optimize discretionary spending.",
"confidenceScore": 0.94,
"source": "AI_MODEL"
}
- Downstream LLM timeouts, rate limits, or outages trip Resilience4j
aiCircuitBreaker, automatically executingruleBasedFallbackwith deterministic keyword heuristic categorization (source: "RULE_BASED_FALLBACK"), guaranteeing zero API downtime for end users.
403 Forbidden: Authenticated user is neither the sender nor receiver of this transaction.404 Not Found: Transaction reference ID does not exist.503 Service Unavailable: Gen-AI Spend Insights feature is disabled (payflow.ai.enabled: false).
Observability & Telemetry Endpoints (Actuator)¶
Payflow API exposes standard Spring Boot Actuator endpoints for container health probes, Resilience4j status, and Prometheus metrics collection:
| Endpoint | Method | Auth Required | Description |
|---|---|---|---|
/actuator/health |
GET |
No | Basic liveness status ({"status": "UP"}). Detailed status (DB, CircuitBreakers, RateLimiters) shown when authorized. |
/actuator/health/liveness |
GET |
No | Kubernetes liveness probe confirming process health. |
/actuator/health/readiness |
GET |
No | Kubernetes readiness probe verifying database and circuit breaker health. |
/actuator/info |
GET |
No | Application build and version information. |
/actuator/prometheus |
GET |
No | Prometheus format scrape output including payflow_transfers_*, hikaricp_connections, resilience4j_circuitbreaker_*, and resilience4j_ratelimiter_*. |
/actuator/metrics |
GET |
No | JSON catalog of available Micrometer metric names. |
Error Handling & RFC 7807 / RFC 9457 ProblemDetail Payloads¶
All error responses adhere to the standard RFC 7807 and RFC 9457 application/problem+json format:
{
"type": "https://api.payflow.com/errors/insufficient-balance",
"title": "Insufficient Balance",
"status": 422,
"detail": "Insufficient balance. Available: 100.0000, Required: 250.0000",
"instance": "/api/v1/transactions",
"timestamp": "2026-08-01T16:00:00Z",
"requestId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
}
Rate Limit Exceeded (429 Too Many Requests)¶
Response includes header: Retry-After: 1
{
"type": "https://api.payflow.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Too many requests. You have exceeded your rate limit of 10 requests per second. Please retry after 1 seconds.",
"instance": "/api/v1/transactions",
"timestamp": "2026-09-05T12:00:00Z",
"requestId": "b7c9d1e2-3456-789a-bcde-f0123456789a"
}
Service Unavailable / Circuit Breaker Open (503 Service Unavailable)¶
{
"type": "https://api.payflow.com/errors/service-unavailable",
"title": "Service Unavailable",
"status": 503,
"detail": "Circuit breaker 'upiValidation' is OPEN and does not permit further calls",
"instance": "/api/v1/transactions",
"timestamp": "2026-09-05T12:00:01Z",
"requestId": "c8d0e2f3-4567-89ab-cdef-0123456789ab"
}
HTTP Status Codes Reference¶
| Code | Status | Trigger Condition |
|---|---|---|
| 200 | OK |
Standard successful read or lookup. |
| 201 | Created |
Successfully registered a user or created a transaction. |
| 400 | Bad Request |
Illegal business arguments (e.g. self-transfer attempt), parameter type mismatch (MethodArgumentTypeMismatchException), or malformed/oversized Idempotency-Key header. |
| 401 | Unauthorized |
Missing, expired, or invalid JWT authentication token. |
| 403 | Forbidden |
Authenticated principal is not authorized for the requested resource (sender impersonation, cross-user ledger/transaction access, or non-admin access to bulk user directory / balance queries). |
| 404 | Not Found |
User or transaction lookup returned no matching records (UserNotFoundException). |
| 409 | Conflict |
Resource conflict (e.g. duplicate UPI ID registration, in-flight idempotency conflict, or constraint violation). |
| 422 | Unprocessable Entity |
Jakarta validation constraint violation, handler method parameter validation failure (HandlerMethodValidationException), or insufficient account balance (InsufficientBalanceException). |
| 429 | Too Many Requests |
Dynamic per-user rate limit exceeded (RFC 6585 with Retry-After: 1 header). |
| 500 | Internal Error |
Unexpected server error (sanitized, stack traces suppressed). |
| 503 | Service Unavailable |
Downstream service circuit breaker is OPEN (CallNotPermittedException). |