{
  "openapi": "3.1.0",
  "info": {
    "title": "Finality API",
    "version": "1.0.0",
    "summary": "100% non-custodial crypto payment gateway",
    "description": "Finality lets merchants accept stablecoin and native-coin payments that land directly in their own wallet. Funds never pass through Finality's accounts and Finality holds no private keys.\n\nBecause the platform is non-custodial it **cannot** freeze funds, reverse a settled payment, refund on a merchant's behalf, or arbitrate a chargeback. Refunds of settled payments are not a platform feature at all: that money is already in the merchant’s own wallet, so the merchant sends it back directly, on their own terms.\n\nSettlement terms are hashed into a per-payment address derivation and enforced on-chain, so the destination of funds cannot be altered by anyone, including Finality.\n\nSandbox versus production is decided by the API key prefix alone (`sk_test_` vs `sk_live_`), never by a request field.",
    "contact": {
      "name": "Finality docs",
      "url": "https://finality.money/docs"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://finality.money",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://finality.money/docs"
  },
  "tags": [
    {
      "name": "Payments",
      "description": "Create, retrieve, list and reconcile payments."
    },
    {
      "name": "Checkout",
      "description": "Customer-facing checkout endpoints. Mostly unauthenticated."
    },
    {
      "name": "Webhooks",
      "description": "Signed event delivery, replay and secret rotation."
    },
    {
      "name": "Withdrawals",
      "description": "Merchant-signed withdrawal of unsettled balances (overpayments, underpayments, expired orders). Refunds of settled payments are not a platform feature: those funds are already in the merchant’s own wallet."
    },
    {
      "name": "Reporting",
      "description": "Invoices and payment analytics."
    },
    {
      "name": "Subscriptions",
      "description": "Non-custodial recurring billing via customer-funded vaults."
    },
    {
      "name": "Utility",
      "description": "Prices, balances, health."
    },
    {
      "name": "x402",
      "description": "HTTP 402 machine-native payments for AI agents. Verify-only: Finality never broadcasts and never fronts gas."
    },
    {
      "name": "Affiliates",
      "description": "Built-in affiliate settlement: commission splits atomically inside the customer's payment, straight to the affiliate's wallet."
    },
    {
      "name": "Account",
      "description": "Merchant account, keys, audit, referral program."
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/v1/intents": {
      "post": {
        "operationId": "createPayment",
        "summary": "Create a payment",
        "description": "Creates a payment and returns a dedicated pay address plus a hosted checkout link.\n\nIdempotent on `merchant_order_id`: re-creating with an id you already used returns the existing payment rather than a second one.\n\nTwo modes:\n- **Fixed currency** — pass `chain` and `token`. The pay address is derived immediately.\n- **Multi-currency** — omit `token` (optionally constrain with `currencies`). The customer picks at checkout via `POST /v1/checkout/{id}/select`, which is when the quote locks and the address is derived.",
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              },
              "examples": {
                "fixed": {
                  "summary": "Fixed currency, USDC on Base",
                  "value": {
                    "chain": "base",
                    "token": "USDC",
                    "amount": "49.00",
                    "merchant_order_id": "order-8801"
                  }
                },
                "splits": {
                  "summary": "Split across recipients",
                  "value": {
                    "chain": "base",
                    "token": "USDC",
                    "amount": "1000.00",
                    "merchant_order_id": "mkt-2001",
                    "splits": [
                      {
                        "address": "0xSeller",
                        "bps": 8900,
                        "label": "seller"
                      },
                      {
                        "address": "0xAffiliate",
                        "bps": 1100,
                        "label": "affiliate"
                      }
                    ]
                  }
                },
                "multiCurrency": {
                  "summary": "Let the customer choose",
                  "value": {
                    "amount": "25.00",
                    "merchant_order_id": "order-8802",
                    "currencies": [
                      "USDC",
                      "SOL",
                      "ETH"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentIntent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Key scope insufficient (a `read_only` key cannot create payments)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/intents/{id}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Retrieve a payment",
        "description": "Returns the public view of a payment. Does **not** include the partial-payment fields — use `GET /v1/checkout/{id}` for `received_atomic` / `remaining_atomic` / `is_partial`.",
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "responses": {
          "200": {
            "description": "The payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentIntent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/merchant/intents": {
      "get": {
        "operationId": "listPayments",
        "summary": "List and search payments",
        "description": "Keyset pagination ordered by `(created_at, id)` descending — page 10,000 is as fast as page 1, and orders arriving mid-scan cannot cause duplicates or gaps.\n\n**Both cursor fields must be echoed back together.** Sending `cursor_id` without `cursor_created_at` is silently ignored and returns page one again.\n\nNote the deliberate asymmetry: `stats` responds only to `period` and excludes sandbox orders, while the `intents` list includes them.",
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "7d",
                "30d"
              ]
            },
            "description": "Anything else (or omitted) means all time. `today` means the last 24 hours, not the calendar day."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated, case-insensitive. e.g. `detected,confirmed`"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Substring match across merchant_order_id, pay address, detected tx, settle tx."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 5000
            }
          },
          {
            "name": "cursor_created_at",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "intents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentIntent"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "cursor_created_at": {
                          "type": "string"
                        },
                        "cursor_id": {
                          "type": "string"
                        }
                      }
                    },
                    "stats": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "settled": {
                          "type": "integer"
                        },
                        "settled_usd": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/merchant/intents/{id}/lifecycle": {
      "get": {
        "operationId": "getPaymentLifecycle",
        "summary": "Full history of one payment",
        "description": "Everything about a single order in one call: the order, every on-chain payment (including the real payer address), the settlement, and every webhook delivery attempt. This is the endpoint to open when a customer says they paid but nothing shipped.\n\nSub-queries degrade silently to empty arrays rather than erroring, so an empty `payments` array can mean either 'nothing arrived' or 'the lookup hiccuped'.",
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "responses": {
          "200": {
            "description": "Lifecycle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "intent": {
                      "$ref": "#/components/schemas/PaymentIntent"
                    },
                    "payments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OnChainPayment"
                      }
                    },
                    "settlement": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "webhooks": {
                      "type": "array",
                      "description": "Capped at 50, newest first.",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not your payment"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/checkout/{id}": {
      "get": {
        "operationId": "getCheckout",
        "summary": "Public checkout view",
        "description": "The only endpoint that exposes underpayment state. Under the exact-amount model settlement requires `received >= required`, so partial payments are a real and frequent case.",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "responses": {
          "200": {
            "description": "Checkout view",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaymentIntent"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "received_atomic": {
                          "type": "string",
                          "description": "Received so far, atomic units."
                        },
                        "remaining_atomic": {
                          "type": "string",
                          "description": "Still owed. \"0\" once satisfied."
                        },
                        "is_partial": {
                          "type": "boolean",
                          "description": "True only when something was paid but not enough."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/checkout/{id}/select": {
      "post": {
        "operationId": "selectCurrency",
        "summary": "Choose a currency (multi-currency mode)",
        "description": "Required when the payment was created without a `token`. Locks the quote, derives that token's pay address, and begins detection for it. Can be called again to change the choice.",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "example": "USDC"
                  },
                  "chain": {
                    "type": "string",
                    "example": "base"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote locked and address derived"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/checkout/{id}/pay-prepare": {
      "post": {
        "operationId": "preparePayment",
        "summary": "Build an unsigned transfer for a connected wallet",
        "description": "Returns an unsigned transaction or calldata that a connected wallet can sign. This is the 'connect wallet and pay' path for a self-hosted checkout. Customers can equally just send a plain transfer to the pay address.",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "description": "Payer wallet address."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned transaction. Shape varies by chain."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/v1/checkout/{id}/simulate-pay": {
      "post": {
        "operationId": "simulatePayment",
        "summary": "Force a sandbox payment to settle",
        "description": "**Sandbox only.** Pushes the payment to `settled` and fires a genuine `payment.settled` webhook carrying `livemode: false`. This is how you test your webhook handler end to end without moving real money. Only works for orders created with an `sk_test_` key.",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IntentId"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment forced to settled; webhook fired."
          },
          "400": {
            "description": "Not a sandbox order."
          }
        }
      }
    },
    "/v1/merchant/webhooks/{event_id}/replay": {
      "post": {
        "operationId": "replayWebhook",
        "summary": "Redeliver a webhook event",
        "description": "Use after an outage or a bad deploy. Find failed event ids via `GET /v1/merchant/webhooks` (dead-lettered events have `dead: true`). Requires a `full` scope key or a JWT.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "event_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Queued for redelivery"
          },
          "403": {
            "description": "Not your webhook event, or key scope insufficient"
          }
        }
      }
    },
    "/v1/merchant/webhooks": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook delivery records",
        "description": "Only `intent_id` and `limit` are supported — there is no paging cursor. Pull periodically if you need long-term history.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "intent_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery records",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/overpaid/prepare": {
      "post": {
        "operationId": "prepareOverpaidWithdrawal",
        "summary": "Withdraw an overpayment",
        "description": "Under the exact-amount model anything paid above the required amount stays at the payment address. Returns a **flat** unsigned transaction object — the transaction fields sit at the top level, with no `tx` wrapper.\n\nOn-chain, only `recipients[0]` (the merchant payout address) may withdraw. That is enforced by the contract and cannot be changed by anyone.",
        "tags": [
          "Withdrawals"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "intent_id"
                ],
                "properties": {
                  "intent_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned withdrawal transaction"
          },
          "403": {
            "description": "Key scope insufficient — requires `full`"
          }
        }
      }
    },
    "/v1/merchant/invoices/{intent_id}": {
      "get": {
        "operationId": "getInvoice",
        "summary": "Settlement receipt",
        "description": "A receipt for money already received, carrying the on-chain transaction hash as tamper-proof evidence — not a bill awaiting payment (the payment intent itself is the bill).",
        "tags": [
          "Reporting"
        ],
        "parameters": [
          {
            "name": "intent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice"
          },
          "409": {
            "description": "Not settled yet, or a sandbox order (sandbox orders get no real invoice number)"
          }
        }
      }
    },
    "/v1/merchant/analytics": {
      "get": {
        "operationId": "getAnalytics",
        "summary": "Revenue and customer analytics",
        "description": "Two complementary customer views: `top_customers` depends on you passing `metadata.client_reference_id` at creation, while `top_payers` is collected from on-chain sender addresses and is always populated even if you pass nothing.\n\n`by_chain`, `by_token` and `top_payers` exclude sandbox orders; `top_customers` does not. Database errors degrade to empty arrays rather than an error response.",
        "tags": [
          "Reporting"
        ],
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "7d",
                "30d"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "by_chain": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "by_token": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "top_customers": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "top_payers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "address": {
                            "type": "string"
                          },
                          "chain": {
                            "type": "string"
                          },
                          "orders": {
                            "type": "integer"
                          },
                          "usd": {
                            "type": "string"
                          },
                          "returning": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "daily_trend": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscriptions/plans": {
      "post": {
        "operationId": "createPlan",
        "summary": "Create a subscription plan",
        "description": "Returns a shareable `checkout_url`. Subscriptions are non-custodial: the customer pre-funds a vault whose address encodes the terms, and a permissionless keeper pulls each cycle. The keeper pays gas and can never redirect funds or change the amount; the customer can withdraw or cancel at any time.",
        "tags": [
          "Subscriptions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "amount",
                  "chain",
                  "token",
                  "cycle"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "string"
                  },
                  "pricing_mode": {
                    "type": "string",
                    "enum": [
                      "usd",
                      "native"
                    ],
                    "default": "usd"
                  },
                  "chain": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "cycle": {
                    "type": "string",
                    "description": "month / year / week / day, or a number of seconds (minimum 3600)."
                  },
                  "grace_seconds": {
                    "type": "integer"
                  },
                  "trial_seconds": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Plan created"
          }
        }
      },
      "get": {
        "operationId": "listPlans",
        "summary": "List subscription plans",
        "tags": [
          "Subscriptions"
        ],
        "responses": {
          "200": {
            "description": "Plans"
          }
        }
      }
    },
    "/v1/subscriptions/overview": {
      "get": {
        "operationId": "getSubscriptionOverview",
        "summary": "Subscription business overview",
        "description": "Aggregate view: MRR, active and at-risk counts, per-plan statistics with subscriber detail including each vault's balance and how many cycles it can still cover. Sandbox plans are excluded from revenue totals so they cannot pollute real figures.",
        "tags": [
          "Subscriptions"
        ],
        "responses": {
          "200": {
            "description": "Overview"
          }
        }
      }
    },
    "/v1/account/subscriptions/{id}/migrate-prepare": {
      "post": {
        "operationId": "prepareSubscriptionMigration",
        "summary": "Prepare an upgrade or downgrade",
        "description": "Because the subscription terms are hashed into the vault address, changing the price means a different address — an existing vault cannot become the new one. So a plan change is one atomic transaction that closes the old vault and opens the new one, signed once by the customer.\n\nProrated credit is computed as 'amount paid this period × seconds remaining ÷ cycle seconds', rounded down. Upgrades pay the difference; downgrades convert surplus credit into free cycles plus one transitional cycle.\n\n**The contract recomputes this and reverts on mismatch**, so the backend's arithmetic does not have to be trusted.\n\nConstraints: same chain and same token only; the old subscription must be active, past_due or confirming; the target plan must not be archived.",
        "tags": [
          "Subscriptions"
        ],
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "new_plan_id",
                  "owner"
                ],
                "properties": {
                  "new_plan_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "owner": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned migration transaction plus the computed proration"
          },
          "400": {
            "description": "Different chain or token"
          },
          "409": {
            "description": "Subscription is cancelled or expired"
          },
          "410": {
            "description": "Target plan is archived"
          }
        }
      }
    },
    "/v1/prices": {
      "get": {
        "operationId": "getPrices",
        "summary": "Live native-coin quotes and Solana rent constants",
        "description": "Use to estimate the token amount for a fiat price, and to show the one-time Solana rent cost when a customer's address needs a token account created.",
        "tags": [
          "Utility"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Prices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sol": {
                      "type": "number"
                    },
                    "eth": {
                      "type": "number"
                    },
                    "bnb": {
                      "type": "number"
                    },
                    "btc": {
                      "type": "number"
                    },
                    "solana_ata_rent_sol": {
                      "type": "number"
                    },
                    "solana_account_rent_sol": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/wallet-balances": {
      "get": {
        "operationId": "getWalletBalance",
        "summary": "Balance of any address",
        "description": "Check whether a wallet holds enough of a token (and enough native coin for gas) before asking the customer to pay. All three parameters are required.",
        "tags": [
          "Utility"
        ],
        "security": [],
        "parameters": [
          {
            "name": "chain",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Balance"
          },
          "400": {
            "description": "Missing a required parameter"
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Liveness probe",
        "tags": [
          "Utility"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up"
          }
        }
      }
    },
    "/v1/x402/{intent_id}": {
      "get": {
        "operationId": "x402Pay",
        "summary": "Pay an order as an AI agent (x402)",
        "tags": [
          "x402"
        ],
        "security": [],
        "description": "Speaks the [x402](https://www.x402.org/) protocol so an autonomous agent can pay without a browser or session.\n\nWithout a payment header this returns **402** with a `PAYMENT-REQUIRED` header (base64 `PaymentRequired`, x402Version 2) and the same JSON in the body. With a valid `PAYMENT-SIGNATURE` header it returns **200** plus a `PAYMENT-RESPONSE` header.\n\n**Settlement is payer-broadcast.** Finality verifies the EIP-3009 authorization but does not broadcast it — the agent broadcasts and pays its own gas. The order then settles through the same on-chain detection as a human payment, firing the same webhooks. Finality touches no funds, fronts no gas, and holds no signing keys.\n\n**Fully standard:** `payTo` is the order’s own derived address, so off-the-shelf clients (@x402/fetch, x402 for Python, the Go SDK) work unmodified — no custom `payerAddress` parameter needed.\n\nLive on Base (eip155:8453) and Ethereum (eip155:1), scheme `exact`. v1 `X-PAYMENT` headers are also accepted.",
        "parameters": [
          {
            "name": "intent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The payment intent to pay."
          },
          {
            "name": "PAYMENT-SIGNATURE",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Base64-encoded PaymentPayload containing the signed EIP-3009 authorization. Omit it to receive a quote."
          }
        ],
        "responses": {
          "200": {
            "description": "Authorization verified. Broadcast the signed transaction next.",
            "headers": {
              "PAYMENT-RESPONSE": {
                "schema": {
                  "type": "string"
                },
                "description": "Base64 SettlementResponse. transaction is empty because Finality does not broadcast."
              }
            }
          },
          "400": {
            "description": "Chain or token does not support x402 (for example a Binance-Peg token with no EIP-3009)."
          },
          "402": {
            "description": "Payment required, or the supplied authorization was rejected.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "Base64 PaymentRequired (the quote)."
              },
              "PAYMENT-RESPONSE": {
                "schema": {
                  "type": "string"
                },
                "description": "Present on rejection; carries errorReason."
              }
            }
          },
          "404": {
            "description": "Intent not found"
          },
          "409": {
            "description": "Intent already settled, expired, or otherwise terminal"
          }
        }
      }
    },
    "/v1/x402/supported": {
      "get": {
        "operationId": "x402Supported",
        "summary": "x402 capability discovery",
        "tags": [
          "x402"
        ],
        "security": [],
        "description": "Chains, scheme, and tokens currently usable over x402. Not hardcoded — the server reads each token’s name()/version() on-chain to confirm genuine EIP-3009 support, so tokens that cannot actually work never appear.",
        "responses": {
          "200": {
            "description": "Supported kinds"
          }
        }
      }
    },
    "/v1/merchant/intents/{id}/settle-retry": {
      "post": {
        "operationId": "retrySettle",
        "summary": "Manually retry settlement",
        "description": "Self-serve retry after the automatic retry window. Only meaningful for detected-but-unsettled payments.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Intent UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Retry accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/intents/{id}/withdrawals": {
      "get": {
        "operationId": "listWithdrawals",
        "summary": "Withdrawal history of one intent",
        "tags": [
          "Withdrawals"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Intent UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawal records",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/overpaid/confirm": {
      "post": {
        "operationId": "overpaidConfirm",
        "summary": "Confirm a broadcast overpaid-withdrawal",
        "description": "Called after the merchant signs and broadcasts the transaction built by /overpaid/prepare.",
        "tags": [
          "Withdrawals"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "intent_id": {
                    "type": "string"
                  },
                  "tx_hash": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/withdrawable": {
      "get": {
        "operationId": "listWithdrawable",
        "summary": "Withdrawable overpaid balances",
        "tags": [
          "Withdrawals"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Balances",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fin-events": {
      "get": {
        "operationId": "listFinEvents",
        "summary": "Recent on-chain events (REST poll)",
        "description": "Merchant-wide event feed. Kinds: payment_settled, subscription_created, subscription_charged, subscription_canceled, subscription_withdrawn, vault_topup. WS version: /v1/fin-events/ws?token=<JWT>.",
        "tags": [
          "Reporting"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Default 50"
          }
        ],
        "responses": {
          "200": {
            "description": "Events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/receipt/{intent_id}": {
      "get": {
        "operationId": "getPublicReceipt",
        "summary": "Public receipt (customer proof, no auth)",
        "description": "Anyone with the order UUID can view (UUIDs are not enumerable — Stripe receipt-link model). Excludes the merchant cost structure (net, fees, splits). 409 for unsettled or sandbox orders. Human-readable at /invoice/{intent_id}.",
        "tags": [
          "Payments"
        ],
        "security": [],
        "parameters": [
          {
            "name": "intent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Intent UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Receipt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Not settled yet, or sandbox order"
          }
        }
      }
    },
    "/v1/subscriptions": {
      "get": {
        "operationId": "listSubscriptions",
        "summary": "List subscriptions (renewal health)",
        "description": "Each row includes live vault balance and cycles remaining. ?client_reference_id= for exact lookup; ?limit= + cursor params for pagination (enveloped response when any pagination param is used).",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "client_reference_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscriptions/{id}": {
      "get": {
        "operationId": "getSubscription",
        "summary": "One subscription",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Subscription UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscriptions/plans/{id}": {
      "patch": {
        "operationId": "setPlanActive",
        "summary": "Archive / restore a plan",
        "description": "Archived plans stop accepting new subscribers; existing subscriptions keep charging.",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Plan UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "active"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscriptions/plans/{id}/upgrade-targets": {
      "patch": {
        "operationId": "setPlanUpgradeTargets",
        "summary": "Plan-change allowlist (required for switching)",
        "description": "By default no plan can be switched to. Only same-chain same-token plans are eligible. Without this, the customer portal's plan switcher stays empty.",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Plan UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "upgrade_to_plan_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "required": [
                  "upgrade_to_plan_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscriptions/plans/{id}/insights": {
      "get": {
        "operationId": "planInsights",
        "summary": "Per-plan funnel and breakdown",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Plan UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Insights",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/subscriptions/{id}/close-prepare": {
      "post": {
        "operationId": "closeSubPrepare",
        "summary": "One-click close (Solana): stop + withdraw all + refund rent deposit",
        "description": "Builds one atomic unsigned transaction: charging stops, the remaining balance AND the vault's rent deposit (~0.004 SOL) return to the owner. The deposit is always refunded — no exit fee. EVM vaults have no rent deposit; use withdraw + cancel. Cancel/close is irreversible: topping up afterwards does not reactivate.",
        "tags": [
          "Subscriptions"
        ],
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Subscription UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner": {
                    "type": "string"
                  }
                },
                "required": [
                  "owner"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned transaction",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliates": {
      "get": {
        "operationId": "listAffiliates",
        "summary": "List affiliates (links, stats, invite URLs)",
        "description": "Each affiliate includes referral links, performance (subscriptions brought / one-time sales / volume), and invite_url — send it to the affiliate so they bind their own payout addresses.",
        "tags": [
          "Affiliates"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Affiliates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAffiliate",
        "summary": "Register an affiliate",
        "tags": [
          "Affiliates"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "addresses": {
                    "type": "object",
                    "description": "Per-chain payout addresses; may be empty and bound later by the affiliate via their invite link"
                  },
                  "external_ref": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliates/{id}": {
      "patch": {
        "operationId": "setAffiliateActive",
        "summary": "Toggle an affiliate",
        "tags": [
          "Affiliates"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Affiliate UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "active"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliates/{id}/links": {
      "post": {
        "operationId": "createAffiliateLink",
        "summary": "Create a referral link",
        "description": "kind=subscription binds a plan (lands on /subscribe/{plan}?aff=CODE); kind=exact binds an amount template (lands on /pay?aff=CODE). bps = the affiliate's cut out of the merchant's 99%.",
        "tags": [
          "Affiliates"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Affiliate UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "subscription",
                      "exact"
                    ]
                  },
                  "plan_id": {
                    "type": "string"
                  },
                  "exact_template": {
                    "type": "object"
                  },
                  "bps": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9900
                  }
                },
                "required": [
                  "kind",
                  "bps"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link with full URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliate-links/{id}": {
      "patch": {
        "operationId": "setAffiliateLinkActive",
        "summary": "Toggle a referral link",
        "tags": [
          "Affiliates"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Link UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "active"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pay-link/{code}": {
      "post": {
        "operationId": "payLinkCheckout",
        "summary": "Create an order from an exact referral link (public)",
        "description": "Called by the /pay?aff=CODE landing page: creates the order from the link's template with the affiliate split injected. Orders expire in 30 minutes.",
        "tags": [
          "Affiliates"
        ],
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Referral code"
          }
        ],
        "responses": {
          "200": {
            "description": "Intent view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliate-portal/invite/{token}": {
      "get": {
        "operationId": "portalInviteInfo",
        "summary": "Affiliate invite preview (public; token is the credential)",
        "tags": [
          "Affiliates"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merchant name, affiliate name, current addresses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliate-portal/invite/{token}/bind": {
      "post": {
        "operationId": "portalBind",
        "summary": "Affiliate binds their own payout addresses (wallet-signed)",
        "description": "The signing wallet must be among the submitted addresses — it becomes the affiliate's sign-in identity. Address changes only affect future orders; existing subscriptions keep the snapshot taken at creation.",
        "tags": [
          "Affiliates"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  },
                  "addresses": {
                    "type": "object"
                  }
                },
                "required": [
                  "owner",
                  "message",
                  "signature",
                  "addresses"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bound",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/affiliate-portal/me": {
      "post": {
        "operationId": "portalMe",
        "summary": "Affiliate sign-in: all profiles under a wallet (cross-merchant)",
        "description": "Wallet-signature identity (same message template as /v1/account/subscriptions). Returns each merchant's profile with links and stats.",
        "tags": [
          "Affiliates"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  }
                },
                "required": [
                  "owner",
                  "message",
                  "signature"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profiles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/keys": {
      "get": {
        "operationId": "listKeys",
        "summary": "List API keys",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Keys (secrets retrievable from key_secret)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createKey",
        "summary": "Create an API key",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "full",
                      "payments_only",
                      "read_only"
                    ]
                  },
                  "is_test": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/config": {
      "get": {
        "operationId": "getConfig",
        "summary": "Merchant config",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateConfig",
        "summary": "Update config (PATCH, not POST/PUT)",
        "description": "Changing payout addresses requires the account owner plus step-up verification — use the dashboard for that.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhook_url": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/audit": {
      "get": {
        "operationId": "listAudit",
        "summary": "Audit log",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audit entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/payout-status": {
      "get": {
        "operationId": "payoutStatus",
        "summary": "Payout readiness per chain",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/referral": {
      "get": {
        "operationId": "referralOverview",
        "summary": "Merchant-referral overview (20% of platform fee, settled on-chain)",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Overview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/merchant/referral/earnings": {
      "get": {
        "operationId": "referralEarnings",
        "summary": "Referral earnings detail",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Earnings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/subscriptions": {
      "post": {
        "operationId": "accountSubscriptions",
        "summary": "Customer sign-in: all subscriptions under a wallet (cross-merchant)",
        "description": "Wallet-signature identity, no API key. Message template (byte-exact, blank lines included): 'Sign in to Finality\\n\\nWallet: <ADDR>\\nNonce: <32_HEX>\\nIssued At: <ISO8601>\\n\\nThis request will not trigger a transaction or cost any gas.' Valid 7 days, read-only.",
        "tags": [
          "Subscriptions"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  }
                },
                "required": [
                  "owner",
                  "message",
                  "signature"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer <key>`.\n\n- `sk_live_` — secret key, server-side, production\n- `sk_test_` — secret key, server-side, sandbox\n- `pk_live_` — publishable key, safe in a browser\n\nScopes: `full` (everything), `payments_only` (identical to full for creating payments; cannot withdraw balances), `read_only` (cannot create).\n\nNo API key of any scope can manage keys, manage the team, toggle 2FA, or change payout addresses — those require a logged-in JWT session, so a leaked key cannot escalate its own privileges."
      }
    },
    "parameters": {
      "IntentId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "The payment intent id."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid credentials",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Back off and retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error has this shape. There are no stable machine-readable error code strings — branch on the HTTP status, not on message text.",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "examples": [
          {
            "error": "coupon invalid: expired"
          }
        ]
      },
      "CreatePaymentRequest": {
        "type": "object",
        "required": [
          "amount",
          "merchant_order_id"
        ],
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "solana",
              "base",
              "bsc",
              "robin"
            ],
            "description": "Required in fixed-currency mode. In multi-currency mode the chain follows from the token the customer picks."
          },
          "token": {
            "type": "string",
            "description": "Pass it for fixed-currency mode. Omit it for multi-currency mode, where the customer chooses at checkout.",
            "examples": [
              "USDC",
              "USDT",
              "SOL",
              "ETH"
            ]
          },
          "currencies": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Multi-currency mode only: restrict which tokens the customer may choose. Omit for all live tokens."
          },
          "amount": {
            "type": "string",
            "description": "Decimal string in USD terms for stablecoins. Use a string to avoid float precision loss.",
            "examples": [
              "49.00"
            ]
          },
          "merchant_order_id": {
            "type": "string",
            "description": "Your order id. Unique per merchant, and the idempotency key — re-creating with the same id returns the same payment."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Overrides the account default for this payment."
          },
          "success_url": {
            "type": "string",
            "format": "uri",
            "description": "Customer returns here after paying, with intent_id and merchant_order_id appended. Passed by your server, so the customer cannot tamper with it."
          },
          "splits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SplitRecipient"
            },
            "description": "Multi-recipient payout. Recipients plus the platform fee sum to 10000 bps. If any leg fails the whole transaction reverts."
          },
          "expiry_secs": {
            "type": "integer",
            "default": 1800,
            "description": "Also the price-lock window for volatile assets."
          },
          "metadata": {
            "type": "object",
            "description": "Returned verbatim in webhooks. Put `client_reference_id` here to power customer analytics.",
            "additionalProperties": true
          }
        }
      },
      "SplitRecipient": {
        "type": "object",
        "required": [
          "address",
          "bps"
        ],
        "properties": {
          "address": {
            "type": "string"
          },
          "bps": {
            "type": "integer",
            "description": "Basis points. 8900 = 89%."
          },
          "label": {
            "type": "string"
          }
        }
      },
      "PaymentIntent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "merchant_order_id": {
            "type": "string"
          },
          "chain": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "USD-terms amount."
          },
          "amount_atomic": {
            "type": "string",
            "description": "Required amount in the token's smallest unit."
          },
          "pay_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "The dedicated address for this payment. Null in multi-currency mode until the customer selects a token."
          },
          "status": {
            "$ref": "#/components/schemas/PaymentStatus"
          },
          "splits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SplitRecipient"
            }
          },
          "detected_tx": {
            "type": [
              "string",
              "null"
            ]
          },
          "settle_tx": {
            "type": [
              "string",
              "null"
            ],
            "description": "On-chain proof of settlement."
          },
          "is_test": {
            "type": "boolean",
            "description": "True for sandbox orders (created with an sk_test_ key)."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "checkout_url": {
            "type": "string",
            "format": "uri"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaymentStatus": {
        "type": "string",
        "enum": [
          "created",
          "awaiting_currency",
          "pending",
          "detected",
          "confirmed",
          "settled",
          "expired",
          "failed"
        ],
        "description": "The complete set — there are exactly eight. The backend never emits a status called `detecting`; to find 'paid, awaiting confirmation' use `detected,confirmed`.\n\nOnly `settled` means the money has arrived and been split. Fulfil orders on `settled`, never on `detected` or `confirmed`."
      },
      "OnChainPayment": {
        "type": "object",
        "properties": {
          "chain": {
            "type": "string"
          },
          "tx_hash": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "amount_atomic": {
            "type": "string"
          },
          "sender": {
            "type": [
              "string",
              "null"
            ],
            "description": "The actual on-chain payer, read from the transaction."
          },
          "detected_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "intent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "event_type": {
            "type": "string"
          },
          "target_url": {
            "type": "string"
          },
          "delivered": {
            "type": "boolean"
          },
          "dead": {
            "type": "boolean",
            "description": "True once 16 attempts have failed. The event is not lost — replay it."
          },
          "attempts": {
            "type": "integer"
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "The POST body Finality sends to your endpoint.\n\nSignature: HMAC-SHA256 over the **raw body bytes**, hex-encoded, in `X-Finality-Signature`. Also sent: `X-Finality-Event-Id` (your idempotency key) and `X-Finality-Event-Type`. There is no timestamp header and no replay window — deduplicate on the event id, and verify against the exact bytes received rather than a re-serialized parse.\n\nDelivery: any 2xx is success. Up to 16 attempts with full-jitter exponential backoff from 1s to 1h (roughly 24–72 hours total), 10s per-attempt timeout, plus a per-merchant circuit breaker (5 consecutive failures pauses delivery for 60s).",
        "properties": {
          "event": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "intent_id": {
            "type": "string",
            "format": "uuid"
          },
          "merchant_order_id": {
            "type": "string"
          },
          "chain": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "amount": {
            "type": "string"
          },
          "livemode": {
            "type": "boolean",
            "description": "False for sandbox orders."
          },
          "settle_tx": {
            "type": "string"
          },
          "payer": {
            "type": [
              "string",
              "null"
            ],
            "description": "The real on-chain sender."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "payment.detected",
          "payment.partial",
          "payment.confirmed",
          "payment.settled",
          "payment.overpaid",
          "payment.blocked",
          "payment.late",
          "payment.expired",
          "payment.failed",
          "payment.refunded",
          "subscription.active",
          "subscription.charged",
          "subscription.insufficient_balance",
          "subscription.expired",
          "subscription.canceled",
          "subscription.trial_ending",
          "subscription.renewal_reminder"
        ],
        "description": "Fulfil an order on `payment.settled` or `subscription.charged` only.\n\nNever fulfil on: `payment.detected` (seen, not settled), `payment.confirmed`, `payment.partial` (underpaid), `payment.blocked` (sanctions screening hit — do not ship), `payment.late` (arrived after the order reached a terminal state). Note: payment.refunded is legacy and no longer emitted — the platform no longer records refunds. It remains listed so existing handlers that match on it stay valid."
      }
    }
  }
}
