{
  "swagger": "2.0",
  "info": {
    "title": "GoBank API",
    "description": "A production-grade banking API built with Go, gRPC, and gRPC-Gateway.\n\nSupports user registration, authentication with access/refresh tokens (PASETO), multi-currency accounts, and atomic money transfers with deadlock-safe transactions.",
    "version": "1.0.0",
    "contact": {
      "name": "Ahmed Alyapany",
      "url": "https://github.com/a7medalyapany/GoBank",
      "email": "ahmedalyapany1@gmail.com"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "tags": [
    {
      "name": "GoBank"
    }
  ],
  "host": "localhost:8080",
  "basePath": "/",
  "schemes": [
    "http",
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/v1/accounts": {
      "get": {
        "summary": "List accounts",
        "description": "Returns a paginated list of all accounts owned by the authenticated user.",
        "operationId": "ListAccounts",
        "responses": {
          "200": {
            "description": "Paginated list of accounts.",
            "schema": {
              "$ref": "#/definitions/pbListAccountsResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Not Found — the requested resource does not exist.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "pageId",
            "description": "1-based page number.",
            "in": "query",
            "required": false,
            "type": "integer",
            "format": "int32"
          },
          {
            "name": "pageSize",
            "description": "Number of accounts per page. Max 100.",
            "in": "query",
            "required": false,
            "type": "integer",
            "format": "int32"
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create an account",
        "description": "Creates a new currency account for the authenticated user. Each user may hold at most one account per currency.",
        "operationId": "CreateAccount",
        "responses": {
          "200": {
            "description": "Account created successfully.",
            "schema": {
              "$ref": "#/definitions/pbCreateAccountResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Account for this currency already exists.",
            "schema": {}
          },
          "404": {
            "description": "Not Found — the requested resource does not exist.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbCreateAccountRequest"
            }
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/accounts/lookup": {
      "get": {
        "summary": "Look up an account",
        "description": "Retrieves a single account by ID.",
        "operationId": "LookUpAccount",
        "responses": {
          "200": {
            "description": "Account retrieved successfully.",
            "schema": {
              "$ref": "#/definitions/pbLookUpAccountResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Account not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "description": "ID of the account to lookup.",
            "in": "query",
            "required": false,
            "type": "string",
            "format": "int64"
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/accounts/{id}": {
      "get": {
        "summary": "Get an account",
        "description": "Retrieves a single account by ID. The account must belong to the authenticated user.",
        "operationId": "GetAccount",
        "responses": {
          "200": {
            "description": "Account retrieved successfully.",
            "schema": {
              "$ref": "#/definitions/pbGetAccountResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Account belongs to a different user.",
            "schema": {}
          },
          "404": {
            "description": "Account not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "description": "ID of the account to retrieve. Must belong to the authenticated user.",
            "in": "path",
            "required": true,
            "type": "string",
            "format": "int64"
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete an account",
        "description": "Permanently deletes an account. The account must belong to the authenticated user.",
        "operationId": "DeleteAccount",
        "responses": {
          "200": {
            "description": "Account deleted.",
            "schema": {
              "$ref": "#/definitions/pbDeleteAccountResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Account belongs to a different user.",
            "schema": {}
          },
          "404": {
            "description": "Account not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "description": "ID of the account to delete. Must belong to the authenticated user.",
            "in": "path",
            "required": true,
            "type": "string",
            "format": "int64"
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      },
      "put": {
        "summary": "Update account balance",
        "description": "Sets the balance of an account. The account must belong to the authenticated user. Balance must be \u003e= 0.",
        "operationId": "UpdateAccount",
        "responses": {
          "200": {
            "description": "Account updated successfully.",
            "schema": {
              "$ref": "#/definitions/pbUpdateAccountResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Account belongs to a different user.",
            "schema": {}
          },
          "404": {
            "description": "Account not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "description": "ID of the account to update. Must belong to the authenticated user.",
            "in": "path",
            "required": true,
            "type": "string",
            "format": "int64"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/GoBankUpdateAccountBody"
            }
          }
        ],
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/auth/login": {
      "post": {
        "summary": "Login and obtain tokens",
        "description": "Authenticates credentials and returns a PASETO access token and a refresh token.",
        "operationId": "LoginUser",
        "responses": {
          "200": {
            "description": "Authentication successful.",
            "schema": {
              "$ref": "#/definitions/pbLoginUserResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Invalid password.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "User not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbLoginUserRequest"
            }
          }
        ],
        "tags": [
          "Auth"
        ],
        "security": []
      }
    },
    "/v1/auth/renew_access": {
      "post": {
        "summary": "Renew access token",
        "description": "Issues a new short-lived access token using a valid refresh token. Validated against the session store — blocked or mismatched sessions are rejected.",
        "operationId": "RenewAccessToken",
        "responses": {
          "200": {
            "description": "New access token issued.",
            "schema": {
              "$ref": "#/definitions/pbRenewAccessTokenResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Refresh token invalid, expired, or session blocked.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Session not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbRenewAccessTokenRequest"
            }
          }
        ],
        "tags": [
          "Auth"
        ],
        "security": []
      }
    },
    "/v1/entries": {
      "get": {
        "summary": "List activity entries",
        "description": "Returns a paginated list of activity entries across all accounts owned by the authenticated user, ordered by most recent first.",
        "operationId": "ListEntries",
        "responses": {
          "200": {
            "description": "Paginated list of authenticated user activity entries.",
            "schema": {
              "$ref": "#/definitions/pbListEntriesResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Not Found — the requested resource does not exist.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "pageId",
            "description": "1-based page number.",
            "in": "query",
            "required": false,
            "type": "integer",
            "format": "int32"
          },
          {
            "name": "pageSize",
            "description": "Number of entries per page. Max 50.",
            "in": "query",
            "required": false,
            "type": "integer",
            "format": "int32"
          }
        ],
        "tags": [
          "Entries"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/transfers": {
      "post": {
        "summary": "Create a transfer",
        "description": "Atomically transfers funds between two accounts. The source account must belong to the authenticated user. Both accounts must hold the specified currency. Uses deadlock-safe transaction ordering.",
        "operationId": "CreateTransfer",
        "responses": {
          "200": {
            "description": "Transfer completed. Returns transfer record, entries, and updated account snapshots.",
            "schema": {
              "$ref": "#/definitions/pbCreateTransferResponse"
            }
          },
          "400": {
            "description": "Insufficient balance or currency mismatch.",
            "schema": {}
          },
          "401": {
            "description": "Source account does not belong to the authenticated user.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Source or destination account not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbCreateTransferRequest"
            }
          }
        ],
        "tags": [
          "Transfers"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/users": {
      "post": {
        "summary": "Register a new user",
        "description": "Creates a new user account. Username and email must be unique. Password is bcrypt-hashed before storage.",
        "operationId": "CreateUser",
        "responses": {
          "200": {
            "description": "User created successfully.",
            "schema": {
              "$ref": "#/definitions/pbCreateUserResponse"
            }
          },
          "400": {
            "description": "Bad Request — invalid input or missing required fields.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Username or email already exists.",
            "schema": {}
          },
          "404": {
            "description": "Not Found — the requested resource does not exist.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbCreateUserRequest"
            }
          }
        ],
        "tags": [
          "Auth"
        ],
        "security": []
      },
      "patch": {
        "summary": "Update current user",
        "description": "Updates the authenticated user's profile. All fields are optional — only provided fields are updated. Password is bcrypt-hashed before storage.",
        "operationId": "UpdateUser",
        "responses": {
          "200": {
            "description": "User updated successfully.",
            "schema": {
              "$ref": "#/definitions/pbUpdateUserResponse"
            }
          },
          "400": {
            "description": "Invalid request parameters — validation failed.",
            "schema": {}
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Not authorized to update this user.",
            "schema": {}
          },
          "404": {
            "description": "User not found.",
            "schema": {}
          },
          "409": {
            "description": "Email already in use by another account.",
            "schema": {}
          },
          "500": {
            "description": "Internal server error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/pbUpdateUserRequest"
            }
          }
        ],
        "tags": [
          "Users"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/v1/verify_email": {
      "get": {
        "summary": "Verify email address",
        "description": "Verifies a user's email address using the id and secret_code from the verification link. The link expires after 15 minutes and can only be used once.",
        "operationId": "VerifyEmail",
        "responses": {
          "200": {
            "description": "Email verified successfully.",
            "schema": {
              "$ref": "#/definitions/pbVerifyEmailResponse"
            }
          },
          "400": {
            "description": "Invalid or expired verification link.",
            "schema": {}
          },
          "401": {
            "description": "Unauthorized — missing or invalid Bearer token.",
            "schema": {}
          },
          "403": {
            "description": "Forbidden — authenticated but not allowed to access this resource.",
            "schema": {}
          },
          "404": {
            "description": "Verification record not found.",
            "schema": {}
          },
          "500": {
            "description": "Internal Server Error.",
            "schema": {}
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "emailId",
            "description": "ID of the verify_email record.",
            "in": "query",
            "required": false,
            "type": "string",
            "format": "int64"
          },
          {
            "name": "secretCode",
            "description": "Secret code sent to the user's email address.",
            "in": "query",
            "required": false,
            "type": "string"
          }
        ],
        "tags": [
          "Auth"
        ],
        "security": []
      }
    }
  },
  "definitions": {
    "CreateTransferResponseAccountSnapshot": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64"
        },
        "owner": {
          "type": "string"
        },
        "balance": {
          "type": "number",
          "format": "double"
        },
        "currency": {
          "type": "string"
        }
      },
      "description": "Updated account snapshots after the atomic transaction\nUse these instead of re-fetching to avoid stale reads."
    },
    "GoBankUpdateAccountBody": {
      "type": "object",
      "properties": {
        "balance": {
          "type": "number",
          "format": "double",
          "example": 500.00,
          "description": "New balance in major currency unit (e.g. dollars). Must be \u003e= 0."
        }
      }
    },
    "pbAccount": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64",
          "description": "Unique account ID."
        },
        "owner": {
          "type": "string",
          "description": "Username of the account owner."
        },
        "balance": {
          "type": "number",
          "format": "double",
          "example": 1050.75,
          "description": "Current balance in major currency unit (e.g. dollars, not cents)."
        },
        "currency": {
          "type": "string",
          "example": "USD",
          "description": "ISO 4217 currency code."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the account was created."
        }
      }
    },
    "pbAccountLookUp": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64",
          "description": "Unique account ID."
        },
        "owner": {
          "type": "string",
          "description": "Username of the account owner."
        },
        "currency": {
          "type": "string",
          "example": "USD",
          "description": "ISO 4217 currency code."
        }
      }
    },
    "pbActivityEntry": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64",
          "description": "Unique activity entry ID."
        },
        "accountId": {
          "type": "string",
          "format": "int64",
          "description": "The account that owns this entry.",
          "minimum": 1
        },
        "amount": {
          "type": "string",
          "format": "int64",
          "example": 50000,
          "description": "Amount in the smallest currency unit. Negative = debit, positive = credit."
        },
        "currency": {
          "type": "string",
          "example": "USD",
          "description": "ISO 4217 currency of the account this entry belongs to."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the entry was created."
        },
        "transferId": {
          "type": "string",
          "format": "int64",
          "description": "Transfer ID when this entry was created by an internal transfer; null for manual adjustments."
        },
        "counterpartAccountId": {
          "type": "string",
          "format": "int64",
          "description": "Counterpart account ID on the other side of the transfer when available."
        },
        "counterpartOwner": {
          "type": "string",
          "description": "Owner username of the counterpart account when available."
        },
        "counterpartCurrency": {
          "type": "string",
          "description": "Currency of the counterpart account when available."
        }
      }
    },
    "pbCreateAccountRequest": {
      "type": "object",
      "properties": {
        "currency": {
          "type": "string",
          "example": "USD",
          "description": "ISO 4217 currency code. Supported values: USD, EUR, EGP. One account per currency per user."
        }
      }
    },
    "pbCreateAccountResponse": {
      "type": "object",
      "properties": {
        "account": {
          "$ref": "#/definitions/pbAccount"
        }
      }
    },
    "pbCreateTransferRequest": {
      "type": "object",
      "properties": {
        "fromAccountId": {
          "type": "string",
          "format": "int64",
          "description": "ID of the account to debit. Must belong to the authenticated user.",
          "minimum": 1
        },
        "toAccountId": {
          "type": "string",
          "format": "int64",
          "description": "ID of the account to credit. Can belong to any user.",
          "minimum": 1
        },
        "amount": {
          "type": "number",
          "format": "double",
          "example": 10.50,
          "description": "Amount to transfer in major currency unit (e.g. dollars). Must be \u003e 0."
        },
        "currency": {
          "type": "string",
          "example": "USD",
          "description": "Currency of the transfer. Both accounts must hold this currency."
        }
      }
    },
    "pbCreateTransferResponse": {
      "type": "object",
      "properties": {
        "transfer": {
          "$ref": "#/definitions/pbTransferRecord"
        },
        "fromEntry": {
          "$ref": "#/definitions/pbTransferEntry"
        },
        "toEntry": {
          "$ref": "#/definitions/pbTransferEntry"
        },
        "fromAccount": {
          "$ref": "#/definitions/CreateTransferResponseAccountSnapshot"
        },
        "toAccount": {
          "$ref": "#/definitions/CreateTransferResponseAccountSnapshot"
        }
      }
    },
    "pbCreateUserRequest": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string",
          "example": "john_doe_123",
          "description": "Unique alphanumeric username. Lowercase letters, digits, and underscores only.",
          "title": "Unique username. Alphanumeric only, no spaces.\nexample: \"john_doe_123\"",
          "maxLength": 50,
          "minLength": 3,
          "pattern": "^[a-z0-9_]+$"
        },
        "fullName": {
          "type": "string",
          "example": "John Doe",
          "description": "Full display name of the user.",
          "title": "Full display name of the user.\nexample: \"John Doe\"",
          "maxLength": 100,
          "minLength": 2
        },
        "email": {
          "type": "string",
          "example": "john@example.com",
          "description": "Valid email address. Must be unique across all accounts.",
          "title": "Valid email address. Must be unique across all accounts.\nexample: \"john@example.com\""
        },
        "password": {
          "type": "string",
          "format": "password",
          "example": "supersecret123",
          "description": "Account password. Minimum 8 characters. Stored as bcrypt hash.",
          "title": "Password for the account. Minimum 8 characters.\nStored as a bcrypt hash — never in plain text.\nexample: \"supersecret123\"",
          "minLength": 8
        }
      }
    },
    "pbCreateUserResponse": {
      "type": "object",
      "properties": {
        "user": {
          "$ref": "#/definitions/pbUser",
          "description": "The newly created user (password excluded)."
        }
      }
    },
    "pbDeleteAccountResponse": {
      "type": "object",
      "properties": {
        "status": {
          "type": "string",
          "example": "deleted",
          "description": "Confirmation message."
        }
      }
    },
    "pbGetAccountResponse": {
      "type": "object",
      "properties": {
        "account": {
          "$ref": "#/definitions/pbAccount"
        }
      }
    },
    "pbListAccountsResponse": {
      "type": "object",
      "properties": {
        "accounts": {
          "type": "array",
          "items": {
            "type": "object",
            "$ref": "#/definitions/pbAccount"
          }
        }
      }
    },
    "pbListEntriesResponse": {
      "type": "object",
      "properties": {
        "entries": {
          "type": "array",
          "items": {
            "type": "object",
            "$ref": "#/definitions/pbActivityEntry"
          }
        }
      }
    },
    "pbLoginUserRequest": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string",
          "example": "john_doe_123",
          "description": "Username of the account to authenticate."
        },
        "password": {
          "type": "string",
          "format": "password",
          "example": "supersecret123",
          "description": "Account password. Transmitted securely over HTTPS."
        }
      }
    },
    "pbLoginUserResponse": {
      "type": "object",
      "properties": {
        "sessionId": {
          "type": "string",
          "description": "UUID of the session created for this login."
        },
        "accessToken": {
          "type": "string",
          "description": "Short-lived PASETO access token. Include as: `Authorization: Bearer \u003ctoken\u003e`",
          "title": "Short-lived PASETO access token. Include as: Authorization: Bearer \u003ctoken\u003e"
        },
        "accessTokenExpiresAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the access token expires."
        },
        "refreshToken": {
          "type": "string",
          "description": "Long-lived refresh token. Store securely. Use at /v1/auth/renew_access to get a new access token."
        },
        "refreshTokenExpiresAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the refresh token expires. After this, the user must log in again."
        },
        "user": {
          "$ref": "#/definitions/pbUser",
          "description": "Authenticated user's public profile. Password is never returned."
        }
      }
    },
    "pbLookUpAccountResponse": {
      "type": "object",
      "properties": {
        "account": {
          "$ref": "#/definitions/pbAccountLookUp"
        }
      }
    },
    "pbRenewAccessTokenRequest": {
      "type": "object",
      "properties": {
        "refreshToken": {
          "type": "string",
          "description": "Valid, non-expired refresh token obtained from the login response."
        }
      }
    },
    "pbRenewAccessTokenResponse": {
      "type": "object",
      "properties": {
        "accessToken": {
          "type": "string",
          "description": "New short-lived PASETO access token. Use as: Authorization: Bearer \u003ctoken\u003e"
        },
        "accessTokenExpiresAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the new access token expires."
        }
      }
    },
    "pbTransferEntry": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64"
        },
        "accountId": {
          "type": "string",
          "format": "int64"
        },
        "amount": {
          "type": "number",
          "format": "double",
          "description": "Amount in major currency unit. Negative for debit entries."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "pbTransferRecord": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "int64"
        },
        "fromAccountId": {
          "type": "string",
          "format": "int64"
        },
        "toAccountId": {
          "type": "string",
          "format": "int64"
        },
        "amount": {
          "type": "number",
          "format": "double",
          "description": "Transferred amount in major currency unit. Always positive."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "pbUpdateAccountResponse": {
      "type": "object",
      "properties": {
        "account": {
          "$ref": "#/definitions/pbAccount"
        }
      }
    },
    "pbUpdateUserRequest": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string",
          "example": "john_doe_123",
          "description": "Unique alphanumeric username. Lowercase letters, digits, and underscores only.",
          "title": "Unique username. Alphanumeric only, no spaces.\nexample: \"john_doe_123\"",
          "maxLength": 50,
          "minLength": 3,
          "pattern": "^[a-z0-9_]+$"
        },
        "fullName": {
          "type": "string",
          "example": "John Doe",
          "description": "Full display name of the user.",
          "title": "Full display name of the user.\nexample: \"John Doe\"",
          "maxLength": 100,
          "minLength": 2
        },
        "email": {
          "type": "string",
          "example": "john@example.com",
          "description": "Valid email address. Must be unique across all accounts.",
          "title": "Valid email address. Must be unique across all accounts.\nexample: \"john@example.com\""
        },
        "password": {
          "type": "string",
          "format": "password",
          "example": "supersecret123",
          "description": "Account password. Minimum 8 characters. Stored as bcrypt hash.",
          "title": "Password for the account. Minimum 8 characters.\nStored as a bcrypt hash — never in plain text.\nexample: \"supersecret123\"",
          "minLength": 8
        }
      }
    },
    "pbUpdateUserResponse": {
      "type": "object",
      "properties": {
        "user": {
          "$ref": "#/definitions/pbUser",
          "description": "The updated user (password excluded)."
        }
      }
    },
    "pbUser": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string",
          "example": "john_doe_123",
          "description": "Unique alphanumeric username."
        },
        "fullName": {
          "type": "string",
          "example": "John Doe",
          "description": "Full display name of the user."
        },
        "email": {
          "type": "string",
          "example": "john@example.com",
          "description": "Registered email address."
        },
        "isEmailVerified": {
          "type": "boolean",
          "description": "Whether the user's email address has been verified."
        },
        "passwordChangedAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp of the last password change. Zero value means password was never changed."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time",
          "description": "UTC timestamp when the account was created."
        }
      },
      "description": "User represents the public profile of a GoBank account.\nSensitive fields (hashed_password) are never included."
    },
    "pbVerifyEmailResponse": {
      "type": "object",
      "properties": {
        "isVerified": {
          "type": "boolean",
          "description": "True if the email was successfully verified."
        }
      }
    },
    "protobufAny": {
      "type": "object",
      "properties": {
        "@type": {
          "type": "string"
        }
      },
      "additionalProperties": {}
    },
    "rpcStatus": {
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "format": "int32"
        },
        "message": {
          "type": "string"
        },
        "details": {
          "type": "array",
          "items": {
            "type": "object",
            "$ref": "#/definitions/protobufAny"
          }
        }
      }
    }
  },
  "securityDefinitions": {
    "BearerAuth": {
      "type": "apiKey",
      "description": "Enter: **Bearer \u0026lt;your_access_token\u0026gt;**",
      "name": "Authorization",
      "in": "header"
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ]
}
