{"openapi":"3.1.0","info":{"title":"MutoPay API","version":"1.0.0","description":"Crypto payment gateway, accept any token, receive your preferred stablecoin.\n\n## Authentication\n\nMutoPay uses two API keys, each for a different purpose:\n\n| Key | Prefix | Header | Scope |\n|-----|--------|--------|-------|\n| Channel API key | `ep_` | `X-API-Key` | Create payments, one key per integration (WooCommerce, mobile app, etc.) |\n| Master API key | `msk_` | `Authorization: Bearer` | Manage channels, settlement, and merchant settings |\n\n### Creating payments\n\nUse a **channel API key** in the `X-API-Key` header. Each channel has its own key so you can revoke or rotate one integration without affecting others.\n\n```http\nPOST /api/payments\nX-API-Key: ep_<channel_api_key>\n```\n\nGet or create channel keys from [Settings → Channels](/dashboard/settings).\n\n### Managing channels and settings\n\nUse a **master API key** as a Bearer token. This grants access to all `/api/merchant/*` endpoints.\n\n```http\nGET /api/merchant/channels\nAuthorization: Bearer msk_<master_api_key>\n```\n\nGenerate a master key from [Settings → Master API Key](/dashboard/settings).\n\n## Payment Flow\n\n1. **Create** a payment via `POST /api/payments` with the amount and optional metadata\n2. Redirect your customer to `https://mutopay.com/pay/{id}`, the hosted payment page handles token selection, wallet connection, and on-chain execution\n3. **Poll** `GET /api/payments/{id}/status` for real-time status updates, or wait for a webhook\n\n### Optional: channel attribution\n\nPass `channel_id` when creating a payment to attribute it to a specific channel. The channel's settlement override (token, chain, wallet address) will be used for that payment instead of the account default.\n\n### Payment Statuses\n\n| Status | Meaning |\n|--------|---------|\n| `pending` | Created, waiting for customer to start |\n| `awaiting_payment` | Customer selected token, waiting for payment |\n| `confirming` | Transaction submitted, verifying on-chain |\n| `processing` | Swap/bridge order executing |\n| `completed` | Payment received successfully |\n| `failed` | Payment failed (see `failure_reason`) |\n| `expired` | Payment expired before completion |\n| `underpaid` | Transfer amount below required threshold |\n\n## Webhooks\n\nConfigure your webhook URL in the merchant dashboard. Payloads are signed with HMAC-SHA256.\n\nVerify the `X-MutoPay-Signature` header:\n\n```\nsignature = HMAC-SHA256(webhook_secret, request_body)\nexpected  = \"sha256=\" + hex(signature)\n```\n\nEvents: `payment.completed`, `payment.failed`, `payment.expired`, `payment.underpaid`, `payment.kyc_required`, `payment.needs_manual_check`\n\n### Webhook Payload\n\n```json\n{\n  \"event\": \"payment.completed\",\n  \"payment_id\": \"pay_abc123\",\n  \"status\": \"completed\",\n  \"amount_usd\": 54.23,\n  \"amount_original\": 50,\n  \"fx_rate\": 1.0846,\n  \"currency\": \"EUR\",\n  \"src_token\": \"USDC\",\n  \"src_chain_id\": \"56\",\n  \"src_amount\": null,\n  \"dest_token\": \"USDC\",\n  \"dest_chain_id\": \"137\",\n  \"dest_amount\": \"54230000\",\n  \"dest_decimals\": 6,\n  \"external_id\": \"order_123\",\n  \"tx_hash\": \"0x...\",\n  \"failure_reason\": null,\n  \"completed_at\": \"2026-04-01T12:00:00Z\",\n  \"timestamp\": \"2026-04-01T12:00:01Z\"\n}\n```\n\n`dest_amount` is in raw token base units. Divide by `10^dest_decimals` for human-readable (e.g. `54230000 / 10^6 = 54.23 USDC`).\n\nRespond with a 2xx status. Failed deliveries are retried up to 5 times with exponential backoff (1m, 5m, 30m, 2h, 12h).\n"},"servers":[{"url":"https://mutopay.com","description":"Production"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Channel or merchant API key (`ep_` prefix). Used for payment creation from your backend."},"MasterKeyAuth":{"type":"http","scheme":"bearer","description":"Merchant master API key (`msk_` prefix). Grants full access to all `/api/merchant/*` endpoints except key management (rotate/revoke require browser sign-in). Generate one from [Dashboard → Settings](/dashboard/settings)."},"JwtAuth":{"type":"http","scheme":"bearer","description":"Merchant JWT from Google Sign-In (browser session). Required for key management endpoints, a master key cannot rotate or revoke itself."}},"schemas":{"CreatedPayment":{"type":"object","properties":{"id":{"type":"string"},"payment_url":{"type":"string","description":"Relative URL to redirect the customer to","example":"/pay/pay_abc123def456"},"amount_usd":{"type":"number"},"amount_original":{"type":"number","nullable":true},"fx_rate":{"type":"number","nullable":true},"currency":{"type":"string"},"status":{"type":"string","enum":["pending"]},"expires_at":{"type":"string"}},"required":["id","payment_url","amount_usd","amount_original","fx_rate","currency","status","expires_at"]},"ValidationError":{"type":"object","properties":{"error":{"type":"string","enum":["Validation error"]},"details":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"]}}},"required":["error","details"]},"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"HeadlessDeposit":{"type":"object","properties":{"address":{"type":"string","description":"Where the customer must send funds"},"chain_id":{"type":"string"},"chain_type":{"type":"string","enum":["evm","non-evm"]},"token_symbol":{"type":"string"},"token_address":{"type":"string"},"token_decimals":{"type":"integer"},"amount_raw":{"type":"string","description":"Exact amount to send in token base units"},"amount_human":{"type":"string","description":"Human-readable amount, e.g. 25.10"},"payment_uri":{"type":"string","nullable":true,"description":"Wallet-scannable URI (EIP-681 / TON / Solana Pay / BIP-21); null for ERC-20 EVM and Tron where no reliable standard exists"},"extra_fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Additional fields from the provider (e.g. TON memo, Solana reference)"}},"required":["address","chain_id","chain_type","token_symbol","token_address","token_decimals","amount_raw","amount_human","payment_uri"]},"HeadlessPaymentResponse":{"type":"object","properties":{"id":{"type":"string","example":"pay_abc123"},"status":{"type":"string","enum":["awaiting_payment"]},"expires_at":{"type":"string","nullable":true},"amount_usd":{"type":"number"},"amount_original":{"type":"number","nullable":true},"currency":{"type":"string"},"route_type":{"type":"string","enum":["direct","swap","bridge","swap_bridge"]},"protocol":{"type":"string","description":"`direct` for same-token same-chain transfers, or a swap/bridge provider identifier for deposit-based routes"},"deposit":{"$ref":"#/components/schemas/HeadlessDeposit"},"bridge":{"type":"object","nullable":true,"properties":{"name":{"type":"string","nullable":true},"estimated_time_ms":{"type":"number","nullable":true},"src_usd":{"type":"string","nullable":true},"bridge_fee_usd":{"type":"string","nullable":true}},"required":["name","estimated_time_ms","src_usd","bridge_fee_usd"],"description":"Null for direct routes; populated for swap/bridge deposit routes"},"order_id":{"type":"string","nullable":true,"description":"Provider order ID; null for direct routes"}},"required":["id","status","expires_at","amount_usd","amount_original","currency","route_type","protocol","deposit","bridge","order_id"]},"HeadlessPaymentBody":{"type":"object","properties":{"amount":{"type":"number","minimum":0,"exclusiveMinimum":true,"description":"Amount in `currency` units (use either this or `amount_usd`). Subject to a per-channel minimum; the server converts to USD before checking and returns 400 with the exact figure in `message` if below.","example":25},"amount_usd":{"type":"number","minimum":0,"exclusiveMinimum":true,"description":"Amount in USD (alternative to `amount` + `currency`). Subject to a per-channel minimum that recomputes hourly from current network conditions. Below the threshold returns 400 with the exact figure in `message`.","example":25},"currency":{"type":"string","description":"ISO currency code; defaults to USD","example":"USD"},"description":{"type":"string","maxLength":500,"example":"Order #1042 – Blue Widget"},"callback_url":{"type":"string","format":"uri","description":"Where to redirect the customer after completion (optional)"},"external_id":{"type":"string","maxLength":255,"description":"Your own order/reference ID","example":"order_1042"},"metadata":{"type":"object","additionalProperties":{"nullable":true},"description":"Arbitrary JSON to attach to the payment"},"expires_in_minutes":{"type":"integer","description":"Override merchant default payment expiry"},"src_token_symbol":{"type":"string","description":"Symbol of the token the customer will pay with","example":"USDT"},"src_token_address":{"type":"string","description":"Contract/mint address of the source token on the source chain. Use the native sentinel `0x0000000000000000000000000000000000000000` for native EVM assets (ETH, MATIC, BNB, etc.). Find the address on the token's CoinMarketCap page (https://coinmarketcap.com), under the **Contracts** section pick the row for the chain you care about and copy the contract address.","example":"0xc2132D05D31c914a87C6611C10748AEb04B58e8F"},"src_chain_id":{"type":"string","description":"Source chain ID, numeric string for EVM chains (e.g. `1` = Ethereum, `137` = Polygon) or slug for non-EVM (`ton`, `solana`, `bitcoin`, `tron`). Look up EVM chain IDs at https://chainlist.org.","example":"56"},"src_decimals":{"type":"integer","minimum":0,"exclusiveMinimum":true,"description":"Decimal places of the source token **on the specific source chain**. The same token symbol can have different decimals on different chains (e.g. USDT is 6 on Ethereum/Polygon/Arbitrum but 18 on BSC), so always check the value for the exact chain you're quoting against, not just the token's primary-chain decimals.\n\nHow to find it:\n- **Block explorer** (authoritative): open the token's contract page on the source chain's explorer (Etherscan, Polygonscan, BscScan, Arbiscan, etc.) → **Contract** tab → **Read Contract** → scroll to `decimals` and read the returned value.\n- **From code with wagmi**: `readContract({ address: '0x...', abi: erc20Abi, functionName: 'decimals', chainId: 137 })` (import `readContract` from `@wagmi/core` and `erc20Abi` from `viem`). Returns the decimal count as a number, pass it straight through.\n\nGetting this wrong produces an incorrect deposit amount, worth double-checking before going live.","example":6},"src_address":{"type":"string","description":"Customer's wallet address on the source chain","example":"0xCustomerWallet..."}},"required":["src_token_symbol","src_token_address","src_chain_id","src_decimals","src_address"]},"PayerDeposit":{"type":"object","properties":{"tx_hash":{"type":"string","description":"On-chain trace hash (TonAPI event_id, what tonviewer shows)"},"amount":{"type":"string","description":"Raw base units, BigInt-as-string; divide by 10^dest_decimals for human-readable","example":"1500000"},"amount_human":{"type":"string","description":"Formatted amount with token symbol (or just the number if decimals unknown)","example":"1.50 USDT"},"received_at":{"type":"integer","description":"Unix seconds when the deposit landed on the ephemeral address"},"source_address":{"type":"string","nullable":true,"description":"Payer wallet in friendly UQ.../EQ... format. Omitted on the unauthenticated public payment-page endpoint."}},"required":["tx_hash","amount","amount_human","received_at"]},"Payment":{"type":"object","properties":{"id":{"type":"string"},"merchant_id":{"type":"string"},"channel_id":{"type":"string","nullable":true},"amount_usd":{"type":"number"},"amount_original":{"type":"number","nullable":true},"fx_rate":{"type":"number","nullable":true},"currency":{"type":"string"},"status":{"type":"string","enum":["pending","awaiting_payment","processing","confirming","completed","failed","expired","underpaid","kyc_required","needs_manual_check","late_received"]},"dest_token":{"type":"string","nullable":true},"dest_chain_id":{"type":"string","nullable":true},"dest_address":{"type":"string","nullable":true},"dest_amount":{"type":"string","nullable":true,"description":"Actual settlement token amount in raw base units; divide by 10^dest_decimals for human-readable","example":"10000000"},"dest_decimals":{"type":"integer","nullable":true,"description":"Decimal places for dest_amount (e.g. 6 for USDC, 9 for TON)","example":6},"src_token":{"type":"string","nullable":true},"src_chain_id":{"anyOf":[{"type":"string"},{"type":"number"},{"nullable":true}]},"src_address":{"type":"string","nullable":true},"src_amount":{"type":"string","nullable":true},"route_type":{"type":"string","nullable":true,"enum":["direct","swap","bridge","swap_bridge"]},"protocol":{"type":"string","nullable":true},"order_id":{"type":"string","nullable":true},"provider_status":{"type":"string","nullable":true},"tx_hash":{"type":"string","nullable":true,"description":"Deprecated. Use payer_deposits[0].tx_hash for the most recent payer transaction. Holds the latest payer hash for backward compatibility.","deprecated":true},"payer_deposits":{"type":"array","items":{"$ref":"#/components/schemas/PayerDeposit"},"description":"Customer inbound transactions for this payment, newest first. Each row is one TonAPI trace. Empty array when no deposits have landed yet. source_address is omitted on the unauthenticated public payment-page endpoint."},"failure_reason":{"type":"string","nullable":true},"callback_url":{"type":"string","nullable":true},"external_id":{"type":"string","nullable":true},"metadata":{"type":"string","nullable":true},"expires_at":{"type":"string","nullable":true},"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","merchant_id","channel_id","amount_usd","amount_original","fx_rate","currency","status","dest_token","dest_chain_id","dest_address","dest_amount","dest_decimals","src_token","src_chain_id","src_address","src_amount","route_type","protocol","order_id","provider_status","tx_hash","failure_reason","callback_url","external_id","metadata","expires_at","completed_at","created_at","updated_at"]},"PaymentStatus":{"type":"object","properties":{"status":{"type":"string","enum":["pending","awaiting_payment","processing","confirming","completed","failed","expired","underpaid","kyc_required","needs_manual_check","late_received"]},"tx_hash":{"type":"string","nullable":true,"description":"Deprecated. Use the payer_deposits field on the full Payment object. Holds the latest payer hash for backward compatibility.","deprecated":true},"route_type":{"type":"string","nullable":true,"enum":["direct","swap","bridge","swap_bridge"]},"completed_at":{"type":"string","nullable":true},"failure_reason":{"type":"string","nullable":true},"received_amount":{"type":"string","nullable":true,"description":"Raw base units received so far on the deposit address. With dust_amount and dest_decimals, the client computes the remaining (top-up) amount for underpaid payments."},"dust_amount":{"type":"string","nullable":true,"description":"Raw base units the customer was asked to send (the expected total)."},"dest_decimals":{"type":"number","nullable":true,"description":"Decimal places for received_amount / dust_amount."},"payer_deposits":{"type":"array","items":{"type":"object","properties":{"tx_hash":{"type":"string"},"amount_human":{"type":"string"},"received_at":{"type":"number"}},"required":["tx_hash","amount_human","received_at"]},"description":"Per-deposit list for ephemeral-wallet (TON) payments. Updates as the customer tops up an underpaid payment."}},"required":["status"]},"Channel":{"type":"object","properties":{"id":{"type":"string","example":"ch_abc123"},"name":{"type":"string","description":"Internal dashboard label","example":"My Store"},"platform":{"type":"string","example":"manual"},"api_key_prefix":{"type":"string","nullable":true,"description":"First 10 chars of the channel API key; null if never generated or revoked","example":"ep_abc12345"},"webhook_url":{"type":"string","nullable":true,"example":"https://yourserver.com/webhooks/mutopay"},"webhook_secret":{"type":"string","description":"HMAC-SHA256 signing secret for this channel's webhooks"},"display_name":{"type":"string","nullable":true,"description":"Customer-facing name override (falls back to merchant business name if null)"},"tagline":{"type":"string","nullable":true,"description":"Customer-facing tagline override (falls back to merchant page tagline if null)"},"preferred_token":{"type":"string","nullable":true,"description":"Settlement token override for this channel; null = use account default","example":"USDC"},"preferred_chain_id":{"type":"string","nullable":true,"description":"Settlement chain ID override; null = use account default","example":"56"},"wallet_address":{"type":"string","nullable":true,"description":"Payout wallet override; null = use account default"},"status":{"type":"string","enum":["active","revoked"]},"last_used_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","platform","api_key_prefix","webhook_url","webhook_secret","display_name","tagline","preferred_token","preferred_chain_id","wallet_address","status","last_used_at","created_at"]},"CreateChannelBody":{"type":"object","properties":{"name":{"type":"string","description":"Internal dashboard label for this channel","example":"iOS App"},"platform":{"type":"string","description":"Integration platform hint","example":"manual"},"webhook_url":{"type":"string","nullable":true,"example":"https://yourserver.com/webhooks/mutopay"},"preferred_token":{"type":"string","description":"Settlement token override (must be set together with preferred_chain_id)","example":"USDC"},"preferred_chain_id":{"type":"string","description":"Settlement chain ID override (must be set together with preferred_token)","example":"56"},"wallet_address":{"type":"string","nullable":true,"description":"Payout wallet override"}},"required":["name"]},"Ok":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]},"ChannelSettlementBody":{"type":"object","properties":{"preferred_token":{"type":"string","example":"USDC"},"preferred_chain_id":{"type":"string","example":"56"},"wallet_address":{"type":"string","nullable":true,"description":"Set to null to clear the override wallet; omit to leave unchanged"}},"required":["preferred_token","preferred_chain_id","wallet_address"]}},"parameters":{}},"paths":{"/api/payments":{"post":{"tags":["Payments"],"summary":"Create a payment","description":"Creates a new payment request. Returns a payment object including the `id`, redirect your customer to `/pay/{id}` to complete payment.","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","minimum":0,"exclusiveMinimum":true,"maximum":1000000000000},"amount_usd":{"type":"number","minimum":0,"exclusiveMinimum":true,"maximum":1000000},"currency":{"type":"string","default":"USD"},"description":{"type":"string","maxLength":500},"callback_url":{"type":"string","format":"uri"},"external_id":{"type":"string","maxLength":255},"metadata":{"type":"object","additionalProperties":{"nullable":true}},"customer_name":{"type":"string"},"customer_email":{"type":"string"},"customer_ref":{"type":"string"},"customer_country":{"type":"string"},"expires_in_minutes":{"type":"integer","minimum":15,"maximum":10080},"channel_id":{"type":"string","maxLength":64}}}}}},"responses":{"201":{"description":"Payment created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedPayment"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payments/headless":{"post":{"tags":["Payments"],"summary":"Create a payment (headless, deposit-based)","description":"Creates a payment, selects the caller-provided source token/chain, and returns a ready-to-use deposit address + exact amount the customer must send. One round trip: no hosted `/pay/{id}` UI and no order-signing required.\n\nSupports **deposit-based routes only**:\n- **direct** (same token, same chain), the customer sends to the merchant's wallet\n- **swap / bridge deposit**: the customer sends to an intake address and the settlement token arrives in the merchant's wallet\n\nIf the only available route for the given src→dest pair requires the customer to sign an on-chain transaction or typed order, the endpoint returns 422 and the caller should fall back to the hosted flow.\n\nAfter calling this endpoint, the caller polls `GET /api/payments/{id}/status` until terminal. Swap/bridge deposits are picked up automatically by the background monitor; direct routes can optionally `POST /{id}/confirm` with the tx hash once the customer broadcasts.","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeadlessPaymentBody"}}}},"responses":{"201":{"description":"Payment created and deposit-ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeadlessPaymentResponse"}}}},"400":{"description":"Validation error or unsupported destination token/chain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No deposit-capable route for this src→dest pair","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payments/{id}":{"get":{"tags":["Payments"],"summary":"Get payment details","description":"Retrieves the full details of a payment. No authentication required, payment IDs are unguessable.","parameters":[{"schema":{"type":"string","description":"Payment ID","example":"pay_abc123def456"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Payment details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"404":{"description":"Payment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payments/{id}/status":{"get":{"tags":["Payments"],"summary":"Poll payment status","description":"Returns the current payment status. Performs inline on-chain verification for in-progress payments, so you get real-time updates without waiting for the background monitor.","parameters":[{"schema":{"type":"string","description":"Payment ID","example":"pay_abc123def456"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Current payment status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentStatus"}}}},"404":{"description":"Payment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels":{"get":{"tags":["Channels"],"summary":"List channels","description":"Returns all channels for the authenticated merchant, including webhook secrets and settlement override fields. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"responses":{"200":{"description":"Channel list","content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}}},"required":["channels"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Channels"],"summary":"Create a channel","description":"Creates a new manual channel. Returns the channel object and the full API key (shown **once**: store it immediately). Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChannelBody"}}}},"responses":{"201":{"description":"Channel created, store the `api_key` now, it won't be shown again","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"},"api_key":{"type":"string","description":"Full channel API key","example":"ep_abc..."}},"required":["channel","api_key"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}":{"get":{"tags":["Channels"],"summary":"Get a channel","description":"Returns full channel detail including webhook secret and settlement override. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Channel detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Channel"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Channels"],"summary":"Update channel labels","description":"Updates the channel's dashboard label (`name`) and/or customer-facing display fields (`display_name`, `tagline`). Pass `null` for display fields to fall back to account branding. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Internal dashboard label"},"display_name":{"type":"string","nullable":true,"description":"Customer-facing name (null = use merchant business name)"},"tagline":{"type":"string","nullable":true,"description":"Customer-facing tagline (null = use merchant page tagline)"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}/webhook-url":{"patch":{"tags":["Channels"],"summary":"Update webhook URL","description":"Updates the channel's webhook URL independently of other fields. Pass `null` to remove it. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhook_url"],"properties":{"webhook_url":{"type":"string","nullable":true,"example":"https://yourserver.com/webhooks/mutopay"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_url":{"type":"string","nullable":true}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}/settlement":{"put":{"tags":["Channels"],"summary":"Set settlement override","description":"Sets a per-channel settlement override (token + chain + wallet). All three fields are required and applied atomically. Payments created with this `channel_id` will use these `dest_*` values instead of the account default. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelSettlementBody"}}}},"responses":{"200":{"description":"Override set","content":{"application/json":{"schema":{"type":"object","properties":{"preferred_token":{"type":"string"},"preferred_chain_id":{"type":"string"},"wallet_address":{"type":"string","nullable":true}}}}}},"400":{"description":"Validation error (unsupported token/chain combination)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Channels"],"summary":"Remove settlement override","description":"Clears the per-channel settlement override. The channel falls back to the account-level default for all future payments. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Override removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}/regenerate-api-key":{"post":{"tags":["Channels"],"summary":"Rotate channel API key","description":"Generates a new API key for the channel, immediately invalidating the old one. Returns the full key **once**: store it immediately. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"New key, store immediately","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"type":"string","description":"Full new API key","example":"ep_abc..."},"api_key_prefix":{"type":"string","example":"ep_abc12345"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}/revoke":{"post":{"tags":["Channels"],"summary":"Revoke a channel","description":"Permanently revokes the channel's API key. The channel record is retained but status changes to `revoked` and the key stops working immediately. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Channel revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/merchant/channels/{id}/test-webhook":{"post":{"tags":["Channels"],"summary":"Send a test webhook","description":"Fires a `payment.completed` test payload to the channel's webhook URL. Accepts a master API key (`msk_`) or a JWT browser session.","security":[{"MasterKeyAuth":[]},{"JwtAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Channel ID (`ch_` prefix)","example":"ch_abc123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Result of the test delivery","content":{"application/json":{"schema":{"type":"object","properties":{"delivered":{"type":"boolean"},"status_code":{"type":"number","nullable":true},"error":{"type":"string","nullable":true}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found or no webhook URL set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}