Skip to content

Payflow API — REST API Specification

Document Metadata

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-Key header.

Interactive OpenAPI & Swagger Documentation

Payflow API auto-generates live, interactive OpenAPI 3.0 documentation using Springdoc OpenAPI 3.1.1:


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).
curl -X POST https://api.payflow.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "upiId": "alice@payflow"
  }'
{
  "upiId": "alice@payflow"
}
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "upiId": "alice@payflow",
  "referenceId": "550e8400-e29b-41d4-a716-446655440000"
}
  • 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, ₹).
curl -X POST https://api.payflow.com/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Aarav Sharma",
    "upiId": "aarav@upi",
    "phoneNumber": "9876543210",
    "balance": 1000.00
  }'
{
  "name": "Aarav Sharma",
  "upiId": "aarav@upi",
  "phoneNumber": "9876543210",
  "balance": 1000.00
}

Headers: Location: /api/v1/users/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d

{
  "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"
}

  • 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> (Requires ROLE_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.
curl -X GET "https://api.payflow.com/api/v1/users?page=0&size=10&sortBy=userId" \
  -H "Authorization: Bearer <admin-token>"
{
  "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 < 1 or size > 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)
curl -X GET https://api.payflow.com/api/v1/users/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer <token>"
{
  "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"
}
  • 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)
curl -X GET https://api.payflow.com/api/v1/users/upi/priya@upi \
  -H "Authorization: Bearer <token>"
{
  "userId": 2,
  "name": "Priya Patel",
  "upiId": "priya@upi",
  "phoneNumber": "9876543211",
  "balance": 50.0000,
  "version": 0,
  "createdAt": "2026-08-01T16:00:00Z",
  "updatedAt": "2026-08-01T16:00:00Z"
}
  • 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 (default 0), min 0.
  • size: Integer, optional (default 10), min 1, max 100.
  • Authentication: Authorization: Bearer <token> (User can only view their own ledger)
curl -X GET "https://api.payflow.com/api/v1/users/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d/ledger?page=0&size=10" \
  -H "Authorization: Bearer <token>"
{
  "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> (Requires ROLE_ADMIN)
  • Path Variables:
  • amount: BigDecimal, required. Minimum balance threshold denominated in INR (₹).
curl -X GET https://api.payflow.com/api/v1/users/balance/500.00 \
  -H "Authorization: Bearer <admin-token>"
[
  {
    "referenceId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
    "name": "Aarav Sharma",
    "upiId": "aarav@payflow",
    "phoneNumber": "9876543210",
    "balance": 1000.0000,
    "createdAt": "2026-08-01T16:00:00Z",
    "updatedAt": "2026-08-01T16:00:00Z"
  }
]
  • 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 match senderUpiId)
  • 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"
  }'
POST /api/v1/transactions HTTP/1.1
Host: api.payflow.com
Authorization: Bearer <token>
Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
Content-Type: application/json

{
  "senderUpiId": "aarav@upi",
  "receiverUpiId": "priya@upi",
  "amount": 150.00,
  "note": "Dinner bill split"
}

Headers: Location: /api/v1/transactions/550e8400-e29b-41d4-a716-446655440000

{
  "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"
}

  • 400 Bad Request: Missing Idempotency-Key header, 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 same Idempotency-Key is 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 includes Retry-After: 1 header).
  • 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)
curl -X GET https://api.payflow.com/api/v1/transactions/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer <token>"
{
  "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"
}
  • 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.
curl -X GET "https://api.payflow.com/api/v1/transactions/user/aarav@upi?page=0&size=10" \
  -H "Authorization: Bearer <token>"
{
  "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 (returns 503 Service Unavailable with Feature Disabled ProblemDetail when disabled)
  • Path Parameters:
  • id: UUID (Transaction Reference ID)
  • Response Model (SpendInsightResponse):
  • transactionReferenceId: UUID
  • category: 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_MODEL or RULE_BASED_FALLBACK)
curl -X POST https://api.payflow.com/api/v1/transactions/550e8400-e29b-41d4-a716-446655440000/insights \
  -H "Authorization: Bearer <token>"
{
  "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 executing ruleBasedFallback with 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).