openapi: 3.0.3 info: title: Banking Demo API version: 1.0.0 description: | Minimal spec matching the current Express+SQLite implementation with request/response validation. ⚠️ Demo only: `/cards` returns full PAN + CVV as stored. servers: - url: http://localhost:3001 components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT responses: Error400: description: Bad Request (validation error) content: application/json: schema: $ref: "#/components/schemas/Error" Error401: description: Unauthorized / missing or invalid credentials content: application/json: schema: $ref: "#/components/schemas/Error" Error403: description: Forbidden content: application/json: schema: $ref: "#/components/schemas/Error" Error500: description: Internal Server Error content: application/json: schema: $ref: "#/components/schemas/Error" schemas: LoginRequest: type: object required: [username, password] properties: username: type: string example: test@test.test password: type: string example: password@123 TokenPair: type: object required: [accessToken, refreshToken, expires] properties: accessToken: type: string description: Access JWT (short-lived). refreshToken: type: string description: Refresh JWT (longer-lived). expires: type: string format: date-time description: Access token expiry timestamp (ISO 8601). Present on /login, omitted on /refresh-token. example: "2024-06-01T10:05:00Z" RefreshRequest: type: object required: [refreshToken] properties: refreshToken: type: string Account: type: object required: [id, user_id, name, iban, balance] properties: id: { type: integer } user_id: { type: integer } iban: { type: string } name: { type: string } balance: type: number format: float description: Calculated from transactions; 0 if none. example: id: 1 user_id: 1 name: Checking balance: 1680.16 Card: type: object required: [id, user_id, number, expiry, cvv] properties: id: { type: integer } user_id: { type: integer } number: type: string description: Full PAN (as stored). Returned as-is by the current API. example: "4111111111111111" expiry: { type: string, example: "12/26" } cvv: { type: string, example: "123" } PaginationMeta: type: object required: [page, limit, total, hasMore] properties: page: type: integer description: Current page number (1-based) limit: type: integer description: Page size hasMore: type: boolean description: True if there are more pages after the current one total: type: integer description: Total number of matching items PaginatedTransactions: type: object required: [data, meta] properties: data: type: array items: $ref: "#/components/schemas/Transaction" meta: $ref: "#/components/schemas/PaginationMeta" TransactionType: type: object required: [name, count] properties: name: { type: string } count: { type: integer } Transaction: type: object required: [id, userId, accountId, amount, type, description, date] properties: id: { type: integer } userId: { type: integer } accountId: { type: integer } amount: type: number format: float description: Positive for credits, negative for debits. type: type: string description: type: string date: type: string format: date-time description: ISO 8601 timestamp. example: "2024-06-01T10:00:00Z" User: type: object required: [id, username, fullname, created] properties: id: type: integer description: Unique user ID example: 2 username: type: string description: login/username of the user example: nmokkenstorm fullname: type: string description: government name of the user example: Niels Mokkenstorm created: type: string format: date-time description: ISO 8601 timestamp. example: "2024-06-01T10:00:00Z" Error: type: object properties: message: type: string LoginError: type: object description: Standard validation/error envelope. required: [errors] properties: errors: type: array description: Non-field/global errors. items: type: string properties: type: object description: Per-field validation errors. properties: username: $ref: "#/components/schemas/FieldError" password: $ref: "#/components/schemas/FieldError" example: errors: [] properties: username: errors: ["Invalid email address"] password: errors: ["Too small: expected string to have >=8 characters"] FieldError: type: object required: [errors] properties: errors: type: array items: type: string tags: - name: Meta - name: Auth - name: Accounts - name: Cards - name: Transactions paths: /: get: summary: Swagger Documentation description: Serves the Swagger UI for this API. tags: [Meta] responses: "200": description: Documentation page content: text/html: schema: type: string /openapi.yaml: get: summary: OpenAPI Spec description: Serves the OpenAPI YAML document used by the validator. tags: [Meta] responses: "200": description: OpenAPI spec (YAML) content: application/yaml: schema: type: string /me: get: summary: Retrieves information about the authenticated user tags: [Auth] security: - BearerAuth: [] responses: "200": description: User object content: application/json: schema: $ref: "#/components/schemas/User" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500" /login: post: summary: Login with username and password tags: [Auth] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginRequest" responses: "200": description: Token pair issued content: application/json: schema: $ref: "#/components/schemas/TokenPair" "400": $ref: "#/components/responses/Error400" "401": description: Invalid credentials content: application/json: schema: $ref: "#/components/schemas/LoginError" "500": $ref: "#/components/responses/Error500" /refresh-token: post: summary: Exchange refresh token for new access & refresh tokens tags: [Auth] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RefreshRequest" responses: "200": description: New token pair content: application/json: schema: $ref: "#/components/schemas/TokenPair" "400": $ref: "#/components/responses/Error400" "401": $ref: "#/components/responses/Error401" "500": $ref: "#/components/responses/Error500" /accounts: get: summary: List accounts for the authenticated user, including calculated balances tags: [Accounts] security: - BearerAuth: [] responses: "200": description: Array of accounts content: application/json: schema: type: array items: $ref: "#/components/schemas/Account" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500" /accounts/{accountId}: get: summary: Retrieves a single account for the authenticated user, including calculated balances parameters: - in: path name: accountId schema: type: integer required: true description: Numeric ID of the account to get tags: [Accounts] security: - BearerAuth: [] responses: "200": description: Account object content: application/json: schema: $ref: "#/components/schemas/Account" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500" /cards: get: summary: List stored cards for the authenticated user description: Returns full PAN and CVV as currently implemented. **Do not use in production.** tags: [Cards] security: - BearerAuth: [] responses: "200": description: Array of cards content: application/json: schema: type: array items: $ref: "#/components/schemas/Card" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500" /transaction-types: get: summary: Find transaction types for the authenticated user tags: [Transactions] security: - BearerAuth: [] parameters: - in: query name: accountId schema: type: integer description: Filter by account ID responses: "200": description: Array of transaction types content: application/json: schema: type: array items: $ref: "#/components/schemas/TransactionType" "400": $ref: "#/components/responses/Error400" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500" /transactions: get: summary: Search/sort/paginate transactions for the authenticated user tags: [Transactions] security: - BearerAuth: [] parameters: - in: query name: search schema: type: string description: Case-insensitive LIKE search against description and type. example: Shopping - in: query name: sort schema: type: string default: date description: Column to sort by (free-form; current API does not enforce an allowlist). example: date - in: query name: order schema: type: string default: desc enum: [asc, desc] - in: query name: page schema: type: integer default: 1 minimum: 1 - in: query name: limit schema: type: integer default: 25 description: Page size - in: query name: accountId schema: type: integer description: Filter by account ID - in: query name: type schema: type: string description: Filter by transaction type (withdrawal, deposit, etc.) responses: "200": description: Array of transactions (page slice) content: application/json: schema: $ref: "#/components/schemas/PaginatedTransactions" "400": $ref: "#/components/responses/Error400" "401": $ref: "#/components/responses/Error401" "403": $ref: "#/components/responses/Error403" "500": $ref: "#/components/responses/Error500"