{"openapi":"3.1.0","info":{"title":"OpenSettle API","version":"1.0.0","description":"Non-custodial Web3 payments API. Stablecoin (USDC, USDT) settlement on the EVM chains Base, Ethereum, Polygon, and Arbitrum. Solana and Tron are implemented at the API + wallet-verification layer (`/wallets` accepts `chain: \"solana\" | \"tron\"`) and the chain reader will detect inbound SPL / TRC-20 deposits to verified wallets, but customer-facing hosted checkout is currently EVM-only. Authenticate with a workspace-scoped Bearer API key (`sk_live_...` or `sk_test_...`). All money amounts are minor units (cents). All times are ISO-8601 UTC strings.","contact":{"name":"OpenSettle","url":"https://opensettle.io"}},"servers":[{"url":"https://api.opensettle.io"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"customers","description":"Customers attached to a workspace"},{"name":"products","description":"Products + recurring/one-time prices"},{"name":"subscriptions","description":"Recurring billing"},{"name":"invoices","description":"One-off + subscription invoices"},{"name":"payments","description":"On-chain payment records + refunds"},{"name":"webhooks","description":"Endpoint registration + event replay"},{"name":"checkouts","description":"Hosted payment sessions"},{"name":"payment_links","description":"Reusable, shareable payment links"},{"name":"wallets","description":"Merchant settlement wallets. NOTE: these are managed in the OpenSettle dashboard and authenticate with a dashboard SESSION, not an API key — calling them with an `sk_live_`/`sk_test_` Bearer token returns 401. Add and verify your settlement wallets in the dashboard before going live; the shapes are documented here for reference."},{"name":"health","description":"Liveness + readiness probes"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_{live|test}_…","description":"Workspace-scoped API key. The key's scope determines which workspace it operates against; the URL `:workspaceId` must match. Permissions: `readonly` → viewer, `restricted` → developer, `full` → admin."}},"schemas":{"Error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"type":"string","enum":["invalid_request","unauthorized","forbidden","not_found","conflict","invalid_state_transition","rate_limited","aal_required","restricted_jurisdiction","kyb_required","chain_reverted","insufficient_confirmations","signing_required","attestation_required","internal_error"],"example":"invalid_request"},"message":{"type":"string","example":"Idempotency-Key header is required on this endpoint"},"param":{"type":"string","nullable":true,"example":"Idempotency-Key"},"request_id":{"type":"string","example":"req_01HZ…"},"metadata":{"type":"object","additionalProperties":true,"nullable":true,"description":"Code-specific extras. `restricted_jurisdiction` carries `{ code, name, reason }`; `kyb_required` carries `{ kybStatus }`; `attestation_required` carries `{ category, requiredAge }`."}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/Error"}}},"Customer":{"type":"object","properties":{"id":{"type":"string","example":"cus_01HZ…"},"workspaceId":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string","nullable":true},"wallet":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","at_risk","churned"]},"createdAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Payment":{"type":"object","properties":{"id":{"type":"string","example":"pay_01HZ…"},"workspaceId":{"type":"string"},"customerId":{"type":"string","nullable":true},"subscriptionId":{"type":"string","nullable":true},"invoiceId":{"type":"string","nullable":true},"checkoutId":{"type":"string","nullable":true},"walletId":{"type":"string","nullable":true},"amountMinor":{"type":"integer","description":"USD cents"},"feeMinor":{"type":"integer"},"netMinor":{"type":"integer"},"currency":{"type":"string","example":"USD"},"token":{"type":"string","enum":["USDC","USDT"]},"chain":{"type":"string","enum":["base","ethereum","polygon","arbitrum","solana","tron"]},"status":{"type":"string","enum":["pending","confirmed","failed","refunded","reorged"]},"failureReason":{"type":"string","nullable":true,"description":"Set when status is `failed`."},"description":{"type":"string","nullable":true},"txHash":{"type":"string","nullable":true,"description":"On-chain settlement tx hash. Null until observed — never substitute the payment id."},"blockNumber":{"type":"integer","nullable":true},"confirmations":{"type":"integer"},"refundTxHash":{"type":"string","nullable":true,"description":"Merchant-signed refund transaction hash."},"refundAmountMinor":{"type":"integer","nullable":true,"description":"Amount refunded to the payer, in minor units (supports partial refunds)."},"refundRecipient":{"type":"string","nullable":true,"description":"Address the refund is/was sent to."},"refundBroadcastAt":{"type":"string","format":"date-time","nullable":true,"description":"When the merchant reported broadcasting the refund tx."},"refundedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the refund confirmed on-chain (status → refunded)."},"refundReason":{"type":"string","nullable":true},"screeningVerdict":{"type":"string","enum":["not_screened","screened_clean","screened_flagged","screen_error"],"description":"Sanctions-screening verdict for the payer address. Default `not_screened` until a screening provider is wired. The `screened_flagged` rows are the ops triage queue."},"screeningProvider":{"type":"string","nullable":true},"screeningScreenedAt":{"type":"string","format":"date-time","nullable":true},"tokenAmountBase":{"type":"string","nullable":true,"description":"Exact on-chain Transfer value in token base units (string bigint)."},"unmatchedInbound":{"type":"boolean","description":"True when an inbound transfer matched no pending checkout — credit manually or refund. Pair with closeMatchCheckoutId."},"closeMatchCheckoutId":{"type":"string","nullable":true,"description":"A pending checkout whose target was within ±5% of an unmatched inbound transfer — the buyer's likely intent."},"expectedTokenAmountBase":{"type":"string","nullable":true,"description":"The intended checkout target in base units (set on a close-match)."},"receivedTokenAmountBase":{"type":"string","nullable":true,"description":"What the buyer actually sent in base units (set on a close-match)."},"reorgSuspected":{"type":"boolean","description":"True when the reorg-afterglow sweep flagged that the canonical chain no longer agrees with the persisted block hash. Status stays `confirmed`; pairs with the `payment.reversed` webhook."},"confirmedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"url":{"type":"string","format":"uri"},"description":{"type":"string","nullable":true},"events":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["enabled","disabled"]},"successRate":{"type":"number"},"createdAt":{"type":"string","format":"date-time"}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"eventId":{"type":"string"},"eventType":{"type":"string"},"status":{"type":"string","enum":["pending","delivering","succeeded","failed","dead"]},"attemptCount":{"type":"integer"},"httpStatus":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"latencyMs":{"type":"integer","nullable":true},"deliveredAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"PaginatedList":{"type":"object","properties":{"data":{"type":"array","items":{}},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true}}},"ChainId":{"type":"string","enum":["base","ethereum","polygon","arbitrum","solana","tron"]},"TokenSymbol":{"type":"string","enum":["USDC","USDT"]},"Product":{"type":"object","properties":{"id":{"type":"string","example":"prod_01HZ…"},"workspaceId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"active":{"type":"boolean"},"metadata":{"type":"object","nullable":true,"additionalProperties":true},"createdAt":{"type":"string","format":"date-time"}}},"Price":{"type":"object","properties":{"id":{"type":"string","example":"price_01HZ…"},"workspaceId":{"type":"string"},"productId":{"type":"string"},"amount":{"type":"integer","description":"Minor units (cents)"},"currency":{"type":"string","example":"USD"},"interval":{"type":"string","enum":["one_time","minute","hour","week","month","year"]},"active":{"type":"boolean"},"metadata":{"type":"object","nullable":true,"additionalProperties":true},"createdAt":{"type":"string","format":"date-time"}}},"LineItem":{"type":"object","required":["description","quantity","unitAmountMinor"],"properties":{"description":{"type":"string","maxLength":200},"quantity":{"type":"integer","minimum":1},"unitAmountMinor":{"type":"integer"}}},"Invoice":{"type":"object","properties":{"id":{"type":"string","example":"in_01HZ…"},"workspaceId":{"type":"string"},"number":{"type":"string","description":"Human-readable, monotonic per workspace"},"customerId":{"type":"string"},"subscriptionId":{"type":"string","nullable":true},"amountMinor":{"type":"integer"},"currency":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"status":{"type":"string","enum":["draft","open","paid","past_due","void"]},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}},"memo":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time","nullable":true},"paidAt":{"type":"string","format":"date-time","nullable":true}}},"Subscription":{"type":"object","properties":{"id":{"type":"string","example":"sub_01HZ…"},"workspaceId":{"type":"string"},"customerId":{"type":"string"},"productId":{"type":"string"},"priceId":{"type":"string"},"amountMinor":{"type":"integer"},"currency":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"status":{"type":"string","enum":["trialing","active","past_due","paused","canceled"]},"autopay":{"type":"string","enum":["allowance","smart-wallet","manual"]},"allowanceTx":{"type":"string","nullable":true},"allowanceRemaining":{"type":"integer","nullable":true},"trialEndsAt":{"type":"string","format":"date-time","nullable":true},"startedAt":{"type":"string","format":"date-time"},"currentPeriodEnd":{"type":"string","format":"date-time"},"nextBillingDate":{"type":"string","format":"date-time"},"canceledAt":{"type":"string","format":"date-time","nullable":true},"cancelReason":{"type":"string","nullable":true},"pausedAt":{"type":"string","format":"date-time","nullable":true},"mrrMinor":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"Checkout":{"type":"object","properties":{"id":{"type":"string","example":"chk_01HZ…"},"workspaceId":{"type":"string"},"mode":{"type":"string","enum":["payment","subscription"]},"status":{"type":"string","enum":["open","pending","succeeded","failed","expired"]},"customerId":{"type":"string","nullable":true},"invoiceId":{"type":"string","nullable":true},"priceId":{"type":"string","nullable":true},"amountMinor":{"type":"integer"},"currency":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"description":{"type":"string","nullable":true},"successUrl":{"type":"string","description":"Buyer return URL, or \"\" when none was set (the hosted success page then shows no redirect)."},"cancelUrl":{"type":"string","format":"uri","nullable":true},"expiresAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true},"metadata":{"type":"object","additionalProperties":true,"nullable":true},"createdAt":{"type":"string","format":"date-time"},"hostedUrl":{"type":"string","description":"Absolute buyer-facing URL (e.g. `https://opensettle.io/checkout/<hostedToken>`). Redirect the buyer straight to it — do NOT prepend an origin. Uses the unguessable hosted-token, not the timestamp-prefixed `id`."}}},"PaymentLink":{"type":"object","description":"A reusable, shareable payment link. Each visit to its `url` spawns a fresh checkout, so one link can collect many payments. Back it with a fixed `amount`, a saved one-time `priceId`, or an `openAmount` the buyer names (\"name your price\" / top-up).","properties":{"id":{"type":"string","example":"plink_01HZ…"},"url":{"type":"string","description":"Absolute, reusable buyer-facing URL (e.g. `https://opensettle.io/pay/<token>`). Share or embed it directly — do NOT prepend an origin."},"description":{"type":"string"},"priceId":{"type":"string","nullable":true},"amountMinor":{"type":"integer","description":"Fixed charge in minor units. `0` when `openAmount` is true."},"openAmount":{"type":"boolean","description":"When true the buyer chooses the amount on the hosted page (\"name your price\" / top-up), bounded by minAmountMinor/maxAmountMinor."},"minAmountMinor":{"type":"integer","nullable":true,"description":"Effective floor for an open-amount link (defaults to $0.50 when unset)."},"maxAmountMinor":{"type":"integer","nullable":true,"description":"Optional ceiling for an open-amount link."},"presetAmounts":{"type":"array","nullable":true,"items":{"type":"integer"},"description":"Optional quick-pick chips (each within [min, max])."},"currency":{"type":"string","example":"USD"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"successUrl":{"type":"string"},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Wallet":{"type":"object","properties":{"id":{"type":"string","example":"wlt_01HZ…"},"workspaceId":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"address":{"type":"string","description":"EIP-55 checksum for EVM, base58 32-byte for Solana, base58check `T…` for Tron."},"nickname":{"type":"string"},"verified":{"type":"boolean"},"verifiedAt":{"type":"string","format":"date-time","nullable":true},"isDefault":{"type":"boolean"},"balance":{"type":"integer","description":"Cached, may be stale"},"createdAt":{"type":"string","format":"date-time"}}},"WalletChallenge":{"type":"object","required":["id","message","expiresAt"],"properties":{"id":{"type":"string","example":"wch_01HZ…"},"message":{"type":"string","description":"Multi-line UTF-8 challenge. Wallet must sign these exact bytes; tampering rotates the verifier into rejection."},"expiresAt":{"type":"string","format":"date-time"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Authenticated but lacks required role / workspace scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource doesn't exist or isn't visible in this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Too many requests — see `Retry-After` header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds"}}},"BadRequest":{"description":"Validation failure (`code: invalid_request`) — `param` points at the offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Conflict":{"description":"State conflict (`code: conflict`) — usually a uniqueness collision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InvalidStateTransition":{"description":"Resource is in a state where the requested operation isn't legal (`code: invalid_state_transition`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"AalRequired":{"description":"Step-up required (`code: aal_required`). Re-authenticate with a recent factor (within 5 min) or upgrade the session to AAL=2 with a passkey assertion, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":{"WorkspaceId":{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Required on every money-adjacent POST. Replaying with the same key returns the original response byte-for-byte. Different body + same key → 409."}}},"paths":{"/v1/healthz":{"get":{"tags":["health"],"summary":"Liveness probe (public)","security":[],"responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/v1/readyz":{"get":{"tags":["health"],"summary":"Readiness probe (public)","description":"Verifies DB connectivity. Used by Fly health checks. Body intentionally omits the session count value to avoid leaking deployment volume to public probes.","security":[],"responses":{"200":{"description":"Healthy","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"db":{"type":"string","enum":["ok"]},"checks":{"type":"object"},"elapsed_ms":{"type":"integer"}}}}}},"503":{"description":"DB unreachable"}}}},"/v1/workspaces/{workspaceId}/customers":{"get":{"tags":["customers"],"summary":"List customers","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","at_risk","churned"]}},{"name":"q","in":"query","schema":{"type":"string","maxLength":120},"description":"Substring match on email or name"},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Page of customers","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["customers"],"summary":"Create a customer","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":254},"name":{"type":"string","maxLength":120},"wallet":{"type":"string","maxLength":100},"country":{"type":"string","minLength":2,"maxLength":2},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Customer created","content":{"application/json":{"schema":{"type":"object","properties":{"customer":{"$ref":"#/components/schemas/Customer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Customer with that email already exists"}}}},"/v1/workspaces/{workspaceId}/customers/{customerId}":{"get":{"tags":["customers"],"summary":"Retrieve a customer","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"customerId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["customers"],"summary":"Update a customer","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"customerId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"wallet":{"type":"string","maxLength":100,"nullable":true},"country":{"type":"string","minLength":2,"maxLength":2,"nullable":true},"metadata":{"type":"object","additionalProperties":true,"nullable":true}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["customers"],"summary":"Soft-delete a customer (PII scrubbed; references preserved)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"customerId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/products":{"get":{"tags":["products"],"summary":"List products","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"active","in":"query","schema":{"type":"boolean"}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Page of products","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}}]}}}}}},"post":{"tags":["products"],"summary":"Create a product","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":1000},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/v1/workspaces/{workspaceId}/products/{productId}":{"get":{"tags":["products"],"summary":"Retrieve a product","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["products"],"summary":"Update a product","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":1000,"nullable":true},"active":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true,"nullable":true}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}}}},"delete":{"tags":["products"],"summary":"Hard-delete a product","description":"Returns 409 if any subscription still references the product (or any of its prices). Cascades to delete the product's prices.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Product still referenced by a subscription"}}}},"/v1/workspaces/{workspaceId}/products/{productId}/prices":{"get":{"tags":["products"],"summary":"List prices for a product","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Prices","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Price"}}}}}}}}},"post":{"tags":["products"],"summary":"Create a price for a product","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","interval"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Minor units (cents)"},"currency":{"type":"string","minLength":3,"maxLength":3,"default":"USD","example":"USD"},"interval":{"type":"string","enum":["one_time","minute","hour","week","month","year"]},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Price"}}}}}}},"/v1/workspaces/{workspaceId}/prices/{priceId}":{"patch":{"tags":["products"],"summary":"Update a price (toggle active flag, edit metadata)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"priceId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true,"nullable":true}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Price"}}}}}},"delete":{"tags":["products"],"summary":"Hard-delete a price","description":"Returns 409 if any subscription still references this price.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"priceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Price still referenced by a subscription"}}}},"/v1/workspaces/{workspaceId}/invoices":{"get":{"tags":["invoices"],"summary":"List invoices","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"customerId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","open","paid","past_due","void"]}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Page of invoices","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}}}}]}}}}}},"post":{"tags":["invoices"],"summary":"Create an invoice","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customerId","chain","token","lineItems"],"description":"The invoice total is derived from `lineItems[].quantity * unitAmountMinor`; there is no top-level amount field.","properties":{"customerId":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"currency":{"type":"string","minLength":3,"maxLength":3,"default":"USD","example":"USD"},"lineItems":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/LineItem"}},"memo":{"type":"string","maxLength":1000},"dueInDays":{"type":"integer","minimum":0,"maximum":365,"default":14,"description":"Days from creation until the invoice is due."},"subscriptionId":{"type":"string","description":"Set when the invoice is generated for a subscription period."},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/v1/workspaces/{workspaceId}/invoices/{invoiceId}":{"get":{"tags":["invoices"],"summary":"Retrieve an invoice","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"invoiceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The invoice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/invoices/{invoiceId}/send":{"post":{"tags":["invoices"],"summary":"Email the hosted invoice link to the customer","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"invoiceId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Email queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/invoices/{invoiceId}/reminder":{"post":{"tags":["invoices"],"summary":"Send a reminder for an unpaid invoice","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"invoiceId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Reminder queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}}}}},"/v1/workspaces/{workspaceId}/invoices/{invoiceId}/void":{"post":{"tags":["invoices"],"summary":"Void an invoice (terminal state)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"invoiceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Voided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/checkouts":{"post":{"tags":["checkouts"],"summary":"Create a hosted checkout session","description":"Returns a session with an absolute `hostedUrl`; redirect the customer straight to it (uses an unguessable 192-bit `hostedToken`, not the merchant-facing `id`). Mode `payment` requires `invoiceId`; mode `subscription` requires `priceId`.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"description":"Customer: supply either `customerId` (existing) or `customerEmail` (find-or-create). For mode `payment` the customer is OPTIONAL — omit both to create a GUEST checkout (scan-and-pay, no email), e.g. a reusable payment link; the response `customerId` is then null. Mode `subscription` always requires a customer. Mode `payment` takes exactly one of `invoiceId`, a one-time `priceId`, or an ad-hoc `amount` (the latter two also need a chain + token); mode `subscription` requires a recurring `priceId` and a chain/token.","properties":{"mode":{"type":"string","enum":["payment","subscription"]},"customerId":{"type":"string"},"customerEmail":{"type":"string","format":"email"},"customerName":{"type":"string","maxLength":200},"invoiceId":{"type":"string"},"priceId":{"type":"string"},"amount":{"type":"integer","minimum":1,"description":"Ad-hoc one-time charge in MINOR units (cents). mode=payment only — alternative to invoiceId / a one-time priceId."},"currency":{"type":"string","minLength":3,"maxLength":3,"description":"Currency for an ad-hoc `amount`. Defaults to USD."},"description":{"type":"string","maxLength":500,"description":"Buyer-facing description for an ad-hoc `amount` checkout."},"successUrl":{"type":"string","format":"uri"},"cancelUrl":{"type":"string","format":"uri"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"expiresInMinutes":{"type":"integer","minimum":1,"maximum":1440,"default":30},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/v1/workspaces/{workspaceId}/checkouts/{id}":{"get":{"tags":["checkouts"],"summary":"Retrieve a checkout session","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/payment_links":{"post":{"tags":["payment_links"],"summary":"Create a reusable payment link","description":"A shareable link whose `url` (`https://opensettle.io/pay/<token>`) spawns a fresh checkout on every visit — built for scan-and-pay, embeds, or \"pay what you want\". Supply exactly ONE amount source: a fixed `amount`, a saved one-time `priceId`, or `openAmount: true` to let the buyer name the amount (bounded by `minAmount`/`maxAmount`). Buyers check out as guests — no email required. The 201 body wraps the resource as `{ \"paymentLink\": PaymentLink }`. Requires the `developer` role.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chain","token"],"description":"Exactly one amount source: `amount` (fixed), `priceId` (saved one-time price), or `openAmount: true`. `description` is required for a fixed `amount` or an open-amount link.","properties":{"amount":{"type":"integer","minimum":1,"description":"Fixed charge in MINOR units (cents)."},"priceId":{"type":"string","description":"A saved one-time price to charge."},"openAmount":{"type":"boolean","description":"Let the buyer choose the amount (\"name your price\" / top-up)."},"minAmount":{"type":"integer","minimum":1,"description":"Floor for an open-amount link (defaults to 50 minor units / $0.50)."},"maxAmount":{"type":"integer","minimum":1,"description":"Optional ceiling for an open-amount link."},"presetAmounts":{"type":"array","items":{"type":"integer","minimum":1},"minItems":1,"maxItems":8,"description":"Optional quick-pick chips for an open-amount link (each within [min, max])."},"description":{"type":"string","minLength":1,"maxLength":200},"currency":{"type":"string","minLength":3,"maxLength":3,"description":"Defaults to USD; must match the token's peg."},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"successUrl":{"type":"string","format":"uri"},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Link created (wrapped as `{ paymentLink }`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}},"get":{"tags":["payment_links"],"summary":"List payment links","parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"Payment links for the workspace (`{ data: PaymentLink[] }`)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentLink"}}}}}}}}}},"/v1/workspaces/{workspaceId}/payment_links/{id}":{"delete":{"tags":["payment_links"],"summary":"Deactivate a payment link","description":"Soft-deactivates the link so its `url` stops spawning new checkouts. Checkouts already spawned from it are unaffected. Requires the `developer` role.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deactivated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/subscriptions":{"get":{"tags":["subscriptions"],"summary":"List subscriptions","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"customerId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["trialing","active","past_due","paused","canceled"]}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Page of subscriptions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}]}}}}}},"post":{"tags":["subscriptions"],"summary":"Create a subscription","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customerId","priceId","chain","token"],"properties":{"customerId":{"type":"string"},"priceId":{"type":"string"},"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"autopay":{"type":"string","enum":["allowance","smart-wallet","manual"],"default":"manual"},"trialDays":{"type":"integer","minimum":0,"maximum":365},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/v1/workspaces/{workspaceId}/subscriptions/{subId}":{"get":{"tags":["subscriptions"],"summary":"Retrieve a subscription","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"subId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/subscriptions/{subId}/pause":{"post":{"tags":["subscriptions"],"summary":"Pause a subscription (no renewals while paused)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"subId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/subscriptions/{subId}/resume":{"post":{"tags":["subscriptions"],"summary":"Resume a paused subscription","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"subId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Resumed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/subscriptions/{subId}/cancel":{"post":{"tags":["subscriptions"],"summary":"Cancel a subscription (now or at period end)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"subId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"Body is optional — server defaults to `mode: at_period_end` with no reason.","properties":{"mode":{"type":"string","enum":["immediately","at_period_end"],"default":"at_period_end"},"reason":{"type":"string","maxLength":256}}}}}},"responses":{"200":{"description":"Canceled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/subscriptions/{subId}/change_plan":{"post":{"tags":["subscriptions"],"summary":"Change a subscription's price (plan upgrade/downgrade)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"subId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["priceId"],"properties":{"priceId":{"type":"string"},"prorationMode":{"type":"string","enum":["immediately","at_period_end"],"default":"at_period_end","description":"Records the merchant's intent for how this swap should be billed. **Reserved for future use** — the platform does not currently issue proration credits or charges; the new price takes effect at the next billing cycle either way. The value is persisted on the subscription's audit trail and echoed on the `subscription.plan_changed` event so your reconciliation pipeline can stage logic for when proration ships."}}}}}},"responses":{"200":{"description":"Plan changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"422":{"$ref":"#/components/responses/InvalidStateTransition"}}}},"/v1/workspaces/{workspaceId}/wallets":{"get":{"tags":["wallets"],"summary":"List settlement wallets","parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"All wallets in this workspace","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Wallet"}}}}}}}}},"post":{"tags":["wallets"],"summary":"Register a new settlement wallet (issues challenge)","description":"Creates the wallet row in `unverified` state and returns a one-time challenge. Sign the challenge with the wallet's key, then POST to `/wallets/{id}/verify` with the signature. Requires a DASHBOARD SESSION + step-up auth — NOT an API key (a Bearer token returns 401). Add settlement wallets in the dashboard.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chain","token","address"],"properties":{"chain":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/TokenSymbol"},"address":{"type":"string","minLength":4,"maxLength":128},"nickname":{"type":"string","maxLength":80}}}}}},"responses":{"201":{"description":"Wallet created with one-shot signing challenge","content":{"application/json":{"schema":{"type":"object","properties":{"wallet":{"$ref":"#/components/schemas/Wallet"},"challenge":{"$ref":"#/components/schemas/WalletChallenge"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/AalRequired"},"409":{"description":"Address already registered to this workspace"}}}},"/v1/workspaces/{workspaceId}/wallets/{walletId}":{"get":{"tags":["wallets"],"summary":"Retrieve a wallet","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"walletId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The wallet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["wallets"],"summary":"Rename / set as default","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"walletId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nickname":{"type":"string","maxLength":80},"isDefault":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}}}},"delete":{"tags":["wallets"],"summary":"Soft-remove a wallet (no longer used for settlement)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"walletId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"401":{"$ref":"#/components/responses/AalRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/wallets/{walletId}/challenge":{"post":{"tags":["wallets"],"summary":"Issue a fresh signing challenge for an existing wallet","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"walletId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"New challenge","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletChallenge"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/wallets/{walletId}/verify":{"post":{"tags":["wallets"],"summary":"Submit a signed challenge to verify wallet ownership","description":"Signature wire-formats per chain — EVM: `0x…` ERC-191 personal-sign hex; Solana: base58 Ed25519 (64 bytes); Tron: `0x…` TIP-191 hex. Challenge is consumed atomically before signature verify, so a replay can't succeed even if you POST the same nonce twice. Step-up auth required.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"walletId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["challengeId","signature"],"properties":{"challengeId":{"type":"string","minLength":8,"maxLength":80},"signature":{"type":"string","minLength":60,"maxLength":200,"pattern":"^(0x[0-9a-fA-F]+|[1-9A-HJ-NP-Za-km-z]+)$","description":"Hex (0x…) for EVM/Tron, base58 for Solana Ed25519."}}}}}},"responses":{"200":{"description":"Wallet verified","content":{"application/json":{"schema":{"type":"object","properties":{"wallet":{"$ref":"#/components/schemas/Wallet"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/AalRequired"}}}},"/v1/workspaces/{workspaceId}/payments/{paymentId}":{"get":{"tags":["payments"],"summary":"Retrieve a payment","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string","enum":["customer"]}}],"responses":{"200":{"description":"The payment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/payments/{paymentId}/refund/broadcast":{"post":{"tags":["payments"],"summary":"Mark a refund as broadcast (chain-reader takes over)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refundTxHash"],"properties":{"refundTxHash":{"type":"string","minLength":4,"maxLength":200}}}}}},"responses":{"200":{"description":"Marked broadcast","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints/{endpointId}":{"get":{"tags":["webhooks"],"summary":"Retrieve a webhook endpoint","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["webhooks"],"summary":"Update a webhook endpoint","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","pattern":"^https://","description":"Must be HTTPS."},"description":{"type":"string","maxLength":200,"nullable":true},"events":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"status":{"type":"string","enum":["enabled","disabled"]}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"delete":{"tags":["webhooks"],"summary":"Soft-delete a webhook endpoint","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints/{endpointId}/rotate":{"post":{"tags":["webhooks"],"summary":"Rotate the signing secret","description":"Returns a new plaintext signing secret. There is NO grace window — the previous secret stops verifying immediately, so deploy the new secret to your endpoint as you rotate. (The webhook retry curve gives you time to redeploy before deliveries are dropped.)","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Rotated","content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint"},"signingSecret":{"type":"string","example":"whsec_…"}}}}}},"401":{"$ref":"#/components/responses/AalRequired"}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints/{endpointId}/test":{"post":{"tags":["webhooks"],"summary":"Enqueue a synthetic test event delivery for the endpoint","description":"Emits a `webhook.endpoint.test` event addressed to this endpoint. Delivery is enqueued via the regular webhook delivery pipeline; poll `/webhook_endpoints/{id}/deliveries` to inspect the result.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Test event enqueued","content":{"application/json":{"schema":{"type":"object","required":["eventId"],"properties":{"eventId":{"type":"string"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/workspaces/{workspaceId}/payments":{"get":{"tags":["payments"],"summary":"List payments","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","confirmed","failed","refunded","reorged"]}},{"name":"customerId","in":"query","schema":{"type":"string"}},{"name":"subscriptionId","in":"query","schema":{"type":"string"}},{"name":"screeningVerdict","in":"query","schema":{"type":"string","enum":["not_screened","screened_clean","screened_flagged","screen_error"]},"description":"Filter by sanctions-screening verdict. Backed by a partial index so a `screened_flagged` query is cheap at scale. Today's default with the noop provider is every payment landing as `not_screened`; once screening verdicts are populated this is the ops triage surface."},{"name":"expand","in":"query","schema":{"type":"string","enum":["customer"]}},{"name":"format","in":"query","schema":{"type":"string","enum":["csv"]},"description":"Returns text/csv when set"},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive lower bound on createdAt (ISO 8601)."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive upper bound on createdAt (ISO 8601)."},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Page of payments","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}}]}}}}}}},"/v1/workspaces/{workspaceId}/payments/{paymentId}/refund":{"post":{"tags":["payments"],"summary":"Initiate a non-custodial refund","description":"Returns an unsigned tx envelope that the merchant signs from their own wallet. OpenSettle never moves funds. The refund only takes effect when the chain reader observes the signed tx via `attachRefundTx` after confirmations.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"amountMinor":{"type":"integer","minimum":1,"description":"Defaults to full payment amount."},"reason":{"type":"string","maxLength":256},"recipientAddress":{"type":"string","minLength":4,"maxLength":128,"description":"Required when not captured at payment time. Strict per-chain validation: cross-chain (e.g. EVM address on Solana payment) is refused."}}}}}},"responses":{"200":{"description":"Refund initiated; sign + broadcast envelope returned","content":{"application/json":{"schema":{"type":"object","properties":{"payment":{"$ref":"#/components/schemas/Payment"},"unsignedTx":{"type":"object"}}}}}},"400":{"description":"Recipient invalid for this chain"},"403":{"description":"Admin role required"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Refund already in progress"}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints":{"get":{"tags":["webhooks"],"summary":"List webhook endpoints","parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"All registered endpoints","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}}}}},"post":{"tags":["webhooks"],"summary":"Register a webhook endpoint","description":"The `signingSecret` is returned exactly once. Verify webhook payloads by HMAC-SHA256 of `${timestamp}.${body}` against this secret; the signature header is `x-opensettle-signature: t=…,v1=…`.","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","description":"Must be HTTPS."},"description":{"type":"string","maxLength":200},"events":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"default":["*"],"description":"`[\"*\"]` for all events."}}}}}},"responses":{"201":{"description":"Endpoint registered; signingSecret revealed once","content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint"},"signingSecret":{"type":"string","example":"whsec_…"}}}}}},"400":{"description":"URL must be HTTPS and not point at private/loopback IPs (SSRF guard)"}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints/{endpointId}/deliveries":{"get":{"tags":["webhooks"],"summary":"List delivery attempts for an endpoint","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Page of deliveries, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedList"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}]}}}}}}},"/v1/workspaces/{workspaceId}/webhook_endpoints/{endpointId}/deliveries/{deliveryId}/replay":{"post":{"tags":["webhooks"],"summary":"Re-enqueue a specific delivery","parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"endpointId","in":"path","required":true,"schema":{"type":"string"}},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Re-enqueued; worker will retry on next tick","content":{"application/json":{"schema":{"type":"object","properties":{"delivery":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Cannot replay against a disabled endpoint"}}}}}}