import type { Metadata } from "next"; import Link from "next/link"; import { Fragment } from "react"; export const metadata: Metadata = { title: "Docs", }; const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? process.env.API_BASE_URL ?? "http://localhost:8000"; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.SITE_URL ?? "http://localhost:3000"; const normalizedSiteUrl = siteUrl.replace(/\/+$/, ""); const methodStyles: Record = { GET: "bg-emerald-900/10 text-emerald-800", POST: "bg-amber-900/15 text-amber-800", DELETE: "bg-red-900/10 text-red-700", PUT: "bg-blue-900/10 text-blue-800", PATCH: "bg-blue-900/10 text-blue-800", }; const endpointGroups = [ { title: "Login", auth: "None", items: [ { method: "POST", path: "/api/core/auth/login", description: "Exchange a primary address and secret view key for an API key and webhook secret.", }, ], }, { title: "Public invoice status", auth: "None", items: [ { method: "GET", path: "/api/core/public/invoice/{invoice_id}", description: "Read invoice status and payment details without auth.", }, { method: "GET", path: "/api/core/public/invoice/{invoice_id}/continue", description: "After confirmation, redirect to the merchant-provided Continue URL (if configured).", }, { method: "GET", path: "/api/core/public/system/status", description: "Read daemon, wallet-rpc, block height, and reconciler heartbeat without auth.", }, ], }, { title: "Invoice workflow", auth: "API key", items: [ { method: "GET", path: "/api/core/invoices", description: "List invoices for the signed-in user (supports search and sorting).", }, { method: "GET", path: "/api/core/invoices/export.csv", description: "Export invoices as CSV (same filters as list endpoint).", }, { method: "GET", path: "/api/core/invoices/{invoice_id}", description: "Retrieve a single invoice for the signed-in user.", }, { method: "POST", path: "/api/core/invoices", description: "Create invoices for the signed-in user.", }, { method: "DELETE", path: "/api/core/invoices/{invoice_id}", description: "Archive a pending invoice until it expires (payment-detected/confirmed invoices cannot be archived).", }, ], }, { title: "API key webhooks", auth: "API key", items: [ { method: "GET", path: "/api/core/webhooks", description: "List active webhook registrations.", }, { method: "GET", path: "/api/core/webhooks/history", description: "List recent webhook delivery attempts and HTTP status.", }, { method: "POST", path: "/api/core/webhooks/deliveries/{delivery_id}/redeliver", description: "Manually redeliver a failed webhook delivery.", }, { method: "POST", path: "/api/core/webhooks", description: "Register webhook URLs (single or per-event) and event filters.", }, { method: "DELETE", path: "/api/core/webhooks/{webhook_id}", description: "Remove a webhook registration.", }, ], }, { title: "API key management", auth: "API key", items: [ { method: "POST", path: "/api/core/api-credentials/reset", description: "Reset the API key, webhook secret, or both.", }, { method: "PATCH", path: "/api/core/profile", description: "Update profile preferences like the BTCPay checkout layout or default confirmations.", }, ], }, { title: "BTCPay Greenfield stores", auth: "API key (token)", items: [ { method: "GET", path: "/api/v1/stores", description: "List stores accessible to the API key.", }, { method: "GET", path: "/api/v1/stores/{store_id}", description: "Retrieve a store by id.", }, { method: "GET", path: "/api/v1/stores/{store_id}/payment-methods", description: "List enabled payment methods (XMR-CHAIN only).", }, ], }, { title: "BTCPay Greenfield invoices", auth: "API key (token)", items: [ { method: "POST", path: "/api/v1/stores/{store_id}/invoices", description: "Create a BTCPay-compatible invoice.", }, { method: "GET", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}", description: "Retrieve a BTCPay-compatible invoice.", }, { method: "GET", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}/payment-methods", description: "Return payment details for the invoice.", }, { method: "POST", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}/status", description: "Mark an invoice as Invalid (compatibility only).", }, ], }, { title: "BTCPay Greenfield webhooks", auth: "API key (token)", items: [ { method: "POST", path: "/api/v1/stores/{store_id}/webhooks", description: "Register a webhook with authorized events.", }, { method: "GET", path: "/api/v1/stores/{store_id}/webhooks", description: "List registered webhooks for a store.", }, { method: "GET", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", description: "Retrieve a webhook by id.", }, { method: "PUT", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", description: "Update a webhook (enabled, url, or events).", }, { method: "DELETE", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", description: "Delete a webhook registration.", }, ], }, { title: "BTCPay server info", auth: "API key (token)", items: [ { method: "GET", path: "/api/v1/server/info", description: "Return server info for compatibility checks.", }, { method: "GET", path: "/api/v1/api-keys/current", description: "Return the API key label and permissions.", }, ], }, ]; const endpointRequirements = [ { method: "POST", path: "/api/core/auth/login", auth: "None", required: ["payment_address", "view_key"], optional: [], notes: [ "Primary address only; subaddresses and integrated addresses are rejected.", "Use the secret view key (never submit a spend key).", ], }, { method: "GET", path: "/api/core/public/invoice/{invoice_id}", auth: "None", required: ["invoice_id (path)"], optional: [], notes: ["Returns invoice status and payment details."], }, { method: "GET", path: "/api/core/public/invoice/{invoice_id}/continue", auth: "None", required: ["invoice_id (path)"], optional: [], notes: [ "Only available after an invoice is confirmed.", "Returns a 302 redirect to the merchant-provided checkout_continue_url (if configured).", ], }, { method: "GET", path: "/api/core/public/system/status", auth: "None", required: [], optional: [], notes: [ "Returns wallet-rpc reachability, daemon reachability, current daemon height, and reconciler heartbeat timestamps.", "Use this endpoint to debug setup issues before sending a real payment.", ], }, { method: "GET", path: "/api/core/invoices", auth: "API key", required: [], optional: [ "limit (default 50, 1-100)", "offset (default 0)", "status", "include_archived (default false)", "q (invoice id, subaddress, metadata)", "sort (created_at, expires_at, amount_xmr, status, confirmations, confirmation_target)", "order (asc or desc)", "created_from (ISO timestamp)", "created_to (ISO timestamp)", ], notes: ["Returns invoices excluding archived entries."], }, { method: "GET", path: "/api/core/invoices/export.csv", auth: "API key", required: [], optional: [ "include_archived (default false)", "status", "q", "sort", "order", "created_from", "created_to", ], notes: ["Returns CSV with invoice rows."], }, { method: "GET", path: "/api/core/invoices/{invoice_id}", auth: "API key", required: ["invoice_id (path)"], optional: [], notes: ["Returns a single invoice (archived invoices are excluded)."], }, { method: "GET", path: "/api/core/webhooks", auth: "API key", required: [], optional: [], notes: ["Returns the most recent webhooks first."], }, { method: "GET", path: "/api/core/webhooks/history", auth: "API key", required: [], optional: ["limit (default 50, 1-200)", "offset (default 0)"], notes: ["Returns the most recent webhook deliveries first."], }, { method: "POST", path: "/api/core/webhooks/deliveries/{delivery_id}/redeliver", auth: "API key", required: ["delivery_id (path)"], optional: [], notes: ["Only failed deliveries can be redelivered."], }, { method: "POST", path: "/api/core/webhooks", auth: "API key", required: ["events (at least one) or event_urls (at least one)"], optional: ["url", "event_urls", "events"], notes: [ "Provide url for all selected events unless every event is mapped in event_urls.", "Events must be one of: invoice.created, invoice.payment_detected, invoice.confirmed, invoice.expired.", ], }, { method: "DELETE", path: "/api/core/webhooks/{webhook_id}", auth: "API key", required: ["webhook_id (path)"], optional: [], notes: ["Returns 204 on success."], }, { method: "POST", path: "/api/core/api-credentials/reset", auth: "API key", required: ["reset_api_key or reset_webhook_secret"], optional: [], notes: ["At least one reset flag must be true."], }, { method: "PATCH", path: "/api/core/profile", auth: "API key", required: [], optional: ["btcpay_checkout_style", "default_confirmation_target"], notes: [ "Set btcpay_checkout_style to standard or btcpay_classic.", "Applies to hosted invoice pages served for BTCPay compatibility.", ], }, { method: "POST", path: "/api/core/invoices", auth: "API key", required: ["amount_xmr or amount_fiat + currency"], optional: [ "confirmation_target (default 2, 0-10)", "checkout_continue_url (https URL, optional post-confirmation Continue button)", "metadata", "expires_at (ISO 8601)", ], notes: [ "Fiat conversion is non-binding and does not lock a rate.", "Creates a pending invoice and returns invoice details.", ], }, { method: "DELETE", path: "/api/core/invoices/{invoice_id}", auth: "API key", required: [], optional: [], notes: [ "Only pending, expired, or invalid invoices can be archived. Returns 204 on success.", ], }, { method: "GET", path: "/api/v1/stores", auth: "API key (token)", required: [], optional: [], notes: [ "Single-store per user: the list returns one store.", "Store ids map to the authenticated user id.", "Header format: Authorization: token .", ], }, { method: "GET", path: "/api/v1/stores/{store_id}", auth: "API key (token)", required: ["store_id (path)"], optional: [], notes: [ "Only the authenticated store id is valid.", "Returns the store metadata expected by Greenfield clients.", ], }, { method: "GET", path: "/api/v1/stores/{store_id}/payment-methods", auth: "API key (token)", required: ["store_id (path)"], optional: [], notes: ["Returns XMR-CHAIN as the only enabled payment method."], }, { method: "POST", path: "/api/v1/stores/{store_id}/invoices", auth: "API key (token)", required: ["store_id (path)", "amount", "currency"], optional: ["metadata", "checkout"], notes: [ "Fiat inputs are converted once at creation time and stored as metadata.", "Invoices are defined and payable in XMR only.", ], }, { method: "GET", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}", auth: "API key (token)", required: ["store_id (path)", "invoice_id (path)"], optional: [], notes: ["Returns BTCPay-compatible status fields."], }, { method: "GET", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}/payment-methods", auth: "API key (token)", required: ["store_id (path)", "invoice_id (path)"], optional: [], notes: [ "Read-only payment details derived from observed on-chain data.", "Returns a list even when only one payment method is enabled.", ], }, { method: "POST", path: "/api/v1/stores/{store_id}/invoices/{invoice_id}/status", auth: "API key (token)", required: ["store_id (path)", "invoice_id (path)", "status"], optional: [], notes: ["Only supports status=Invalid for compatibility; no funds move."], }, { method: "POST", path: "/api/v1/stores/{store_id}/webhooks", auth: "API key (token)", required: ["store_id (path)", "url", "authorizedEvents"], optional: ["enabled", "automaticRedelivery"], notes: [ "Deliveries include BTCPay-Sig: sha256=.", "Events follow BTCPay naming conventions for compatibility.", ], }, { method: "GET", path: "/api/v1/stores/{store_id}/webhooks", auth: "API key (token)", required: ["store_id (path)"], optional: [], notes: ["Returns the most recent webhooks first."], }, { method: "GET", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", auth: "API key (token)", required: ["store_id (path)", "webhook_id (path)"], optional: [], notes: ["Returns a single webhook registration."], }, { method: "PUT", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", auth: "API key (token)", required: ["store_id (path)", "webhook_id (path)"], optional: ["enabled", "automaticRedelivery", "url", "authorizedEvents"], notes: ["Updates a webhook registration."], }, { method: "DELETE", path: "/api/v1/stores/{store_id}/webhooks/{webhook_id}", auth: "API key (token)", required: ["store_id (path)", "webhook_id (path)"], optional: [], notes: ["Returns 204 on success."], }, { method: "GET", path: "/api/v1/server/info", auth: "API key (token)", required: [], optional: [], notes: ["Version is set for compatibility with refund UI behavior."], }, { method: "GET", path: "/api/v1/api-keys/current", auth: "API key (token)", required: [], optional: [], notes: [ "Permissions are store-scoped strings tied to one store id.", "Refund permissions are intentionally omitted.", ], }, ]; const requirementsByEndpoint = Object.fromEntries( endpointRequirements.map((entry) => [`${entry.method} ${entry.path}`, entry]) ); export default function DocsPage() { return (

Documentation

Monero checkout documentation & API reference.

xmrcheckout.com generates invoices, detects Monero payments using view-only access, and relays status events. Setup is explicit: bring your primary address and secret view key - never spend keys, never custody.

Customer → Merchant wallet No spend keys required OpenAPI schema available
Integration recipes Copy/paste examples for curl, Node, PHP, Python, and Go.

Start here

  1. 1) Sign in (view-only)

    Use your primary address + secret view key to retrieve an API key.

  2. 2) Create an invoice

    Call POST /api/core/invoices and redirect the customer to the hosted invoice URL.

  3. 3) Optional: configure webhooks

    Subscribe to invoice.confirmed to get status events pushed to your system.

  4. 4) Confirm and fulfill

    Wait for invoice.confirmed (webhook or polling). You can also check the public status endpoint, then mark your order paid.

See recipes OpenAPI JSON

Fiat inputs (optional)

You can request an invoice using a fiat amount. The backend converts the value to XMR using the current rate and returns a non-binding quote in the response. The invoice is always stored in XMR.

          {`POST /api/core/invoices
Authorization: ApiKey 
Content-Type: application/json

{
  "amount_fiat": "100.00",
  "currency": "USD",
  "confirmation_target": 2,
  "checkout_continue_url": "https://merchant.example/thanks",
  "metadata": { "order_id": "ORDER-1234" }
}`}
        

Core principles

All behavior is constrained by non-custodial rules.

  • Merchants retain wallet access (your keys, your funds)
  • View-only access maximum
  • No fiat rails or execution of financial actions

Subaddress-only invoices

Every invoice uses a subaddress derived from your primary address for clearer reconciliation.

  • Subaddress index shown on each invoice
  • Wallet lookahead controls discovery
  • Subaddresses cycle through indices 1 to 100
  • Wallets may require adding the index to view funds

Invoice lifecycle

Clear state transitions without hidden automation.

  • Pending → Payment detected → Confirmed
  • Pending → Expired
  • Pending → Invalid (manual)

Authentication

Two auth modes keep integrations explicit and observable.

  • API key for authenticated endpoints
  • Primary address + secret view key to retrieve API credentials
  • Headers: Authorization: ApiKey <api_key>
  • BTCPay compatibility: Authorization: token <api_key>

Webhook events

Observable events for merchant systems.

  • invoice.created
  • invoice.payment_detected
  • invoice.confirmed
  • invoice.expired
  • Deliveries include the X-Webhook-Secret header
  • Webhooks are optional; poll /api/core/public/invoice/<invoice_id> without auth

Base URL

The API can be reached from:

  • {normalizedSiteUrl}/api/core

BTCPay compatibility (WooCommerce)

A Greenfield-compatible subset keeps the official WooCommerce plugin working without custodial behavior.

  • Base URL uses /api/v1
  • Supported endpoints: stores, invoices, payment methods, webhooks
  • Invoices are defined in XMR; fiat inputs are quote-only metadata
  • Modal checkout is supported; the hosted invoice page is recommended for clarity

BTCPay webhooks

Deliveries are signed with the BTCPay header format.

  • BTCPay-Sig: sha256=<hmac>
  • Event names follow BTCPay conventions for compatibility.

API reference

Every endpoint exposed by the API is listed below, grouped by access mode.

{endpointGroups.map((group, index) => { const needsDivider = group.title.startsWith("BTCPay") && endpointGroups[index - 1] && !endpointGroups[index - 1].title.startsWith("BTCPay"); return ( {needsDivider ? ( ) : null}

{group.title}

Auth: {group.auth}

{group.items.length} endpoints
{group.items.map((endpoint) => { const requirements = requirementsByEndpoint[`${endpoint.method} ${endpoint.path}`]; return (
{endpoint.method}
{endpoint.path} {endpoint.description}
{requirements ? (
Requirements
Required: {" "} {requirements.required.length > 0 ? requirements.required.join(", ") : "None"}
{requirements.optional.length > 0 ? (
Optional: {" "} {requirements.optional.join(", ")}
) : null} {requirements.notes.length > 0 ? (
    {requirements.notes.map((note) => (
  • {note}
  • ))}
) : null}
) : null}
); })}
); })}
Need the machine-readable schema? OpenAPI JSON
); }