openapi: 3.1.0
info:
  title: LetAgentPay Agent API
  description: |
    API for AI agents to submit purchase requests, check budgets, and confirm purchases.
    All endpoints require Bearer token authentication with an agent token (prefix `agt_`).
  version: 1.0.0
  contact:
    name: LetAgentPay
    url: https://letagentpay.com

servers:
  - url: https://letagentpay.com/api/v1/agent-api
    description: Production

security:
  - BearerAuth: []

paths:
  /requests:
    post:
      operationId: createPurchaseRequest
      summary: Create a purchase request
      description: |
        Submit a purchase request for policy evaluation. The request is checked against
        the agent's policy (category, limits, schedule, budget) and account-level budget rules.
        Returns `auto_approved`, `pending`, or `rejected` status.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional idempotency key to prevent duplicate requests (cached for 24 hours)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePurchaseRequest"
      responses:
        "201":
          description: Purchase request created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PurchaseRequestResponse"
        "400":
          description: Invalid request (e.g. currency mismatch)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid or missing Bearer token
        "429":
          description: Rate limit exceeded or too many pending requests

    get:
      operationId: listRequests
      summary: List purchase requests
      description: List agent's purchase requests with optional status filter, ordered by creation time (newest first).
      parameters:
        - name: status
          in: query
          required: false
          description: Filter by status
          schema:
            type: string
            enum: [pending, approved, auto_approved, rejected, completed, failed, expired]
        - name: limit
          in: query
          required: false
          description: Max number of results (default 20, max 100)
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of purchase requests
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestListResponse"
        "401":
          description: Invalid or missing Bearer token

  /requests/{request_id}:
    get:
      operationId: getRequestStatus
      summary: Check request status
      description: |
        Get the current status of a purchase request.
        Expired pending requests are automatically marked as `expired` when polled.
      parameters:
        - name: request_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Request details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestStatusResponse"
        "401":
          description: Invalid or missing Bearer token
        "404":
          description: Request not found

  /requests/{request_id}/confirm:
    post:
      operationId: confirmPurchase
      summary: Confirm purchase result
      description: |
        Report whether the purchase succeeded or failed. Only `approved` or `auto_approved`
        requests can be confirmed. If `success=false`, the spent amount is refunded.
        If `actual_amount` differs from the original, the budget is adjusted.
      parameters:
        - name: request_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConfirmPurchaseRequest"
      responses:
        "200":
          description: Purchase confirmed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConfirmResponse"
        "400":
          description: Request cannot be confirmed (wrong status)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid or missing Bearer token
        "404":
          description: Request not found

  /budget:
    get:
      operationId: getBudget
      summary: Check remaining budget
      description: Get the agent's current budget, spent amount, held amount, and remaining balance.
      responses:
        "200":
          description: Budget information
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BudgetResponse"
        "401":
          description: Invalid or missing Bearer token

  /policy:
    get:
      operationId: getPolicy
      summary: View current policy
      description: Get the agent's current spending policy as a JSON object.
      responses:
        "200":
          description: Agent policy
          content:
            application/json:
              schema:
                type: object
                properties:
                  policy:
                    $ref: "#/components/schemas/Policy"
        "401":
          description: Invalid or missing Bearer token

  /categories:
    get:
      operationId: getCategories
      summary: List valid categories
      description: Get a sorted list of all valid spending category names.
      responses:
        "200":
          description: List of categories
          content:
            application/json:
              schema:
                type: object
                required: [categories]
                properties:
                  categories:
                    type: array
                    items:
                      type: string
                    example:
                      - accommodation
                      - clothing
                      - education
                      - electronics
                      - entertainment
                      - flights
                      - food_delivery
                      - gas
                      - groceries
                      - health
                      - household
                      - other
                      - restaurants
                      - subscriptions
                      - taxi
                      - transport

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: Agent token with `agt_` prefix

  schemas:
    CreatePurchaseRequest:
      type: object
      required: [amount, category]
      properties:
        amount:
          type: number
          exclusiveMinimum: 0
          description: Purchase amount
        currency:
          type: string
          maxLength: 3
          description: ISO 4217 currency code (e.g. USD). Defaults to account currency
          nullable: true
        category:
          type: string
          maxLength: 50
          description: Category name — auto-resolved via aliases + AI if not an exact match
        merchant_name:
          type: string
          maxLength: 255
          description: Merchant or store name
          nullable: true
        description:
          type: string
          description: What is being purchased
          nullable: true
        agent_comment:
          type: string
          maxLength: 2000
          description: Agent's reasoning for this purchase (shown to the account owner)
          nullable: true

    PurchaseRequestResponse:
      type: object
      required: [request_id, status, auto_approved]
      properties:
        request_id:
          type: string
          description: Unique request ID
        status:
          type: string
          enum: [auto_approved, pending, rejected]
          description: Result of policy evaluation
        currency:
          type: string
          description: Currency used
          nullable: true
        category:
          type: string
          description: Resolved category
          nullable: true
        original_category:
          type: string
          description: Category as sent by agent (before resolution)
          nullable: true
        policy_check:
          type: object
          description: Detailed results of each policy check
          properties:
            passed:
              type: boolean
            checks:
              type: array
              items:
                type: object
                properties:
                  rule:
                    type: string
                  result:
                    type: string
                    enum: [pass, fail]
                  detail:
                    type: string
          nullable: true
        auto_approved:
          type: boolean
          description: Whether the request was auto-approved
        budget_remaining:
          type: number
          description: Remaining budget after this request (only present if auto_approved)
          nullable: true
        expires_at:
          type: string
          format: date-time
          description: Expiry time for pending requests (ISO 8601)
          nullable: true

    RequestStatusResponse:
      type: object
      required: [request_id, status, amount, category]
      properties:
        request_id:
          type: string
        status:
          type: string
          enum: [pending, approved, auto_approved, rejected, completed, failed, expired]
        amount:
          type: number
        category:
          type: string
        created_at:
          type: string
          format: date-time
        reviewed_at:
          type: string
          format: date-time
          nullable: true

    RequestListResponse:
      type: object
      required: [requests, total, limit, offset]
      properties:
        requests:
          type: array
          items:
            type: object
            properties:
              request_id:
                type: string
              status:
                type: string
              amount:
                type: number
              currency:
                type: string
              category:
                type: string
              merchant:
                type: string
                nullable: true
              description:
                type: string
                nullable: true
              created_at:
                type: string
                format: date-time
              reviewed_at:
                type: string
                format: date-time
                nullable: true
              expires_at:
                type: string
                format: date-time
                nullable: true
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer

    ConfirmPurchaseRequest:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          description: Whether the purchase succeeded
        actual_amount:
          type: number
          description: Actual amount charged (if different from requested — budget is adjusted)
          nullable: true
        receipt_url:
          type: string
          maxLength: 2000
          description: URL to receipt or proof of purchase
          nullable: true

    ConfirmResponse:
      type: object
      required: [request_id, status]
      properties:
        request_id:
          type: string
        status:
          type: string
          enum: [completed, failed]
        actual_amount:
          type: string
          description: Actual amount, if provided
          nullable: true

    BudgetResponse:
      type: object
      required: [budget, spent, held, remaining]
      properties:
        budget:
          type: number
          description: Total budget
        spent:
          type: number
          description: Amount spent
        held:
          type: number
          description: Amount held (reserved by pending requests)
        remaining:
          type: number
          description: Available budget (budget - spent - held)
        currency:
          type: string
          description: Account currency
          nullable: true

    Policy:
      type: object
      properties:
        daily_limit:
          type: number
          nullable: true
        weekly_limit:
          type: number
          nullable: true
        monthly_limit:
          type: number
          nullable: true
        per_request_limit:
          type: number
          nullable: true
        allowed_categories:
          type: array
          items:
            type: string
          nullable: true
        blocked_categories:
          type: array
          items:
            type: string
          nullable: true
        schedule:
          type: object
          description: Time-based access schedule
          nullable: true
        auto_approve:
          type: object
          description: Auto-approve criteria
          nullable: true

    Error:
      type: object
      required: [detail]
      properties:
        detail:
          type: string
          description: Error message