openapi: "3.0.3"
info:
  title: AvoPay API
  version: "1.0.0"
  description: |
    Bitcoin Lightning + Liquid BTC payment API. Payments go straight to your
    self-custody AQUA wallet — no KYC, no chargebacks, no middleman.

    **AI Agent onboarding:** use `POST /api/v1/account/register` to create an account
    and receive a Lightning invoice. Once paid, poll the status endpoint for your API key.
    No free trial — Lightning payment required upfront.

    **Base URL:** `https://api.avopay.dev`

    **Supported currencies:** EUR, USD, GBP, CAD, AUD, JPY, CHF, DKK, BRL

servers:
  - url: https://api.avopay.dev

components:
  securitySchemes:
    SecretKey:
      type: http
      scheme: bearer
      description: Secret API key (`ak_live_...`) — merchant backend calls only
    PublishableKey:
      type: http
      scheme: bearer
      description: Publishable key (`ak_pub_...`) — safe to use in browser JS SDK

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
      required: [error]

    Payment:
      type: object
      properties:
        payment_id:
          type: string
          example: pmt_abc123
        status:
          type: string
          enum: [pending, paid, expired]
          description: "`pending` until settlement; `paid` on `transaction.mempool`; `expired` on swap failure"
        amount_fiat:
          type: number
          description: Amount in fiat currency
          example: 25.00
        currency:
          type: string
          example: EUR
        amount_sats:
          type: integer
          description: Order amount in satoshis (fiat converted at current BTC rate)
          example: 12500
        invoice_sats:
          type: integer
          description: |
            Amount the buyer must pay via Lightning (satoshis). Usually equal to `amount_sats`
            but may be higher when `amount_sats` falls below Boltz's minimum swap size (~1000 sat).
            Always use this value when displaying the Lightning invoice amount.
          example: 12500
        methods:
          type: array
          items:
            type: string
            enum: [lightning, liquid, bitcoin]
          description: Payment methods available for this payment. `bitcoin` is included only when the merchant has Bitcoin on-chain enabled and the amount meets Boltz's minimum.
          example: ["lightning", "liquid"]
        lightning_invoice:
          type: string
          description: BOLT11 invoice. Render as a QR code (bare string — no `lightning:` prefix). Use as an href with the `lightning:` scheme for tap-to-pay links.
          example: lnbc125u1p...
        liquid_address:
          type: string
          description: Confidential Liquid BTC address. Render as a QR code (bare string — no URI prefix).
          example: VJL4iV2MML...
        btc_address:
          type: string
          nullable: true
          description: Native Bitcoin address for on-chain payment. Present only when `methods` includes `"bitcoin"`. Use with `btc_amount_sat` to construct a BIP21 URI.
          example: bc1q...
        btc_amount_sat:
          type: integer
          nullable: true
          description: Amount in satoshis to send to `btc_address`. Required to construct a valid BIP21 URI.
          example: 12500
        checkout_url:
          type: string
          description: Hosted checkout page. Redirect your buyer here for a zero-integration checkout. Alternatively, render `lightning_invoice` and `liquid_address` in your own UI.
          example: https://avopay.dev/pay/pmt_abc123
        expires_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the Lightning invoice expires (10 minutes after creation).
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of payment creation. Returned by GET only.
        settled_at:
          type: string
          format: date-time
          nullable: true
          description: ISO 8601 timestamp of settlement. `null` until the payment is `paid`. Returned by GET only.
        metadata:
          type: object
          additionalProperties: true
          description: Arbitrary key-value data you passed at creation. Returned verbatim in webhooks.
      example:
        payment_id: pmt_abc123
        status: pending
        amount_fiat: 25.00
        currency: EUR
        amount_sats: 12500
        invoice_sats: 12500
        methods: ["lightning", "liquid"]
        lightning_invoice: "lnbc125u1p..."
        liquid_address: "VJL4iV2MML..."
        btc_address: null
        btc_amount_sat: null
        checkout_url: "https://avopay.dev/pay/pmt_abc123"
        expires_at: "2026-06-19T12:10:00Z"

    PaymentLink:
      type: object
      properties:
        id:
          type: string
          example: lnk_abc123
        title:
          type: string
        amount:
          type: number
          nullable: true
          description: Fixed amount, or null for customer-specified
        currency:
          type: string
        url:
          type: string
        active:
          type: boolean
        created_at:
          type: string
          format: date-time

    AgentRegistration:
      type: object
      properties:
        registration_id:
          type: string
          example: a1b2c3d4e5f6...
        invoice:
          type: string
          description: BOLT11 Lightning invoice to pay
        sats:
          type: integer
          description: Amount in satoshis
        plan:
          type: string
          enum: [lightning_monthly, lightning_annual]
        expires_at:
          type: string
          format: date-time
        message:
          type: string

    RegistrationStatus:
      type: object
      properties:
        status:
          type: string
          enum: [pending, active, expired]
        invoice:
          type: string
          description: Present when status is pending
        sats:
          type: integer
          description: Present when status is pending
        expires_at:
          type: string
          format: date-time
          description: Present when status is pending
        api_key:
          type: string
          description: Present on first active poll only — save immediately, not shown again
        plan:
          type: string
          description: Present when status is active
        api_key_retrieved:
          type: boolean
          description: True when api_key was already retrieved in a previous poll

paths:

  # ── Agent Registration ────────────────────────────────────────────────────────

  /api/v1/account/register:
    post:
      summary: Register an agent account (programmatic onboarding)
      description: |
        Creates a pending registration and returns a Lightning invoice.
        No free trial — payment required to activate the account.
        Rate limited to 5 requests per hour per IP.
      tags: [Agent Onboarding]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, plan, terms_accepted]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
                plan:
                  type: string
                  enum: [monthly, yearly]
                  description: monthly = $9/mo, yearly = $72/yr (billed as one Lightning payment)
                terms_accepted:
                  type: boolean
                  description: Must be true — programmatic acceptance of Terms & Conditions
            example:
              email: agent@example.com
              password: SecurePass123
              plan: monthly
              terms_accepted: true
      responses:
        "201":
          description: Registration created — pay the invoice to activate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentRegistration"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Email already registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Could not create Lightning invoice
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/v1/account/register/status/{registration_id}:
    get:
      summary: Poll registration status
      description: |
        Poll until `status` is `"active"`. The `api_key` field is returned only
        on the **first** successful poll after payment — save it immediately.
        Subsequent polls return `api_key_retrieved: true` without the key.
      tags: [Agent Onboarding]
      parameters:
        - name: registration_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Current registration status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegistrationStatus"
        "404":
          description: Registration not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/v1/connect/descriptor:
    post:
      summary: Connect wallet via Liquid descriptor (no AQUA QR required)
      description: |
        Supply your own LBTC and BTCLN Liquid descriptors directly.
        This is the programmatic alternative to scanning the AQUA wallet QR code.
        Resets address indexes to 0. Requires a secret API key.
      tags: [Agent Onboarding]
      security:
        - SecretKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [lbtc_descriptor, btcln_descriptor]
              properties:
                lbtc_descriptor:
                  type: string
                  description: Liquid BTC confidential descriptor (ct(slip77(...),elsh(wpkh(...))))
                  example: "ct(slip77(master_key_hex),elsh(wpkh([fp/49'/1776'/0']xpub.../0/*)))"
                btcln_descriptor:
                  type: string
                  description: Lightning (Boltz swap) descriptor — same xpub, separate index counter
                  example: "ct(slip77(master_key_hex),elsh(wpkh([fp/49'/1776'/0']xpub.../0/*)))"
      responses:
        "200":
          description: Descriptors stored, wallet connected
          content:
            application/json:
              schema:
                type: object
                properties:
                  connected:
                    type: boolean
                    example: true
        "400":
          description: Invalid descriptor format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ── Payments ──────────────────────────────────────────────────────────────────

  /api/v1/payments:
    post:
      summary: Create a payment
      description: |
        Creates a Boltz reverse swap and returns a Lightning invoice, Liquid BTC address,
        and (optionally) a Bitcoin on-chain address.

        **Hosted checkout:** redirect your buyer to `checkout_url` for a zero-integration checkout.

        **Headless:** render `lightning_invoice` and `liquid_address` (and optionally `btc_address`)
        in your own UI. Use `invoice_sats` for the Lightning amount display — it may differ from
        `amount_sats` when the order is below Boltz's minimum swap size.

        Rate limited to 30 requests per minute per API key.
      tags: [Payments]
      security:
        - SecretKey: []
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
            maxLength: 255
          description: |
            Client-generated key (e.g. UUID or order ID) to make this request idempotent.
            If the same key is sent twice with identical body, the cached response is returned
            with no duplicate swap created. If the body differs, the server returns `422`.
            Keys expire after 24 hours and can be freely reused after expiry.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount, currency]
              properties:
                amount:
                  type: number
                  description: Amount in fiat
                  example: 25.00
                currency:
                  type: string
                  description: ISO 4217 code
                  enum: [EUR, USD, GBP, CAD, AUD, JPY, CHF, DKK, BRL]
                  example: EUR
                redirect_url:
                  type: string
                  description: HTTPS URL to redirect buyer after payment on the hosted checkout. Must be https:, no credentials, ≤2048 chars.
                description:
                  type: string
                  maxLength: 500
                  description: Optional description shown on the invoice.
                metadata:
                  type: object
                  additionalProperties: true
                  description: Arbitrary key-value pairs (≤2 KB) stored with the payment and returned in webhooks.
            example:
              amount: 25.00
              currency: EUR
              redirect_url: https://yourstore.com/thank-you
              metadata:
                order_id: "ORD-1234"
      responses:
        "201":
          description: Payment created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
              example:
                payment_id: pmt_abc123
                status: pending
                amount_fiat: 25.00
                currency: EUR
                amount_sats: 12500
                invoice_sats: 12500
                methods: ["lightning", "liquid"]
                lightning_invoice: "lnbc125u1p..."
                liquid_address: "VJL4iV2MML..."
                btc_address: null
                btc_amount_sat: null
                checkout_url: "https://avopay.dev/pay/pmt_abc123"
                expires_at: "2026-06-19T12:10:00Z"
        "400":
          description: Validation error (unsupported currency, invalid redirect_url, amount too small, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "402":
          description: Wallet not connected, trial limit reached, or subscription inactive
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: "`request_in_flight` — same idempotency key is being processed concurrently"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: "`idempotency_key_reused_with_different_payload` — same key sent with a different request body"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/v1/payments/{id}:
    get:
      summary: Get payment status
      description: |
        Returns the full payment object including `created_at` and `settled_at`.
        Poll this endpoint or use webhooks to track payment status.
      tags: [Payments]
      security:
        - SecretKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: pmt_abc123
      responses:
        "200":
          description: Payment object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
              example:
                payment_id: pmt_abc123
                status: paid
                amount_fiat: 25.00
                currency: EUR
                amount_sats: 12500
                invoice_sats: 12500
                methods: ["lightning", "liquid"]
                lightning_invoice: "lnbc125u1p..."
                liquid_address: "VJL4iV2MML..."
                btc_address: null
                btc_amount_sat: null
                checkout_url: "https://avopay.dev/pay/pmt_abc123"
                created_at: "2026-06-19T12:00:00Z"
                settled_at: "2026-06-19T12:07:43Z"
        "404":
          description: Payment not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ── Payment Links ─────────────────────────────────────────────────────────────

  /api/v1/payment-links:
    post:
      summary: Create a payment link
      tags: [Payment Links]
      security:
        - SecretKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, currency]
              properties:
                title:
                  type: string
                amount:
                  type: number
                  nullable: true
                  description: Fixed amount, or omit for customer-specified
                currency:
                  type: string
                  enum: [EUR, USD, GBP, CAD, AUD, JPY, CHF, DKK, BRL]
                redirect_url:
                  type: string
                  description: HTTPS URL after payment
      responses:
        "201":
          description: Link created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentLink"

    get:
      summary: List payment links
      tags: [Payment Links]
      security:
        - SecretKey: []
      responses:
        "200":
          description: Array of payment links
          content:
            application/json:
              schema:
                type: object
                properties:
                  links:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaymentLink"

  /api/v1/payment-links/{id}:
    patch:
      summary: Update a payment link
      tags: [Payment Links]
      security:
        - SecretKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                amount:
                  type: number
                  nullable: true
                redirect_url:
                  type: string
                active:
                  type: boolean
      responses:
        "200":
          description: Updated link
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentLink"

    delete:
      summary: Delete a payment link
      tags: [Payment Links]
      security:
        - SecretKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean

  # ── Webhooks ──────────────────────────────────────────────────────────────────

  /api/v1/webhooks:
    post:
      summary: Register a webhook endpoint
      description: |
        AvoPay signs webhook POSTs with `X-AvoPay-Signature: sha256=HMAC`.
        Events: `payment.paid`, `payment.expired`.
      tags: [Webhooks]
      security:
        - SecretKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  description: HTTPS URL to receive POST notifications
      responses:
        "201":
          description: Webhook registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhook_secret:
                    type: string
                    description: Secret for verifying the X-AvoPay-Signature header

    get:
      summary: Get current webhook configuration
      tags: [Webhooks]
      security:
        - SecretKey: []
      responses:
        "200":
          description: Webhook config
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                  created_at:
                    type: string

    delete:
      summary: Remove webhook endpoint
      tags: [Webhooks]
      security:
        - SecretKey: []
      responses:
        "200":
          description: Removed

  /api/v1/webhooks/test:
    post:
      summary: Send a test webhook event
      tags: [Webhooks]
      security:
        - SecretKey: []
      responses:
        "200":
          description: Test event sent

  # ── Public Checkout ───────────────────────────────────────────────────────────

  /api/v1/checkout/{token}:
    get:
      summary: Get checkout data for payment page
      description: Called by the buyer's browser when loading `pay.html?token=...`. No auth.
      tags: [Public Checkout]
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Checkout data (invoice, addresses, amounts)
          content:
            application/json:
              schema:
                type: object

  /api/v1/checkout/{token}/status:
    post:
      summary: Poll checkout payment status
      description: Called by the buyer's browser. Returns `{status}` for polling.
      tags: [Public Checkout]
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [pending, paid, expired]

  # ── Health ────────────────────────────────────────────────────────────────────

  /health:
    get:
      summary: Health check
      tags: [System]
      responses:
        "200":
          description: API is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "503":
          description: Database unavailable

tags:
  - name: Agent Onboarding
    description: Programmatic account registration for AI agents — Lightning payment upfront, no trial
  - name: Payments
    description: Create and track payments
  - name: Payment Links
    description: Shareable payment links with optional fixed amounts
  - name: Webhooks
    description: Receive real-time payment notifications
  - name: Public Checkout
    description: Buyer-facing endpoints (no auth — called from browser)
  - name: System
    description: Health and metadata
