{
  "openapi": "3.1.0",
  "info": {
    "title": "WavePay Public API",
    "version": "2.1.0",
    "description": "Checkout, licensing and settlement API for software. One POST registers a merchant and returns a live key and a test key; one POST starts a checkout. Local wallets (EcoCash, OneMoney, InnBucks, O'mari, ZimSwitch) and international rails (cards, PayPal) through one integration; subscriptions, buy-now-pay-later and refunds behind feature flags (GET /v2/features), all usable in test mode. Agents: skill.md is the canonical onboarding; POST /mcp speaks JSON-RPC tools.",
    "contact": {
      "email": "support@t3ratech.co.zw",
      "url": "https://wavepay.t3ratech.co.zw/docs.html"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://t3rnel-wavepay-production.t3ratech.workers.dev"
    }
  ],
  "security": [
    {
      "merchantKey": []
    }
  ],
  "paths": {
    "/v2/register": {
      "post": {
        "summary": "Register a merchant account — no key required — returns a live key and a test key",
        "security": [],
        "operationId": "registerMerchant",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "ownerEmail"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "ownerEmail": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ merchantId, key: mk_live_… shown once, planId, paymentsPerMonth }"
          },
          "409": {
            "description": "MERCHANT_ALREADY_REGISTERED"
          }
        }
      }
    },
    "/v2/providers": {
      "get": {
        "summary": "Enabled payment rails on this deployment",
        "security": [],
        "operationId": "listProviders",
        "responses": {
          "200": {
            "description": "{ providers: [{ id, label, methods }] }"
          }
        }
      }
    },
    "/v2/site/providers": {
      "get": {
        "summary": "Providers the public website may describe",
        "description": "Shaped by SITE_PROVIDER_VIEW: `real` (default) lists only providers that can take payments now; `all` lists every provider WavePay has built, each marked `live`. Not the product API — GET /v2/providers always reports the real set.",
        "security": [],
        "operationId": "listSiteProviders",
        "responses": {
          "200": {
            "description": "{ view: 'all'|'real', providers: [{ id, name, accepts, instruments, live }] }"
          }
        }
      }
    },
    "/v2/mail/unsubscribe": {
      "get": {
        "summary": "Unsubscribe confirmation page",
        "description": "Opened from the unsubscribe link in a bulk email. Shows a confirmation; it changes nothing, so a mail scanner opening the link cannot unsubscribe anyone. Query: h (recipient hash), t (token).",
        "security": [],
        "operationId": "unsubscribePage",
        "responses": {
          "200": {
            "description": "HTML confirmation page"
          },
          "400": {
            "description": "Link altered or incomplete"
          }
        }
      },
      "post": {
        "summary": "Unsubscribe (or resubscribe)",
        "description": "Performs the unsubscribe. Accepts the RFC 8058 one-click body `List-Unsubscribe=One-Click` that mail clients send from their own Unsubscribe button; `action=resubscribe` reverses it.",
        "security": [],
        "operationId": "unsubscribe",
        "responses": {
          "200": {
            "description": "HTML result page"
          },
          "400": {
            "description": "Link altered or incomplete"
          }
        }
      }
    },
    "/v2/internal/mailing/import": {
      "post": {
        "summary": "Import another product's registrants onto the mailing list",
        "description": "Global service key only. Body `{ source: 'market-pulse'|'admin', addresses: string[1..500] }`. Idempotent; an address that unsubscribed stays unsubscribed.",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "operationId": "importMailingAddresses",
        "responses": {
          "200": {
            "description": "{ received, added, existing, invalid }"
          },
          "400": {
            "description": "Invalid source or addresses"
          },
          "401": {
            "description": "Not the global service key"
          }
        }
      }
    },
    "/v2/pricing": {
      "get": {
        "summary": "Plans, prices and promotions for a product",
        "security": [],
        "operationId": "getPricing",
        "parameters": [
          {
            "name": "productId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ plans: [{ planId, priceMinor, currency, promotion }] }"
          }
        }
      }
    },
    "/v2/checkouts": {
      "post": {
        "summary": "Create a buyer-facing hosted checkout",
        "security": [],
        "operationId": "createCheckout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "productId",
                  "planId"
                ],
                "properties": {
                  "productId": {
                    "type": "string"
                  },
                  "planId": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "amountMinor": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{ reference: pay_…, checkoutUrl, provider, amountMinor, currency, test }"
          }
        }
      }
    },
    "/v2/checkouts/{reference}/status": {
      "get": {
        "summary": "Poll a checkout's settlement status",
        "security": [],
        "operationId": "checkoutStatus",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ reference, state, amountMinor, currency }"
          }
        }
      }
    },
    "/v2/licensing/checkout": {
      "post": {
        "summary": "Sell and email a licence key",
        "operationId": "licensingCheckout",
        "security": [],
        "responses": {
          "201": {
            "description": "Checkout created; key emailed on settle"
          }
        }
      }
    },
    "/v2/licensing/activate": {
      "post": {
        "summary": "Activate a licence key on a device",
        "operationId": "licensingActivate",
        "security": [],
        "responses": {
          "200": {
            "description": "{ entitlement } — signed, verifiable offline"
          }
        }
      }
    },
    "/v2/licensing/recover": {
      "post": {
        "summary": "Mail the buyer a fresh licence key",
        "operationId": "licensingRecover",
        "security": [],
        "responses": {
          "200": {
            "description": "Recovery email sent"
          }
        }
      }
    },
    "/v2/internal/checkouts": {
      "post": {
        "summary": "Create a server-side checkout (merchant-scoped)",
        "operationId": "internalCheckout",
        "parameters": [
          {
            "name": "idempotency-key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "productId",
                  "planId",
                  "provider"
                ],
                "properties": {
                  "productId": {
                    "type": "string"
                  },
                  "planId": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "externalSubject": {
                    "type": "string"
                  },
                  "amountMinor": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{ reference, checkoutUrl, provider, amountMinor, currency, test }"
          },
          "403": {
            "description": "MERCHANT_SCOPE — product not bound to this merchant"
          }
        }
      }
    },
    "/v2/internal/checkouts/status": {
      "post": {
        "summary": "Poll a checkout's status (merchant)",
        "operationId": "internalCheckoutStatus",
        "responses": {
          "200": {
            "description": "{ reference, state }"
          }
        }
      }
    },
    "/v2/internal/payments": {
      "post": {
        "summary": "Reconciliation ledger — idempotent drain with since/limit cursors",
        "operationId": "internalPayments",
        "responses": {
          "200": {
            "description": "{ payments: [{ reference, amountMinor, currency, settledAt }], cursor }"
          }
        }
      }
    },
    "/v2/internal/merchant/catalog": {
      "get": {
        "summary": "Everything this key can sell — sites, plans, live prices. The sign-in check.",
        "operationId": "merchantCatalog",
        "responses": {
          "200": {
            "description": "{ merchantId, sites: [{ siteId, products, plans }], key: { mode, planId } }"
          },
          "401": {
            "description": "key missing, wrong, or revoked — refused loudly"
          }
        }
      }
    },
    "/v2/internal/sites/webhook": {
      "post": {
        "summary": "Set or rotate a site's settlement webhook URL+secret (secret shown once)",
        "operationId": "setWebhook",
        "responses": {
          "200": {
            "description": "{ webhookUrl, secret }"
          }
        }
      }
    },
    "/v2/test/settle": {
      "post": {
        "summary": "Settle a test checkout end-to-end — exercises the webhook without touching a provider (mk_test_* keys only)",
        "operationId": "testSettle",
        "responses": {
          "200": {
            "description": "Settled in test mode"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "MCP endpoint — JSON-RPC 2.0 over HTTP. Tools: wavepay_register, wavepay_providers, wavepay_pricing, wavepay_checkout, wavepay_checkout_status, wavepay_features, wavepay_subscribe, wavepay_bnpl, wavepay_refund, wavepay_test_billing_run, wavepay_catalog",
        "operationId": "mcp",
        "security": [],
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          }
        }
      }
    },
    "/v2/features": {
      "get": {
        "summary": "Which flagged capabilities are on for live money (test keys can use all)",
        "operationId": "features",
        "responses": {
          "200": {
            "description": "{ features: [{flag, live, test}] }"
          }
        }
      }
    },
    "/v2/internal/subscriptions": {
      "post": {
        "summary": "Open a subscription (flag: recurring; test keys always)",
        "operationId": "createSubscription",
        "description": "automatic charges a saved Paynow card token or a PayPal subscription; invoice_and_remind raises a payment request each period and is the only mode for mobile wallets (they cannot be debited unattended).",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Stable per operation; a retry with the same key returns the same result, never a second one."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "productId": {
                    "type": "string"
                  },
                  "planId": {
                    "type": "string",
                    "description": "a time-limited plan with a price"
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "paynow",
                      "paynow-mobile",
                      "paypal"
                    ]
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "externalSubject": {
                    "type": "string",
                    "description": "your own customer id; owns the entitlement"
                  },
                  "collection": {
                    "type": "string",
                    "enum": [
                      "automatic",
                      "invoice_and_remind"
                    ],
                    "default": "invoice_and_remind"
                  },
                  "method": {
                    "type": "string"
                  }
                },
                "required": [
                  "productId",
                  "planId",
                  "provider",
                  "email",
                  "externalSubject"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      },
      "get": {
        "summary": "List this merchant's subscriptions",
        "operationId": "listSubscriptions",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/subscriptions/cancel": {
      "post": {
        "summary": "Cancel a subscription now or at period end",
        "operationId": "cancelSubscription",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "atPeriodEnd": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/bnpl": {
      "post": {
        "summary": "Start buy-now-pay-later (flag: bnpl; test keys always)",
        "operationId": "createBnpl",
        "description": "Deposit now, the balance collected from the card until the total is paid (1, 2, then 3-day back-off, no attempt limit). Cards only; prepaid and virtual cards are refused. WavePay collects; it does not lend.",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Stable per operation; a retry with the same key returns the same result, never a second one."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "productId": {
                    "type": "string"
                  },
                  "planId": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "paynow",
                      "paypal"
                    ]
                  },
                  "email": {
                    "type": "string"
                  },
                  "externalSubject": {
                    "type": "string"
                  },
                  "instalments": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 24
                  },
                  "depositMinor": {
                    "type": "integer"
                  },
                  "intervalDays": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  }
                },
                "required": [
                  "productId",
                  "planId",
                  "provider",
                  "email",
                  "externalSubject",
                  "instalments"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      },
      "get": {
        "summary": "List this merchant's BNPL plans",
        "operationId": "listBnpl",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/bnpl/cancel": {
      "post": {
        "summary": "Cancel a BNPL plan",
        "operationId": "cancelBnpl",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/payment-methods": {
      "get": {
        "summary": "Saved payment methods (never the token)",
        "operationId": "listPaymentMethods",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/refunds": {
      "post": {
        "summary": "Refund a settled payment (flag: refunds; test keys always)",
        "operationId": "createRefund",
        "description": "PayPal and test payments. PayNow documents no refund API: REFUND_UNSUPPORTED. Never more than was paid in total.",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Stable per operation; a retry with the same key returns the same result, never a second one."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reference": {
                    "type": "string"
                  },
                  "amountMinor": {
                    "type": "integer"
                  },
                  "reason": {
                    "type": "string"
                  },
                  "revokeAccess": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "reference"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/checkouts/otp": {
      "post": {
        "summary": "Complete an O'mari payment with the buyer's one-time PIN",
        "operationId": "submitOtp",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reference": {
                    "type": "string"
                  },
                  "otp": {
                    "type": "string"
                  }
                },
                "required": [
                  "reference",
                  "otp"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/paypal/disputes": {
      "get": {
        "summary": "PayPal disputes (operator key, flag: disputes)",
        "operationId": "paypalDisputes",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/test/billing/run": {
      "post": {
        "summary": "Run the billing engine as if advanceDays had passed (mk_test_ keys only)",
        "operationId": "testBillingRun",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "advanceDays": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 3650
                  }
                },
                "required": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED (flag off for live money) or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/test/pay/{reference}": {
      "get": {
        "summary": "Hosted page for a test checkout: Approve or Decline (test payments only)",
        "operationId": "testPayPage",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML"
          },
          "409": {
            "description": "NOT_A_TEST_PAYMENT"
          }
        }
      }
    },
    "/v2/internal/subscriptions/pause": {
      "post": {
        "summary": "Pause a subscription: keeps paid access, stops the next renewal (also at 2Checkout)",
        "operationId": "pauseSubscription",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/subscriptions/resume": {
      "post": {
        "summary": "Resume a paused subscription",
        "operationId": "resumeSubscription",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/internal/subscriptions/price": {
      "post": {
        "summary": "Change what the next renewals cost (also at 2Checkout)",
        "operationId": "setSubscriptionPrice",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "amountMinor": {
                    "type": "integer",
                    "minimum": 1
                  }
                },
                "required": [
                  "id",
                  "amountMinor"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "FEATURE_DISABLED or MERCHANT_SCOPE"
          }
        }
      }
    },
    "/v2/twocheckout/ipn": {
      "post": {
        "summary": "2Checkout IPN: signed order, renewal, refund and chargeback notification",
        "operationId": "twocheckoutIPN",
        "description": "HMAC-SHA256 signed by 2Checkout; answered with the signed read receipt. Not for merchants to call.",
        "responses": {
          "200": {
            "description": "Acknowledged"
          },
          "202": {
            "description": "Acknowledged, nothing to do"
          },
          "401": {
            "description": "PROVIDER_SIGNATURE_INVALID"
          }
        }
      },
      "get": {
        "summary": "2Checkout IPN endpoint liveness (2Checkout checks a URL with a GET when it is saved)",
        "operationId": "twocheckoutIPNLiveness",
        "responses": {
          "200": {
            "description": "{ok, endpoint}"
          }
        }
      }
    },
    "/v2/twocheckout/lcn": {
      "post": {
        "summary": "2Checkout LCN: signed subscription change notification",
        "operationId": "twocheckoutLCN",
        "description": "HMAC-SHA256 signed by 2Checkout; answered with the signed read receipt. Not for merchants to call.",
        "responses": {
          "200": {
            "description": "Acknowledged"
          },
          "202": {
            "description": "Acknowledged, nothing to do"
          },
          "401": {
            "description": "PROVIDER_SIGNATURE_INVALID"
          }
        }
      },
      "get": {
        "summary": "2Checkout LCN endpoint liveness (2Checkout checks a URL with a GET when it is saved)",
        "operationId": "twocheckoutLCNLiveness",
        "responses": {
          "200": {
            "description": "{ok, endpoint}"
          }
        }
      }
    },
    "/v2/internal/merchant/settlement": {
      "get": {
        "summary": "The provider accounts your key has connected (masked); the secret is never returned",
        "operationId": "listSettlementAccounts",
        "responses": {
          "200": {
            "description": "{ accounts: [{ provider, mode: 'own-keys', environment, account, status, connectedAt, verifiedAt }] }"
          },
          "403": {
            "description": "a global key, which has no merchant"
          }
        }
      }
    },
    "/v2/internal/merchant/settlement/{provider}": {
      "put": {
        "summary": "Connect your own provider account so the money lands with you (own keys). Credentials are checked with the provider, sealed, and never returned",
        "operationId": "connectSettlementAccount",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "paypal"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "clientId",
                  "clientSecret"
                ],
                "properties": {
                  "clientId": {
                    "type": "string"
                  },
                  "clientSecret": {
                    "type": "string"
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "sandbox",
                      "production"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ account: { provider, mode, environment, account, status, connectedAt, verifiedAt } }"
          },
          "400": {
            "description": "SETTLEMENT_PROVIDER_UNSUPPORTED or INVALID_REQUEST"
          },
          "403": {
            "description": "LIVE_KEY_REQUIRED (a test key cannot hold an account) or a global key"
          },
          "422": {
            "description": "CREDENTIALS_REJECTED (the provider refused them) or ENVIRONMENT_MISMATCH (sandbox keys on a production Worker, or the reverse)"
          }
        }
      },
      "delete": {
        "summary": "Disconnect the account; later payments settle through the deployment's account again",
        "operationId": "disconnectSettlementAccount",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "paypal"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ disconnected: boolean }"
          }
        }
      }
    },
    "/v2/price-list": {
      "get": {
        "summary": "WavePay's own price list: plans, the usage schedule and the modes (same data as /pricing.json)",
        "operationId": "priceList",
        "responses": {
          "200": {
            "description": "{ currency, plans: [{ id, name, monthlyMinor, annualMinor, includedPayments, sites }], usage: { tiers }, providerFees, modes }"
          }
        }
      }
    },
    "/v2/internal/merchant/usage": {
      "get": {
        "summary": "This month's usage meter for your key: payments, included allowance, usage fee by tier, total due",
        "operationId": "merchantUsage",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "2026-10"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ month, plan, payments, includedPayments, nonUsdPayments, usage: { feeMinor, chargedPayments, byTier }, totalDueMinor }"
          },
          "403": {
            "description": "a global key, which has no merchant"
          }
        }
      }
    },
    "/v2/crypto/rails": {
      "get": {
        "summary": "The crypto rails WavePay takes (Bitcoin, Ethereum, Solana, BNB, Litecoin, Dogecoin, Tron, USDT, USDC) and which can be quoted right now",
        "operationId": "cryptoRails",
        "responses": {
          "200": {
            "description": "{ rails: [{ id, symbol, network, address, confirmations, quotable }] }"
          }
        }
      }
    },
    "/v2/internal/crypto/rates": {
      "post": {
        "summary": "Global key: store coin prices in US dollars (a checkout refuses a rate older than ten minutes)",
        "operationId": "cryptoRates",
        "responses": {
          "200": {
            "description": "{ ok, coins }"
          },
          "400": {
            "description": "unknown coin or a price that is not positive"
          }
        }
      }
    },
    "/v2/internal/crypto/pending": {
      "post": {
        "summary": "Global key: open crypto payments with rail, exact base-unit amount and address — what the watcher looks for",
        "operationId": "cryptoPending",
        "responses": {
          "200": {
            "description": "{ payments: [{ reference, rail, baseUnits, address, createdAt }] }"
          }
        }
      }
    },
    "/v2/internal/crypto/settle": {
      "post": {
        "summary": "Global key: settle a payment from a deposit the exchange credited. Re-verified: rail, address, exact amount, and the transaction never settled a payment before",
        "operationId": "cryptoSettle",
        "responses": {
          "200": {
            "description": "the payment's status"
          },
          "422": {
            "description": "PAYMENT_MISMATCH — not the deposit that was asked for"
          },
          "409": {
            "description": "TRANSFER_ALREADY_USED"
          }
        }
      }
    },
    "/v2/internal/merchant/invoices": {
      "get": {
        "summary": "Your usage invoices (month, amount, status)",
        "operationId": "merchantInvoices",
        "responses": {
          "200": {
            "description": "{ invoices: [{ id, month, planId, payments, chargedPayments, amountMinor, status, createdAt, dueAt, paidAt }] }"
          },
          "403": {
            "description": "a global key, which has no merchant"
          }
        }
      }
    },
    "/v2/internal/invoices/run": {
      "post": {
        "summary": "Global key: invoice a month's usage now (default last month); dryRun lists who would be invoiced. Idempotent",
        "operationId": "runInvoices",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "month": {
                    "type": "string"
                  },
                  "dryRun": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ month, created: [{ merchantId, invoiceId, amountMinor, emailed }], skipped }"
          }
        }
      }
    },
    "/v2/invoices/{id}/pay/{provider}": {
      "get": {
        "summary": "The emailed pay link for a usage invoice: needs its token (?t=), opens a checkout for the invoice amount and redirects to it",
        "operationId": "payInvoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "paynow",
                "paypal"
              ]
            }
          },
          {
            "name": "t",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "redirect to the provider's checkout"
          },
          "401": {
            "description": "wrong token"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "merchantKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-wavepay-service-key",
        "description": "mk_live_… / mk_test_… — minted by POST /v2/register, SHA-256 stored, shown once"
      }
    }
  }
}
