"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Changelog · Mauzo API</title>\n<link rel=\"icon\" type=\"image/x-icon\" href=\"/mauzo.ico\">\n<script>\n(function () {\n    var KEY = 'mauzo.appearance', root = document.documentElement, prefs = {};\n    try { prefs = JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch (e) { prefs = {}; }\n    try {\n        var legacy = localStorage.getItem('tablerTheme');\n        if (!prefs.scheme && legacy) { prefs.scheme = legacy; }\n        var q = new URLSearchParams(location.search).get('theme');\n        if (q === 'dark' || q === 'light' || q === 'system') { prefs.scheme = q; localStorage.setItem(KEY, JSON.stringify(prefs)); }\n    } catch (e) {}\n    var defaults = {scheme: 'system', sidebar: 'expanded', nav: 'floating', width: 'default'};\n    var dark = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;\n    function apply() {\n        var p = Object.assign({}, defaults, prefs);\n        if (p.scheme === 'light' || p.scheme === 'dark') { root.setAttribute('data-theme', p.scheme); } else { root.removeAttribute('data-theme'); }\n        var resolved = p.scheme === 'dark' || (p.scheme !== 'light' && dark && dark.matches) ? 'dark' : 'light';\n        root.setAttribute('data-bs-theme', resolved);\n        root.setAttribute('data-sidebar', p.sidebar);\n        root.setAttribute('data-nav', p.nav);\n        root.setAttribute('data-width', p.width);\n        if (document.body) { document.body.removeAttribute('data-bs-theme'); }\n    }\n    apply();\n    if (dark && dark.addEventListener) { dark.addEventListener('change', apply); }\n    // Charts (ApexCharts reads window.Apex): Mauzo red for the main series, then ink, green, blue,\n    // colours in a fixed order; grid and labels from the tokens. Re-read when the theme changes.\n    function chartDefaults() {\n        var css = getComputedStyle(root), v = function (n) { return css.getPropertyValue(n).trim(); };\n        window.Apex = {\n            colors: [v('--brand'), v('--ink-strong'), v('--success'), v('--info'), v('--warning'), v('--vendor-purple'), v('--vendor-teal')],\n            chart: {fontFamily: 'inherit', foreColor: v('--muted'), toolbar: {show: false}, background: 'transparent'},\n            grid: {borderColor: v('--border'), strokeDashArray: 4, xaxis: {lines: {show: false}}},\n            stroke: {width: 2, curve: 'smooth'},\n            dataLabels: {enabled: false},\n            tooltip: {theme: root.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light'},\n            legend: {labels: {colors: v('--muted')}, markers: {radius: 2}}\n        };\n    }\n    // Read a design token in chart code: mzTok('--brand').\n    window.mzTok = function (n) { return getComputedStyle(root).getPropertyValue(n).trim(); };\n    document.addEventListener('DOMContentLoaded', function () { try { chartDefaults(); } catch (e) {} });\n    document.addEventListener('mauzo:appearance', function () { try { chartDefaults(); } catch (e) {} });\n    window.MauzoAppearance = {\n        get: function () { return Object.assign({}, defaults, prefs); },\n        set: function (key, value) {\n            prefs[key] = value;\n            try { localStorage.setItem(KEY, JSON.stringify(prefs)); localStorage.removeItem('tablerTheme'); } catch (e) {}\n            apply();\n            document.dispatchEvent(new CustomEvent('mauzo:appearance', {detail: this.get()}));\n        }\n    };\n})();\n</script>\n<link rel=\"stylesheet\" href=\"/css/mauzo-tokens.css\">\n<style>\n    * { box-sizing: border-box; }\n    html, body { margin: 0; }\n    body { font-family: var(--font-sans); font-size: 15px; line-height: 1.6; background: var(--bg); color: var(--ink); }\n    a { color: var(--ink-strong); }\n    .dx-top { position: sticky; top: 0; z-index: 50; display: flex; align-items: center; gap: 16px; padding: 0 20px; height: 56px; background: var(--surface); border-bottom: 1px solid var(--border); }\n    .dx-brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--ink-strong); font-weight: 700; font-size: 17px; white-space: nowrap; }\n    .dx-brand .mz-logo { color: var(--ink-strong); }\n    .dx-brand span { font-weight: 500; color: var(--muted); }\n    .dx-nav { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; flex: 1; }\n    .dx-nav a { padding: 6px 10px; border-radius: var(--radius-md); color: var(--ink); text-decoration: none; font-size: 14px; font-weight: 500; white-space: nowrap; }\n    .dx-nav a:hover { background: var(--surface-3); color: var(--ink-strong); }\n    .dx-nav a.active { background: var(--surface-3); color: var(--ink-strong); font-weight: 600; }\n    .dx-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); color: var(--ink-strong); cursor: pointer; }\n    .dx-icon-btn:hover { background: var(--surface-3); }\n    .mz-when-dark { display: none !important; }\n    html[data-bs-theme=\"dark\"] .mz-when-dark { display: inline-flex !important; }\n    html[data-bs-theme=\"dark\"] .mz-when-light { display: none !important; }\n    .dx-wrap { max-width: 1180px; margin: 0 auto; padding: 32px 20px 64px; }\n    .dx-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-card); }\n    .dx-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: var(--radius-md); border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink-strong); font-weight: 500; font-size: 14px; text-decoration: none; }\n    .dx-btn:hover { background: var(--surface-3); }\n    .dx-btn.primary { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); }\n    .dx-btn.primary:hover { background: var(--primary-hover); }\n    .dx-muted { color: var(--muted); }\n    .dx-label { font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }\n    .dx-foot { text-align: center; color: var(--muted); font-size: 13px; padding: 24px 0 8px; }\n    @media (max-width: 720px) { .dx-top { gap: 10px; padding: 0 12px; } .dx-brand span { display: none; } }\n</style>\n    <style>\n        .dx-doc { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 28px; align-items: start; }\n        .dx-toc { position: sticky; top: 80px; font-size: 14px; }\n        .dx-toc a { display: block; padding: 5px 10px; border-radius: var(--radius-md); color: var(--ink); text-decoration: none; }\n        .dx-toc a:hover, .dx-toc a.on { background: var(--surface-3); color: var(--ink-strong); }\n        .dx-body { padding: 32px 36px; }\n        .dx-body h1 { font-size: 30px; line-height: 1.15; letter-spacing: -.02em; margin: 0 0 6px; color: var(--ink-strong); }\n        .dx-body .intro { color: var(--muted); font-size: 16px; margin: 0 0 24px; }\n        .dx-body h2 { font-size: 21px; margin: 2.2rem 0 .6rem; padding-top: .4rem; color: var(--ink-strong); scroll-margin-top: 80px; }\n        .dx-body h3 { font-size: 17px; margin: 1.6rem 0 .4rem; color: var(--ink-strong); scroll-margin-top: 80px; }\n        .dx-body p, .dx-body li { color: var(--ink); }\n        .dx-body a { color: var(--ink-strong); text-underline-offset: 2px; }\n        .dx-body code { font-family: var(--font-mono); font-size: .88em; background: var(--surface-3); color: var(--ink-strong); padding: .12rem .35rem; border-radius: 4px; }\n        .dx-body pre { background: #0f172a; color: #e2e8f0; padding: 16px 18px; border-radius: var(--radius-lg); overflow-x: auto; font-size: 13px; line-height: 1.55; border: 1px solid var(--border); }\n        .dx-body pre code { background: none; color: inherit; padding: 0; font-size: inherit; }\n        .dx-body table { border-collapse: collapse; width: 100%; margin: 1rem 0; display: block; overflow-x: auto; font-size: 14px; }\n        .dx-body th, .dx-body td { border: 1px solid var(--border); padding: 8px 10px; text-align: left; vertical-align: top; }\n        .dx-body th { background: var(--surface-2); color: var(--ink-strong); font-weight: 600; }\n        .dx-body blockquote { margin: 1rem 0; padding: 10px 14px; border-left: 3px solid var(--brand); background: var(--surface-2); border-radius: 0 var(--radius-md) var(--radius-md) 0; }\n        .dx-body blockquote p { margin: 0; }\n        .dx-body hr { border: 0; border-top: 1px solid var(--border); margin: 2rem 0; }\n        .dx-body .full { margin-top: 2.5rem; padding-top: 1.2rem; border-top: 1px solid var(--border); font-size: 14px; color: var(--muted); }\n        @media (max-width: 900px) { .dx-doc { grid-template-columns: 1fr; } .dx-toc { display: none; } .dx-body { padding: 22px 18px; } }\n    </style>\n</head>\n<body>\n<header class=\"dx-top\">\n    <a class=\"dx-brand\" href=\"/api/docs\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 634.44 105.04\" width=\"132.9\" height=\"22\" class=\"mz-logo \" role=\"img\" aria-label=\"Mauzo\"><title>Mauzo</title>\n<path fill=\"var(--brand, #e9473c)\" d=\"M129.5,33.65c-11.73,12.75-23.45,25.49-35.18,38.24-2.36,2.56-4.62,5.22-7.09,7.67-4.37,4.34-8.93,4.9-13.7,.46-6.78-6.31-13.64-12.54-21.42-19.68-11.06,11.2-21.97,22.28-32.92,33.31-2.04,2.05-4.04,5.21-6.42,5.59-3.74,.6-8.95,.53-11.34-1.65-1.86-1.7-1.97-8.58-.1-10.55,14.97-15.78,30.39-31.16,46.29-46,1.8-1.68,8.39-.54,11.1,1.5,7.34,5.52,13.78,12.22,21.1,18.92,13.09-14.48,25.64-28.34,39.62-43.8-7.53,0-13.17-.22-18.79,.06-6.17,.3-9.79-2.96-9.95-8.45C90.53,3.26,94.34-.08,100.8,0c12.77,.16,25.55,.21,38.32,.07,6.75-.07,9.4,3.41,9.43,9.65,.05,12.19,.21,24.38,.05,36.57-.07,5.6-2.68,9.61-8.91,9.47-5.75-.13-8.11-3.81-8.23-9.18-.09-4-.02-8.01-.02-12.02-.65-.31-1.29-.62-1.94-.94Z\"/>\n<path fill=\"currentColor\" d=\"M218.77,102.87h-16.86V27.34h14.61c.26,2.15,.56,4.58,.93,7.62,15-12.59,29.63-14.28,42.29,.4,7.16-3.41,13.47-7.84,20.43-9.45,15.25-3.54,28.19,6.64,29.03,23.16,.9,17.62,.2,35.32,.2,53.47h-17.39c0-16.18,.53-32.1-.33-47.95-.26-4.7-3.58-12.04-7.04-13.08-4.83-1.45-11.51,1.02-16.56,3.51-2.34,1.15-3.72,6.28-3.8,9.64-.39,15.67-.17,31.35-.17,47.69h-17.44c0-15.37-.1-30.94,.05-46.51,.06-6.14-.53-13.07-6.9-14.36-5.19-1.05-11.59,1.19-16.69,3.6-2.39,1.13-4.1,6.07-4.18,9.34-.42,15.9-.19,31.81-.19,48.45Z\"/>\n<path fill=\"currentColor\" d=\"M389.86,102.9c-5.16-2.31-13.45,5.54-16.55-4.52-6.41,2.06-13.25,5.49-20.39,6.3-13.73,1.56-24.7-7.04-25.97-18.78-1.72-15.92,5.77-26.1,21.45-27.84,7.7-.85,15.57-.15,23.2-.15,2.39-19.95-7.38-22.88-37.56-12.48-3.05-13.04-2.28-15.43,10.21-17.81,8.65-1.64,17.89-1.82,26.64-.79,10.74,1.26,18.21,8.91,18.73,19.8,.88,18.44,.24,36.96,.24,56.26Zm-18.11-32.73c-5.87,0-11.92-.58-17.81,.15-7.01,.87-10.41,5.35-9.62,12.72,.74,6.95,5.46,7.95,11.11,7.98,13.61,.07,18.18-5.49,16.33-20.85Z\"/>\n<path fill=\"currentColor\" d=\"M634.42,66.25c-.61,24.75-14.83,39.19-38.11,38.69-21.72-.46-36.16-16.95-35.63-40.67,.54-24.21,14.88-39.27,37.19-39.06,23.52,.21,37.18,15.55,36.56,41.04Zm-54.55-1.63c0,2.9-.29,5.83,.05,8.69,1.33,11.09,9.56,17.65,20.39,16.46,10.21-1.12,15.91-9.71,16.01-24.14,.11-15.17-5.35-24.11-15.52-25.43-14.03-1.82-21.21,6.45-20.93,24.42Z\"/>\n<path fill=\"currentColor\" d=\"M474.85,27.74V102.55h-16.04v-8.65c-8.55,8.29-17.93,12.79-29.18,10.58-11.86-2.33-19.5-9.87-20.28-22.87-1.07-17.87-.27-35.86-.27-54.16h17.26c0,15.05-.02,30.04,0,45.03,.02,12,4.02,17.13,13.39,17.23,10.32,.11,18.07-7.41,18.1-17.67,.04-14.52,.01-29.04,.01-44.31h17.01Z\"/>\n<path fill=\"currentColor\" d=\"M549.78,88.81v13.54h-57.57c-1.86-7.22-.86-13.51,4.21-19.84,10.61-13.24,20.5-27.07,31.43-41.66h-32.65v-13.07h54.33c1.64,6.34,.45,11.71-3.93,17.27-10.91,13.87-21.13,28.29-32.57,43.76h36.76Z\"/>\n</svg>\n <span>API docs</span>\n    </a>\n    <nav class=\"dx-nav\" aria-label=\"Documentation\">\n                <a href=\"/api/docs\" class=\"\" >Overview</a>\n                <a href=\"/api/docs/authentication\" class=\"\" >Authentication</a>\n                <a href=\"/api/docs/examples\" class=\"\" >Examples</a>\n                <a href=\"/api/docs/webhooks\" class=\"\" >Webhooks</a>\n                <a href=\"/api/docs/errors\" class=\"\" >Errors</a>\n                <a href=\"/api/docs/swagger\" class=\"\" >API reference</a>\n                <a href=\"/api/docs/redoc\" class=\"\" >ReDoc</a>\n                <a href=\"/api/docs/changelog\" class=\"active\" aria-current=\"page\">Changelog</a>\n            </nav>\n    <button type=\"button\" class=\"dx-icon-btn\" id=\"dx-theme\" aria-label=\"Switch light or dark\" title=\"Light or dark\">\n        <svg class=\"mz-when-light\" xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/></svg>\n        <svg class=\"mz-when-dark\" xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0\"/><path d=\"M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7\"/></svg>\n    </button>\n</header>\n<script>\ndocument.getElementById('dx-theme').addEventListener('click', function () {\n    var dark = document.documentElement.getAttribute('data-bs-theme') === 'dark';\n    if (window.MauzoAppearance) { window.MauzoAppearance.set('scheme', dark ? 'light' : 'dark'); }\n});\n</script>\n<main class=\"dx-wrap\">\n    <div class=\"dx-doc\">\n        <nav class=\"dx-toc\" id=\"dx-toc\" aria-label=\"On this page\">\n            <div class=\"dx-label\" style=\"padding: 0 10px 6px;\">On this page</div>\n        </nav>\n        <article class=\"dx-card dx-body\" id=\"dx-body\">\n            <h1>Changelog</h1>\n            <p class=\"intro\">What changed for integrators, newest first.</p>\n            <p>What changed for integrators, newest first. v1 is append-only: fields and endpoints are added,\nnever removed or renamed. Questions: <strong>techteam@1809ltd.co.ke</strong>.</p>\n<h2>2026-10-14</h2>\n<ul>\n<li><strong>A payout now reaches a final state.</strong> Payouts raised through <code>POST /payouts</code> told M-Pesa to\nsend the result to the wrong one of our two callback addresses, so the verdict was refused as\nmalformed and never applied: the payout stayed <code>processing</code> and the amount stayed held in your\nwallet, even when the transfer had failed and the money had never left. Results now land where\nthey can be read, so a payout ends as <code>completed</code> or <code>failed</code>, a failed one returns the held\namount to your wallet immediately, and you get <code>payout.success</code> or <code>payout.failed</code> either way.\nPayouts already stuck this way have been settled from the verdicts M-Pesa had already sent.</li>\n</ul>\n<h2>2026-10-08 (later)</h2>\n<ul>\n<li><strong>A payment whose callback never arrives is now settled anyway.</strong> Every five minutes we ask\nthe provider about payments still waiting and apply the verdict through the same path a\ncallback takes, so the wallet credit, the fee, the sale and your <code>collection.success</code> or\n<code>collection.failed</code> all happen exactly once. Callbacks stopped reaching us for three days\nafter a DNS lapse; results now resolve within minutes regardless.</li>\n</ul>\n<h2>2026-10-08</h2>\n<ul>\n<li><strong>A non-Kenyan <code>payer_phone</code> is now refused with <code>422</code></strong> and <code>details.payer_phone</code>, before\nanything is attempted. It used to reach the provider's rail, fail there, and come back as\n<code>502 COLLECTION_FAILED</code> — \"no money has moved\" — which reads like an outage and invites a\nretry that cannot succeed. M-Pesa can only charge Kenyan lines.</li>\n<li><strong>Reminder about refresh tokens:</strong> <code>POST /auth/refresh</code> returns a <em>new</em> refresh token and\ninvalidates the one you sent. Store the new one every time, and refresh from one place only.\nPresenting a refresh token twice is treated as theft: the whole chain is revoked and the call\nfails with <code>401 REFRESH_TOKEN_REUSED</code>. An access token lasts 30 minutes, so refreshing every\nfew minutes is unnecessary.</li>\n</ul>\n<h2>2026-09-17</h2>\n<ul>\n<li><strong>Callback events match what we send.</strong> <code>GET /webhooks/events</code> now lists exactly the 15\nevents we deliver, each saying when. <code>payout.approved</code>, <code>payout.rejected</code> and\n<code>payout.cancelled</code> were sent but could not be subscribed to; now they can.</li>\n<li><strong>Retired event names.</strong> <code>invoice.paid</code>, <code>invoice.partially_paid</code>, <code>invoice.voided</code>,\n<code>bill.paid</code>, <code>bill.partially_paid</code>, <code>bill.voided</code>, <code>collection.pending</code> and\n<code>payout.pending</code> were listed but never sent. They are still accepted when you register a\nwebhook, so nothing breaks, but they deliver nothing. For payments, use\n<code>collection.success</code> / <code>payout.success</code>.</li>\n<li><strong>\"What you hear back, and when\"</strong> in the integration guide: which requests get only a\nreply and which also get a callback.</li>\n<li><strong>Interactive reference fixed.</strong> The Swagger and Redoc pages were empty because the\nOpenAPI document failed to load. The <code>Collection</code> and <code>Payout</code> schemas also listed <code>type</code>\ntwice; <code>type</code> is the payment type, e.g. <code>mobile_money</code>.</li>\n<li><strong>Support</strong> is now techteam@1809ltd.co.ke. Our emails come from noreply@mymauzo.com, and\nreplies to them reach support.</li>\n</ul>\n<h2>2026-09-16</h2>\n<ul>\n<li><strong>Say what was sold on <code>POST /collections</code>.</strong> Optional <code>product_uuid</code>, <code>item_name</code> and\n<code>quantity</code>. Every completed collection is recorded on your account as a paid sale. Without\nthese fields, the sale is recorded against your default \"Collection\" product. Responses\nand webhooks carry <code>item</code> and, once paid, <code>invoice</code>.</li>\n<li><strong>Look up a transaction by your own reference.</strong> <code>GET /collections/{id}</code>,\n<code>/collections/{id}/status</code> and the payout equivalents accept our <code>id</code>, our <code>reference</code>, or\nthe <code>reference</code> you sent.</li>\n<li><strong><code>POST /webhooks/test</code></strong> sends your endpoint a signed test event and returns its answer,\nplus a hint when it was refused.</li>\n<li><strong>One secret per URL.</strong> A URL you registered as a webhook is always signed with that\nwebhook's secret, including when you also send it as <code>notify_url</code>. Other URLs use your\naccount's Webhook Secret, never your API secret.</li>\n<li><strong>Third callback destination.</strong> The Default Callback URL set on your account in the\ndashboard now receives results when a request has no <code>notify_url</code> and no registered webhook\ntakes the event.</li>\n<li><strong>Registration fix.</strong> An endpoint that refuses our unsigned registration probe with 401 or\n403 can now be registered.</li>\n<li><strong><code>invoice.*</code> and <code>bill.*</code> events</strong> are now delivered to webhooks that subscribe to them.\nThey were never sent before.</li>\n<li><strong>Retry schedule.</strong> Failed deliveries are retried after 1m, 5m, 15m, 1h and 3h: six\nattempts in about four and a half hours. We can resend on request after that.</li>\n<li><strong>Invoice numbers.</strong> API invoice numbers no longer collide with invoices created in the\ndashboard.</li>\n<li><strong><code>POST /invoices/with-collection</code> fixes:</strong><ul>\n<li>a tax-inclusive price is charged once;</li>\n<li>goods stock is deducted;</li>\n<li>a retried request never records a second sale;</li>\n<li>a request refused before payment leaves no sale behind.</li>\n</ul>\n</li>\n<li><strong>Settlement.</strong> A completed payment settles an invoice only in your own account.</li>\n</ul>\n<h2>2026-09-10</h2>\n<ul>\n<li><strong>Docs pages fixed.</strong> The Webhooks, Errors and Examples pages no longer return an error.</li>\n</ul>\n<h2>2026-09-06</h2>\n<ul>\n<li><strong>Webhook registration fixed.</strong> <code>POST /webhooks</code> works; it was refused for every caller.</li>\n<li><strong>Clearer availability.</strong> <code>GET /collection-methods</code> and <code>/payout-methods</code> give the real\nreason a payment type is unavailable. \"Try again shortly\" and \"ask support to enable this\"\nare now distinguishable.</li>\n</ul>\n<h2>2026-09-05</h2>\n<ul>\n<li><strong>Wallets.</strong> <code>GET /wallets</code>, <code>GET /wallets/{uuid}/transactions</code> and\n<code>POST /wallets/transfer</code>, which moves funds between your own wallets. They need the new\nscopes <code>wallets.read</code> and <code>wallets.transfer</code>.</li>\n<li><strong>Up to 10 active API keys</strong> per account. Revoking one frees a slot.</li>\n<li><strong>No double credit.</strong> A provider retrying a payment callback no longer credits your wallet\ntwice.</li>\n</ul>\n<h2>2026-09-04</h2>\n<ul>\n<li><strong>You choose the payment type, we choose the provider.</strong> Send <code>\"type\": \"mobile_money\"</code>\n(and the fields <code>GET /collection-methods</code> lists). Naming a provider is refused.</li>\n<li><strong>Self-service API keys.</strong> Issue, rotate and revoke keys in the dashboard under\nSettings → API Keys.</li>\n<li><strong>Fees shown.</strong> Collections and payouts show the platform <code>fee</code> and your <code>net_amount</code>.</li>\n<li><strong>Local testing.</strong> A local sandbox environment for Postman.</li>\n</ul>\n\n            <p class=\"full\">\n                This page is generated from the integration guide, so it never disagrees with it.\n                <a href=\"/api/docs/integration-guide\">Download the full guide</a>.\n            </p>\n        </article>\n    </div>\n</main>\n<script>\n// Contents from the page's own sections; the one in view is marked.\n(function () {\n    var toc = document.getElementById('dx-toc'), heads = document.querySelectorAll('#dx-body h2');\n    var slug = function (s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };\n    heads.forEach(function (h) {\n        if (!h.id) { h.id = slug(h.textContent); }\n        var a = document.createElement('a'); a.href = '#' + h.id; a.textContent = h.textContent; toc.appendChild(a);\n    });\n    if (!heads.length) { toc.style.display = 'none'; return; }\n    var links = toc.querySelectorAll('a');\n    var mark = function () {\n        var cur = 0;\n        heads.forEach(function (h, i) { if (h.getBoundingClientRect().top < 120) { cur = i; } });\n        links.forEach(function (a, i) { a.classList.toggle('on', i === cur); });\n    };\n    document.addEventListener('scroll', mark, {passive: true}); mark();\n})();\n</script>\n</body>\n</html>\n"