---
openapi: 3.0.3
info:
  title: Sure API
  version: v1
  description: OpenAPI documentation generated from executable request specs.
servers:
  - url: http://localhost:3000
    description: Production
  - url: http://localhost:3000
    description: Local development
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key for authentication. Supports read and read_write scopes.
  schemas:
    Pagination:
      type: object
      required:
        - page
        - per_page
        - total_count
        - total_pages
      properties:
        page:
          type: integer
          minimum: 1
        per_page:
          type: integer
          minimum: 1
        total_count:
          type: integer
          minimum: 0
        total_pages:
          type: integer
          minimum: 0
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        message:
          type: string
          nullable: true
        details:
          oneOf:
            - type: array
              items:
                type: string
            - type: object
          nullable: true
    ToolCall:
      type: object
      required:
        - id
        - function_name
        - function_arguments
        - created_at
      properties:
        id:
          type: string
          format: uuid
        function_name:
          type: string
        function_arguments:
          type: object
          additionalProperties: true
        function_result:
          type: object
          additionalProperties: true
          nullable: true
        created_at:
          type: string
          format: date-time
    Message:
      type: object
      required:
        - id
        - type
        - role
        - content
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - user_message
            - assistant_message
        role:
          type: string
          enum:
            - user
            - assistant
        content:
          type: string
        model:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        tool_calls:
          type: array
          items:
            "$ref": "#/components/schemas/ToolCall"
          nullable: true
    MessageResponse:
      allOf:
        - "$ref": "#/components/schemas/Message"
        - type: object
          required:
            - chat_id
          properties:
            chat_id:
              type: string
              format: uuid
            ai_response_status:
              type: string
              enum:
                - pending
                - complete
                - failed
              nullable: true
            ai_response_message:
              type: string
              nullable: true
    ChatResource:
      type: object
      required:
        - id
        - title
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        error:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ChatSummary:
      allOf:
        - "$ref": "#/components/schemas/ChatResource"
        - type: object
          required:
            - message_count
          properties:
            message_count:
              type: integer
              minimum: 0
            last_message_at:
              type: string
              format: date-time
              nullable: true
    ChatDetail:
      allOf:
        - "$ref": "#/components/schemas/ChatResource"
        - type: object
          required:
            - messages
          properties:
            messages:
              type: array
              items:
                "$ref": "#/components/schemas/Message"
            pagination:
              "$ref": "#/components/schemas/Pagination"
              nullable: true
    ChatCollection:
      type: object
      required:
        - chats
        - pagination
      properties:
        chats:
          type: array
          items:
            "$ref": "#/components/schemas/ChatSummary"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    RetryResponse:
      type: object
      required:
        - message
        - message_id
      properties:
        message:
          type: string
        message_id:
          type: string
          format: uuid
    Account:
      type: object
      required:
        - id
        - name
        - balance
        - currency
        - classification
        - account_type
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        balance:
          type: string
          description: Formatted balance (e.g. "$1,234.56")
        currency:
          type: string
          description: Currency code (e.g. "USD")
        classification:
          type: string
          description: Account classification (e.g. "asset", "liability")
        account_type:
          type: string
          description: Accountable type (e.g. "depository", "investment", "credit_card")
    AccountDetail:
      type: object
      required:
        - id
        - name
        - balance
        - currency
        - classification
        - account_type
        - is_active
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        balance:
          type: string
          description: Formatted balance (e.g. "$1,234.56")
        currency:
          type: string
          description: Currency code (e.g. "USD")
        classification:
          type: string
          description: Account classification (e.g. "asset", "liability")
        account_type:
          type: string
          description: Accountable type (e.g. "depository", "investment", "credit_card")
        subtype:
          type: string
          nullable: true
          description: Account subtype (e.g. "checking", "savings")
        institution_name:
          type: string
          nullable: true
          description: Name of the financial institution
        institution_domain:
          type: string
          nullable: true
          description: Domain of the financial institution
        notes:
          type: string
          nullable: true
          description: Additional notes about the account
        is_active:
          type: boolean
          description: Whether the account is active
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Category:
      type: object
      required:
        - id
        - name
        - classification
        - color
        - icon
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        classification:
          type: string
        color:
          type: string
        icon:
          type: string
    CategoryParent:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
    CategoryDetail:
      type: object
      required:
        - id
        - name
        - classification
        - color
        - icon
        - subcategories_count
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        classification:
          type: string
          enum:
            - income
            - expense
        color:
          type: string
        icon:
          type: string
        parent:
          "$ref": "#/components/schemas/CategoryParent"
          nullable: true
        subcategories_count:
          type: integer
          minimum: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CategoryCollection:
      type: object
      required:
        - categories
        - pagination
      properties:
        categories:
          type: array
          items:
            "$ref": "#/components/schemas/CategoryDetail"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    MerchantDetail:
      type: object
      required:
        - id
        - name
        - color
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        color:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    MerchantCollection:
      type: object
      required:
        - merchants
        - pagination
      properties:
        merchants:
          type: array
          items:
            "$ref": "#/components/schemas/MerchantDetail"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    Merchant:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
    Tag:
      type: object
      required:
        - id
        - name
        - color
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        color:
          type: string
    Transfer:
      type: object
      required:
        - id
        - amount
        - currency
      properties:
        id:
          type: string
          format: uuid
        amount:
          type: string
        currency:
          type: string
        other_account:
          "$ref": "#/components/schemas/Account"
          nullable: true
    Transaction:
      type: object
      required:
        - id
        - date
        - amount
        - currency
        - name
        - classification
        - account
        - tags
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        amount:
          type: string
        currency:
          type: string
        name:
          type: string
        notes:
          type: string
          nullable: true
        classification:
          type: string
        account:
          "$ref": "#/components/schemas/Account"
        category:
          "$ref": "#/components/schemas/Category"
          nullable: true
        merchant:
          "$ref": "#/components/schemas/Merchant"
          nullable: true
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/Tag"
        transfer:
          "$ref": "#/components/schemas/Transfer"
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TransactionCollection:
      type: object
      required:
        - transactions
        - pagination
      properties:
        transactions:
          type: array
          items:
            "$ref": "#/components/schemas/Transaction"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    DeleteResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
    AccountCollection:
      type: object
      required:
        - accounts
        - pagination
      properties:
        accounts:
          type: array
          items:
            "$ref": "#/components/schemas/Account"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    UsageApiKeyResponse:
      type: object
      required:
        - api_key
        - rate_limit
      properties:
        api_key:
          type: object
          required:
            - name
            - scopes
            - last_used_at
            - created_at
          properties:
            name:
              type: string
            scopes:
              type: array
              items:
                type: string
            last_used_at:
              type: string
              format: date-time
              nullable: true
            created_at:
              type: string
              format: date-time
        rate_limit:
          type: object
          required:
            - tier
            - limit
            - current_count
            - remaining
            - reset_in_seconds
            - reset_at
          properties:
            tier:
              type: string
              enum:
                - standard
                - premium
                - enterprise
            limit:
              type: integer
            current_count:
              type: integer
            remaining:
              type: integer
            reset_in_seconds:
              type: integer
            reset_at:
              type: string
              format: date-time
    UsageOAuthResponse:
      type: object
      required:
        - authentication_method
        - message
      properties:
        authentication_method:
          type: string
          enum:
            - oauth
        message:
          type: string
    SyncResponse:
      type: object
      required:
        - id
        - status
        - syncable_type
        - syncable_id
        - message
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - syncing
            - completed
            - failed
        syncable_type:
          type: string
        syncable_id:
          type: string
          format: uuid
        syncing_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        window_start_date:
          type: string
          format: date
          nullable: true
        window_end_date:
          type: string
          format: date
          nullable: true
        message:
          type: string
    AuthTokenResponse:
      type: object
      required:
        - access_token
        - refresh_token
        - token_type
        - expires_in
        - created_at
      properties:
        access_token:
          type: string
        refresh_token:
          type: string
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          description: Token expiration time in seconds
        created_at:
          type: integer
          description: Unix timestamp of token creation
    AuthSignupResponse:
      allOf:
        - "$ref": "#/components/schemas/AuthTokenResponse"
        - type: object
          required:
            - user
          properties:
            user:
              type: object
              required:
                - id
                - email
                - first_name
                - last_name
              properties:
                id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                first_name:
                  type: string
                last_name:
                  type: string
    AuthLoginResponse:
      allOf:
        - "$ref": "#/components/schemas/AuthTokenResponse"
        - type: object
          required:
            - user
          properties:
            user:
              type: object
              required:
                - id
                - email
                - first_name
                - last_name
              properties:
                id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                first_name:
                  type: string
                last_name:
                  type: string
paths:
  "/api/v1/accounts":
    get:
      summary: List accounts
      tags:
        - Accounts
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
        - name: page
          in: query
          required: false
          description: "Page number (default: 1)"
          schema:
            type: integer
        - name: per_page
          in: query
          required: false
          description: "Items per page (default: 25, max: 100)"
          schema:
            type: integer
      responses:
        "200":
          description: accounts listed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountCollection"
        "401":
          description: unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    post:
      summary: Create account
      tags:
        - Accounts
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "201":
          description: account created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountDetail"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                account:
                  type: object
                  properties:
                    name:
                      type: string
                      description: Account name
                    balance:
                      type: number
                      description: Initial account balance
                    currency:
                      type: string
                      description: Currency code (defaults to family currency if not provided)
                    accountable_type:
                      type: string
                      description: Type of account (e.g. "Depository", "CreditCard", "Investment", "Property", "Loan", "OtherAsset", "OtherLiability")
                    subtype:
                      type: string
                      description: Account subtype (e.g. "checking", "savings")
                    institution_name:
                      type: string
                      description: Name of the financial institution
                    institution_domain:
                      type: string
                      description: Domain of the financial institution
                    notes:
                      type: string
                      description: Additional notes
                    accountable_attributes:
                      type: object
                      description: Type-specific attributes (varies by accountable_type)
                      additionalProperties: true
                  required:
                    - name
                    - accountable_type
              required:
                - account
        required: true
  "/api/v1/accounts/{id}":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with read scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with read scope (alternative to Bearer token)
      - name: id
        in: path
        required: true
        description: Account ID
        schema:
          type: string
    get:
      summary: Retrieve an account
      tags:
        - Accounts
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: account retrieved
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountDetail"
        "404":
          description: account not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    patch:
      summary: Update an account
      tags:
        - Accounts
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "200":
          description: account updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountDetail"
        "404":
          description: account not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                account:
                  type: object
                  properties:
                    name:
                      type: string
                    balance:
                      type: number
                      description: Updates the current balance of the account
                    subtype:
                      type: string
                    institution_name:
                      type: string
                    institution_domain:
                      type: string
                    notes:
                      type: string
                    accountable_attributes:
                      type: object
                      additionalProperties: true
        required: true
    delete:
      summary: Delete an account
      tags:
        - Accounts
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: account deleted
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        "404":
          description: account not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: cannot delete linked account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/auth/signup":
    post:
      summary: Sign up a new user
      tags:
        - Authentication
      security: []
      parameters: []
      responses:
        "201":
          description: user created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AuthSignupResponse"
        "403":
          description: invite code required or invalid
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    email:
                      type: string
                      format: email
                    password:
                      type: string
                      format: password
                      description: Must be at least 8 characters with uppercase, lowercase, number, and special character
                    first_name:
                      type: string
                    last_name:
                      type: string
                  required:
                    - email
                    - password
                    - first_name
                    - last_name
                invite_code:
                  type: string
                  description: Required if invite codes are enabled
                device:
                  type: object
                  properties:
                    device_id:
                      type: string
                    device_name:
                      type: string
                    device_type:
                      type: string
                    os_version:
                      type: string
                    app_version:
                      type: string
                  required:
                    - device_id
                    - device_name
                    - device_type
                    - os_version
                    - app_version
              required:
                - user
                - device
        required: true
  "/api/v1/auth/login":
    post:
      summary: Log in a user
      tags:
        - Authentication
      security: []
      parameters: []
      responses:
        "200":
          description: login successful
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AuthLoginResponse"
        "401":
          description: invalid credentials or MFA required
          content:
            application/json:
              schema:
                oneOf:
                  - "$ref": "#/components/schemas/ErrorResponse"
                  - type: object
                    properties:
                      error:
                        type: string
                      mfa_required:
                        type: boolean
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  format: password
                otp_code:
                  type: string
                  description: Required if user has MFA enabled
                device:
                  type: object
                  properties:
                    device_id:
                      type: string
                    device_name:
                      type: string
                    device_type:
                      type: string
                    os_version:
                      type: string
                    app_version:
                      type: string
                  required:
                    - device_id
                    - device_name
                    - device_type
                    - os_version
                    - app_version
              required:
                - email
                - password
                - device
        required: true
  "/api/v1/auth/refresh":
    post:
      summary: Refresh an access token
      tags:
        - Authentication
      security: []
      parameters: []
      responses:
        "200":
          description: token refreshed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AuthTokenResponse"
        "400":
          description: refresh token missing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "401":
          description: invalid refresh token
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                refresh_token:
                  type: string
                device:
                  type: object
                  properties:
                    device_id:
                      type: string
                  required:
                    - device_id
              required:
                - refresh_token
        required: true
  "/api/v1/categories":
    get:
      summary: List categories
      tags:
        - Categories
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
        - name: page
          in: query
          required: false
          description: "Page number (default: 1)"
          schema:
            type: integer
        - name: per_page
          in: query
          required: false
          description: "Items per page (default: 25, max: 100)"
          schema:
            type: integer
        - name: classification
          in: query
          required: false
          description: Filter by classification (income or expense)
          schema:
            type: string
            enum:
              - income
              - expense
        - name: roots_only
          in: query
          required: false
          description: Return only root categories (no parent)
          schema:
            type: boolean
        - name: parent_id
          in: query
          required: false
          description: Filter by parent category ID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: categories filtered by parent
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CategoryCollection"
    post:
      summary: Create category
      tags:
        - Categories
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "201":
          description: category created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CategoryDetail"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                category:
                  type: object
                  properties:
                    name:
                      type: string
                      description: Category name
                    classification:
                      type: string
                      enum:
                        - income
                        - expense
                      description: Category classification
                    color:
                      type: string
                      description: Category color (hex code)
                    lucide_icon:
                      type: string
                      description: Lucide icon name
                    parent_id:
                      type: string
                      format: uuid
                      description: Parent category ID for subcategories
                  required:
                    - name
                    - classification
                    - color
              required:
                - category
        required: true
  "/api/v1/categories/{id}":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with read scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with read scope (alternative to Bearer token)
      - name: id
        in: path
        required: true
        description: Category ID
        schema:
          type: string
    get:
      summary: Retrieve a category
      tags:
        - Categories
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: subcategory retrieved with parent
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CategoryDetail"
        "404":
          description: category not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    patch:
      summary: Update a category
      tags:
        - Categories
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "200":
          description: category updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CategoryDetail"
        "404":
          description: category not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                category:
                  type: object
                  properties:
                    name:
                      type: string
                    classification:
                      type: string
                      enum:
                        - income
                        - expense
                    color:
                      type: string
                    lucide_icon:
                      type: string
                    parent_id:
                      type: string
                      format: uuid
        required: true
    delete:
      summary: Delete a category
      tags:
        - Categories
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: category deleted
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        "404":
          description: category not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/merchants":
    get:
      summary: List merchants
      tags:
        - Merchants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
        - name: page
          in: query
          required: false
          description: "Page number (default: 1)"
          schema:
            type: integer
        - name: per_page
          in: query
          required: false
          description: "Items per page (default: 25, max: 100)"
          schema:
            type: integer
      responses:
        "200":
          description: merchants listed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MerchantCollection"
    post:
      summary: Create merchant
      tags:
        - Merchants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "201":
          description: merchant created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MerchantDetail"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                merchant:
                  type: object
                  properties:
                    name:
                      type: string
                      description: Merchant name
                    color:
                      type: string
                      description: Merchant color (hex code)
                  required:
                    - name
              required:
                - merchant
        required: true
  "/api/v1/merchants/{id}":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with read scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with read scope (alternative to Bearer token)
      - name: id
        in: path
        required: true
        description: Merchant ID
        schema:
          type: string
    get:
      summary: Retrieve a merchant
      tags:
        - Merchants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: merchant retrieved
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MerchantDetail"
        "404":
          description: merchant not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    patch:
      summary: Update a merchant
      tags:
        - Merchants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "200":
          description: merchant updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MerchantDetail"
        "404":
          description: merchant not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                merchant:
                  type: object
                  properties:
                    name:
                      type: string
                    color:
                      type: string
        required: true
    delete:
      summary: Delete a merchant
      tags:
        - Merchants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: merchant deleted
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        "404":
          description: merchant not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/chats":
    get:
      summary: List chats
      tags:
        - Chats
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
      responses:
        "200":
          description: chats listed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ChatCollection"
        "403":
          description: AI features disabled
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    post:
      summary: Create chat
      tags:
        - Chats
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "201":
          description: chat created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ChatDetail"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  example: Monthly budget review
                message:
                  type: string
                  description: Optional initial message in the chat
                model:
                  type: string
                  description: Optional OpenAI model identifier
              required:
                - title
        required: true
  "/api/v1/chats/{id}":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with read scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with read scope (alternative to Bearer token)
      - name: id
        in: path
        required: true
        description: Chat ID
        schema:
          type: string
    get:
      summary: Retrieve a chat
      tags:
        - Chats
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: chat retrieved
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ChatDetail"
        "404":
          description: chat not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    patch:
      summary: Update a chat
      tags:
        - Chats
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "200":
          description: chat updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ChatDetail"
        "404":
          description: chat not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  example: Updated chat title
        required: true
    delete:
      summary: Delete a chat
      tags:
        - Chats
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "204":
          description: chat deleted
        "404":
          description: chat not found
  "/api/v1/chats/{chat_id}/messages":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with write scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with write scope (alternative to Bearer token)
      - name: chat_id
        in: path
        required: true
        description: Chat ID
        schema:
          type: string
    post:
      summary: Create a message
      tags:
        - Chat Messages
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "201":
          description: message created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        "404":
          description: chat not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                content:
                  type: string
                model:
                  type: string
              required:
                - content
        required: true
  "/api/v1/chats/{chat_id}/messages/retry":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with write scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with write scope (alternative to Bearer token)
      - name: chat_id
        in: path
        required: true
        description: Chat ID
        schema:
          type: string
    post:
      summary: Retry the last assistant response
      tags:
        - Chat Messages
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "202":
          description: retry started
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/RetryResponse"
        "404":
          description: chat not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: no assistant message available
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/transactions":
    get:
      summary: List transactions
      tags:
        - Transactions
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
        - name: page
          in: query
          required: false
          description: "Page number (default: 1)"
          schema:
            type: integer
        - name: per_page
          in: query
          required: false
          description: "Items per page (default: 25, max: 100)"
          schema:
            type: integer
        - name: account_id
          in: query
          required: false
          description: Filter by account ID
          schema:
            type: string
        - name: category_id
          in: query
          required: false
          description: Filter by category ID
          schema:
            type: string
        - name: merchant_id
          in: query
          required: false
          description: Filter by merchant ID
          schema:
            type: string
        - name: start_date
          in: query
          required: false
          description: Filter transactions from this date
          schema:
            type: string
            format: date
        - name: end_date
          in: query
          required: false
          description: Filter transactions until this date
          schema:
            type: string
            format: date
        - name: min_amount
          in: query
          required: false
          description: Filter by minimum amount
          schema:
            type: number
        - name: max_amount
          in: query
          required: false
          description: Filter by maximum amount
          schema:
            type: number
        - name: type
          in: query
          required: false
          description: Filter by transaction type
          schema:
            type: string
            enum:
              - income
              - expense
        - name: search
          in: query
          required: false
          description: Search by name, notes, or merchant name
          schema:
            type: string
        - name: account_ids
          in: query
          required: false
          description: Filter by multiple account IDs
          schema:
            type: array
            items:
              type: string
        - name: category_ids
          in: query
          required: false
          description: Filter by multiple category IDs
          schema:
            type: array
            items:
              type: string
        - name: merchant_ids
          in: query
          required: false
          description: Filter by multiple merchant IDs
          schema:
            type: array
            items:
              type: string
        - name: tag_ids
          in: query
          required: false
          description: Filter by tag IDs
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: transactions filtered by date range
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TransactionCollection"
    post:
      summary: Create transaction
      tags:
        - Transactions
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "201":
          description: transaction created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Transaction"
        "422":
          description: validation error - missing required fields
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                transaction:
                  type: object
                  properties:
                    account_id:
                      type: string
                      format: uuid
                      description: Account ID (required)
                    date:
                      type: string
                      format: date
                      description: Transaction date
                    amount:
                      type: number
                      description: Transaction amount
                    name:
                      type: string
                      description: Transaction name/description
                    description:
                      type: string
                      description: Alternative to name field
                    notes:
                      type: string
                      description: Additional notes
                    currency:
                      type: string
                      description: Currency code (defaults to family currency)
                    category_id:
                      type: string
                      format: uuid
                      description: Category ID
                    merchant_id:
                      type: string
                      format: uuid
                      description: Merchant ID
                    nature:
                      type: string
                      enum:
                        - income
                        - expense
                        - inflow
                        - outflow
                      description: Transaction nature (determines sign)
                    tag_ids:
                      type: array
                      items:
                        type: string
                        format: uuid
                      description: Array of tag IDs
                  required:
                    - account_id
                    - date
                    - amount
                    - name
              required:
                - transaction
        required: true
  "/api/v1/transactions/{id}":
    parameters:
      - name: Authorization
        in: header
        required: false
        schema:
          type: string
        description: Bearer token with read scope (alternative to X-Api-Key)
      - name: X-Api-Key
        in: header
        required: false
        schema:
          type: string
        description: API key with read scope (alternative to Bearer token)
      - name: id
        in: path
        required: true
        description: Transaction ID
        schema:
          type: string
    get:
      summary: Retrieve a transaction
      tags:
        - Transactions
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: transaction retrieved
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Transaction"
        "404":
          description: transaction not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    patch:
      summary: Update a transaction
      tags:
        - Transactions
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters: []
      responses:
        "200":
          description: transaction updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Transaction"
        "404":
          description: transaction not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                transaction:
                  type: object
                  properties:
                    date:
                      type: string
                      format: date
                    amount:
                      type: number
                    name:
                      type: string
                    description:
                      type: string
                      description: Alternative to name field
                    notes:
                      type: string
                    currency:
                      type: string
                      description: Currency code
                    category_id:
                      type: string
                      format: uuid
                    merchant_id:
                      type: string
                      format: uuid
                    nature:
                      type: string
                      enum:
                        - income
                        - expense
                        - inflow
                        - outflow
                    tag_ids:
                      type: array
                      items:
                        type: string
                        format: uuid
        required: true
    delete:
      summary: Delete a transaction
      tags:
        - Transactions
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: transaction deleted
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        "404":
          description: transaction not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/sync":
    post:
      summary: Trigger a family sync
      description: Triggers a sync operation that will apply all active rules, sync all accounts, and auto-match transfers
      tags:
        - Sync
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with write scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with write scope (alternative to Bearer token)
      responses:
        "202":
          description: sync queued
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SyncResponse"
        "401":
          description: unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "403":
          description: insufficient scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "500":
          description: internal server error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/usage":
    get:
      summary: Get API usage information
      description: Returns usage statistics for API key authentication or a message for OAuth authentication
      tags:
        - Usage
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: Bearer token with read scope (alternative to X-Api-Key)
        - name: X-Api-Key
          in: header
          required: false
          schema:
            type: string
          description: API key with read scope (alternative to Bearer token)
      responses:
        "200":
          description: usage information retrieved
          content:
            application/json:
              schema:
                oneOf:
                  - "$ref": "#/components/schemas/UsageApiKeyResponse"
                  - "$ref": "#/components/schemas/UsageOAuthResponse"
        "401":
          description: unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
