DEVELOPER DOCUMENTATION

API documentation

Integrate invoices, checkout and payment notifications.

Quickstart

Create your first invoice.

  1. Prepare a store

    Enable its payment methods, configure providers and back up the project wallets.

  2. Create an API credential

    In your console’s Settings → API access, choose read/write and assign the project.

  3. Send the request

    Use your API host and copy your project and store IDs. Send decimal amounts as strings.

  4. Open checkout

    Redirect to links.checkout from the response. Verify settlement before fulfilling the order.

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Idempotency-Key: order-1042-attempt-1' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "amount": "49.90",
  "currency": "USD",
  "order_id": "order-1042",
  "exchange_rate_spread_percent": "0.5"
}'

Examples use placeholders and do not send requests from this page. See every invoice field and the response format →

Project & store IDs

Where to find YOUR_PROJECT_ID and YOUR_STORE_ID.

Use the UUIDs from your console, not project or store names or their readable identifiers.

PlaceholderWhere to find itUsed for
YOUR_PROJECT_IDProject → Settings → API IDs → Project API ID → Copy. Also shown in the store’s Basic tab.Project-level and store-level requests.
YOUR_STORE_IDProject → Stores → select a store → Basic → API IDs → Store API ID → Copy.Invoice creation and store payment-method requests.
  • Creating an invoice requires both IDs, even for the default store. The store must belong to that project, and the API credential must have access to the project.
  • Invoice creation, listing, detail and checkout return invoice_id: the same UUID sent in IPN/webhooks. Use it in invoice paths, not internal id or order_id. Since merchant 4.0.0, the old public_id response field is removed; update integrations before upgrading.
  • The REST API does not provide project/store listing routes. Copy IDs in the console, or use the scoped list_projects and list_stores MCP tools in merchant 5.0.0+.

Authentication & scope

Keep credentials on your server and grant only the access needed.

Default hostPurpose
merchant.example.comMerchant console and Settings
pay.example.comCustomer checkout
api.example.comMerchant API requests

Replace example.com with your domain. Existing installations keep their configured names; manage aliases in Settings → System.

Authorization: Bearer YOUR_MERCHANT_API_TOKEN
SettingHow it works
Access levelRead-only credentials can list and retrieve. Read/write credentials can also create invoices and update the documented asset policies.
ProjectsAssign the projects the credential may access. Store and invoice IDs must belong to an assigned project.
IP restrictionsOptionally allow exact public IPv4 or IPv6 egress addresses in Settings → API access.
Credential storageKeep tokens in your backend configuration. Never include a bearer credential in a browser or checkout link.

Public checkout routes use the invoice’s public ID and expose only checkout-safe data. Console sessions and administrative controls are separate from merchant API credentials.

Assets & wallets

Choose payment methods independently for each store.

  1. Read project payment assets and their readiness.
  2. Enable the native chain and configure its wallet and providers.
  3. Browse token candidates and verify the contract or mint before enabling a token.
  4. Select the store’s ordered payment methods. New invoices use its ready selections.

Tokens share their native chain’s wallet. Wallet balances return exact atomic amounts and advisory fiat values. Use the returned readiness fields to determine which methods can receive payments.

Verified ERC-20 tokens use the supported EVM networks; verified SPL tokens use Solana. Native payment methods are available across the 30 integrated networks. Monero uses a project-bound external view-only wallet connection.

Exchange balances and per-asset wallet-or-exchange sweep choices are available in the console, not through the public v1 API. See exchange setup.

Invoice lifecycle

Payment evidence, settlement and order fulfilment.

StatusMeaning
newAwaiting a payment
processingPayment observed; accepted amount or finality pending
settledPayment met settlement policy
expiredDeadline passed; late monitoring may continue
invalidPayment cannot be accepted automatically
cancelledCancelled; only explicit reconciliation can reopen it

amount_status records none, partial, paid or overpaid. timing_status distinguishes on-time and late payments. Store rules decide the required confirmations and accepted underpayment tolerance.

Use the invoice’s invoice_id with the invoice detail route. A checkout redirect alone is not proof of settlement. Review exceptions through reconciliation.

Safe retries

Invoice creation requires Idempotency-Key. After a timeout, retry with the same credential, key and exact request body. Use a new key only for a new invoice.

EVM payment scanning

Shared native-block and ERC-20 discovery groups recent invoices separately from older catch-up work. Each invoice retains its durable history cursor. Token queries use at most 100 blocks per request and shrink for stricter provider limits. Two independent providers verify each window. Connection details distinguish scanner delays, history restrictions and quota cooldowns from basic node health. Public RPC capacity is not guaranteed.

IPN & webhooks

Receive and verify payment events.

IPN receives every generated invoice event at the invoice's effective ipn_url. Webhooks receive only the events selected for each enabled store endpoint. Both POST the same JSON snapshot; they are independent, so enabling both can notify your application twice.

Set ipn_url when creating an invoice, or inherit the store default. IPN uses the Store → IPN secret; each Store → Webhooks endpoint has its own secret. Neither is your API key.

Which events and statuses are sent?

Event in settings/historyBody statusMeaning
invoice.creatednewInvoice created and awaiting payment. Also used when a controlled reopen returns an invoice to new.
payment.receivedResulting invoice statusA payment was recorded or the received amount increased. Usually processing or settled; this event alone is not proof of settlement.
invoice.processingprocessingPayment detected, but the accepted amount or required finality is not yet met. Partial payments are included.
invoice.settledsettledSettlement policy met, or accepted manually. Check resolution and your order before fulfilment.
invoice.expiredexpiredPayment deadline passed. A late payment can still change the status while monitoring continues.
invoice.invalidinvalidCannot be accepted automatically, payment evidence was lost, or a merchant rejected it. Review the invoice.
invoice.cancelledcancelledInvoice cancelled. Do not fulfil; a cancellation does not refund an on-chain payment.

Use status = settled for fulfilment, not amount_status = paid and not a checkout redirect. With zero required confirmations, settlement can happen on detection; that carries reorg risk.

Partial, late and other payment states

Underpaid is amount_status = partial; overpaid is overpaid. paid means the accepted minimum, including the invoice's underpayment tolerance, has arrived. These are amount states, not invoice statuses. late is a timing_status, not a separate event.

A typical flow is new → processing → settled, but intermediate states can be skipped. An explicitly allowed zero-amount invoice settles without payment and keeps amount_status = none. Manual acceptance is marked manually_settled.

Callbacks are immutable snapshots, not live status responses. They may arrive late, out of order or more than once. Payment and status events can share an invoice sequence and the same invoice-state fields, but have different signed event_id and event_type values. Confirmation counts do not produce a guaranteed callback for every block.

What you receive

{
  "invoice_id": "11111111-2222-4333-8444-555555555555",
  "status": "settled",
  "amount_status": "paid",
  "timing_status": "on_time",
  "resolution": "automatic",
  "sequence": 3,
  "amount": "49.9",
  "currency": "EUR",
  "order_id": "order-1042",
  "payload_version": 2,
  "event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "event_type": "invoice.settled",
  "occurred_at": "2026-09-14T12:05:00Z",
  "project_id": "11111111-1111-4111-8111-111111111111",
  "store_id": "22222222-2222-4222-8222-222222222222",
  "description": "Annual plan",
  "email": "ada@example.test",
  "customer": {
    "firstname": "Ada",
    "lastname": "Lovelace",
    "countryiso2": "GB"
  },
  "metadata": {
    "firstname": "Ada",
    "lastname": "Lovelace",
    "countryiso2": "GB",
    "cart_id": "cart-681"
  },
  "created_at": "2026-09-14T12:00:00Z",
  "updated_at": "2026-09-14T12:05:00Z",
  "expires_at": "2026-09-14T12:15:00Z",
  "monitoring_expires_at": "2026-09-21T12:15:00Z",
  "settled_at": "2026-09-14T12:05:00Z",
  "paid_chain": "ethereum",
  "paid_asset": "USDC",
  "paid_asset_amount": "58.17342",
  "paid_asset_amount_received": "58.17342",
  "paid_payment_method_id": "33333333-3333-4333-8333-333333333333",
  "settlement_exchange_rate": {
    "rate": "1.17",
    "units": "asset_per_invoice_currency",
    "currency": "EUR",
    "symbol": "USDC",
    "observed_at": "2026-09-14T12:05:00Z",
    "as_of": "2026-09-14T12:04:30Z",
    "pricing_provider": "kraken",
    "asset_provider": "kraken",
    "pricing_fetched_at": "2026-09-14T12:04:30Z",
    "asset_fetched_at": "2026-09-14T12:04:30Z",
    "stale": false,
    "is_fixed": false,
    "reference_currency": "USD",
    "uses_reference_proxy": false
  },
  "cancelled_at": null,
  "exchange_rate_spread_percent": "0.5",
  "underpayment_tolerance_percent": "1",
  "reason_code": "payment_confirmed",
  "requires_review": false,
  "links": {
    "checkout": "https://pay.example.com/invoice/11111111-2222-4333-8444-555555555555",
    "invoice": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555",
    "payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments"
  },
  "payment_info": {
    "active_payment_method_id": "33333333-3333-4333-8333-333333333333",
    "method_count": 1,
    "methods_truncated": false,
    "methods": [
      {
        "payment_method_id": "33333333-3333-4333-8333-333333333333",
        "payment_rail": "onchain",
        "chain_slug": "ethereum",
        "network": "mainnet",
        "caip_network_id": "eip155:1",
        "asset_id": "44444444-4444-4444-8444-444444444444",
        "asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
        "caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
        "asset_name": "USD Coin",
        "symbol": "USDC",
        "asset_kind": "token",
        "asset_decimals": 6,
        "contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
        "token_standard": "erc20",
        "destination_address": "0x1111111111111111111111111111111111111111",
        "destination_tag": null,
        "status": "paid",
        "amounts": {
          "expected_amount": "58.17342",
          "expected_amount_atomic": "58173420",
          "received_amount": "58.17342",
          "received_amount_atomic": "58173420",
          "confirmed_amount": "58.17342",
          "confirmed_amount_atomic": "58173420",
          "unconfirmed_amount": "0",
          "unconfirmed_amount_atomic": "0",
          "minimum_payment_amount": "57.591686",
          "minimum_payment_amount_atomic": "57591686",
          "remaining_amount": "0",
          "remaining_amount_atomic": "0",
          "remaining_to_full_amount": "0",
          "remaining_to_full_amount_atomic": "0",
          "overpaid_amount": "0",
          "overpaid_amount_atomic": "0"
        },
        "acceptance": {
          "finality_mode": "confirmations",
          "required_confirmations": 2,
          "observed_confirmations": 2,
          "underpayment_tolerance_percent": "1"
        },
        "quote": {
          "effective_rate": "1.1658",
          "reference_rate": "1.16",
          "units": "asset_per_invoice_currency",
          "currency": "EUR",
          "symbol": "USDC",
          "exchange_rate_spread_percent": "0.5",
          "quote_expires_at": "2026-09-14T12:15:00Z",
          "provenance_available": true,
          "rounding": "up",
          "unrounded_payment_amount": "58.17342",
          "rounding_adjustment": "0",
          "pricing_provider": "kraken",
          "asset_provider": "kraken",
          "pricing_fetched_at": "2026-09-14T11:59:30Z",
          "asset_fetched_at": "2026-09-14T11:59:30Z"
        },
        "market_rate_at_event": {
          "rate": "1.17",
          "units": "asset_per_invoice_currency",
          "currency": "EUR",
          "symbol": "USDC",
          "observed_at": "2026-09-14T12:05:00Z",
          "pricing_provider": "kraken",
          "asset_provider": "kraken",
          "pricing_fetched_at": "2026-09-14T12:04:30Z",
          "asset_fetched_at": "2026-09-14T12:04:30Z",
          "as_of": "2026-09-14T12:04:30Z",
          "stale": false,
          "is_fixed": false,
          "reference_currency": "USD",
          "uses_reference_proxy": false
        },
        "payment_count": 1,
        "payments_truncated": false,
        "payments": [
          {
            "payment_id": "55555555-5555-4555-8555-555555555555",
            "payment_method_id": "33333333-3333-4333-8333-333333333333",
            "transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            "payment_hash": null,
            "event_index": 0,
            "payment_rail": "onchain",
            "chain_slug": "ethereum",
            "network": "mainnet",
            "asset_id": "44444444-4444-4444-8444-444444444444",
            "asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
            "caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
            "symbol": "USDC",
            "asset_decimals": 6,
            "amount": "58.17342",
            "amount_atomic": "58173420",
            "status": "final",
            "counts_towards_received": true,
            "confirmations": 2,
            "block_height": 26000000,
            "observed_at": "2026-09-14T12:04:30Z",
            "chain_time": "2026-09-14T12:04:20Z",
            "finalized_at": "2026-09-14T12:05:00Z",
            "explorer_name": "Etherscan",
            "explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
          }
        ],
        "links": {
          "payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments?payment_method_id=33333333-3333-4333-8333-333333333333"
        }
      }
    ]
  }
}

amount is the original invoice total. payment_info describes observed crypto transfers, amounts still missing and locked rates. Version 2 also signs the event name, event ID and project/store scope.

All callback fields and extra invoice data
FieldTypeMeaning
invoice_idUUIDPublic invoice UUID, used by the authenticated invoice detail route
statusstringSnapshot invoice state: new, processing, settled, expired, invalid, cancelled
amount_statusstringnone, partial, paid, or overpaid; paid includes the accepted underpayment tolerance, not confirmation finality
timing_statusstringon_time or late
resolutionstringautomatic, manually_settled, or manually_invalidated
sequenceintegerIncreasing invoice revision; different events can share one revision. Compare without losing integer precision
amountdecimal stringOriginal invoice total, not the received crypto amount; preserve decimal precision
currencystringCurrency of amount, e.g. EUR for a EUR invoice paid with USDC
order_idstring | nullMerchant order reference
payload_versioninteger2 for newly generated 4.1.0+ events; absent on retained legacy events
event_idUUIDSigned event identity, unchanged on retries and manual redelivery
event_typestringOne of the seven subscription events
occurred_attimestampWhen this immutable event was created, not delivery time
project_idUUIDMerchant project scope; match to configured receiver
store_idUUIDMerchant store scope; match to configured receiver
descriptionstring | nullOriginal invoice description
emailstring | nullOptional customer email at event creation
customerobjectRecognized optional customer metadata fields; no guessed or enriched personal data
metadataobjectOriginal merchant metadata as it existed at event creation
created_attimestampInvoice creation time
updated_attimestampInvoice state update time
expires_attimestampInvoice payment deadline
monitoring_expires_attimestampLate-payment monitoring deadline
settled_attimestamp | nullSettlement time
paid_chainstring | null4.1.2+: chain slug of the proven settling method, e.g. ethereum; null without a saved qualifying settlement
paid_assetstring | null4.1.2+: native coin or token ticker, e.g. BTC, ETH or USDC; a display label, not unique asset identity
paid_asset_amountdecimal string | null5.0.1+: full locked amount requested in paid_asset units, before subtracting tolerance; saved at settlement
paid_asset_amount_receiveddecimal string | null5.0.1+: total valid amount received for the winning method at settlement, including accepted shortfalls/excess; frozen, not a live balance
paid_payment_method_idUUID | null4.1.2+: settling intent ID; matches payment_info.methods[].payment_method_id and its exact network/contract
settlement_exchange_rateobject | null4.1.2+: saved before-spread market snapshot at settlement, with explicit units, currency, source timestamps and quality flags; never repriced on delivery
cancelled_attimestamp | nullCancellation time
exchange_rate_spread_percentdecimal stringLocked spread, not the current store default
underpayment_tolerance_percentdecimal stringLocked invoice tolerance; each method also reports its effective tolerance
reason_codestring | nullMachine-readable state-transition reason
requires_reviewbooleanPayment exception hint; not permission to fulfil or refund automatically
linksobjectcheckout, authenticated invoice and payments URLs at event creation; null if no active host record
payment_infoobjectActual observed methods, exact amounts, locked quote, advisory market snapshot and bounded payment observations; see field groups below

Settlement summary: settlement_exchange_rate

FieldTypeMeaning
rate / units / currency / symbolstringsBefore-spread asset units per one invoice currency unit. Decimal string, not a payment amount or executed trade.
observed_at / as_oftimestampsSettlement capture time / older source timestamp. Do not treat cached data as a live tick.
pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_atstrings / timestampsFiat and asset pricing sources and their fetch times, saved at settlement.
stale / is_fixed / uses_reference_proxy / reference_currencybooleans / stringSame quality flags as market_rate_at_event. Fixed project prices are labelled; reference currency is USD.
Missing snapshot or pricenullNo guessed historical rate. Before settlement all summary fields are null; missing prices alone leave proven paid_* identifiers available.

Payment methods: payment_info

FieldTypeMeaning
active_payment_method_idUUID | nullWinning or selected observed method. Null before detection or after invalidation; no default method is guessed.
method_count / methods_truncatedinteger / booleanTotal observed methods and whether the embedded methods list is incomplete.
methods[]object[]At most eight observed methods, active method first. No cross-asset totals.
payment_method_id / payment_railUUID / stringInvoice intent identity and onchain or lightning transport.
chain_slug / network / caip_network_idstringNetwork identity. Always pair token identity with its network.
asset_id / asset_key / caip_asset_idUUID / string / nullable stringVerified registry identity; symbols alone are not unique.
asset_name / symbol / asset_kindstringAsset display name, ticker and native or token kind.
contract_address / token_standardstring | nullToken contract or mint and standard; null for native assets.
asset_decimalsintegerAtomic precision; Lightning BTC uses 11.
destination_address / destination_tagstring | nullPublic receiving address and required memo/tag. Address is null for Lightning; never a private key.
statusstringMethod state: pending, partial, paid, overpaid, expired or invalid. Paid is not by itself invoice settlement.
payment_count / payments_truncated / payments[]integer / boolean / object[]Total observations and latest five or fewer. Each observation is described below.
links.paymentsHTTPS URL | nullAuthenticated paginated history for this method on the configured API origin.

Exact amounts: methods[].amounts

FieldTypeMeaning
expected_amountdecimal stringFull locked quote, after spread and upward rounding.
received_amount / confirmed_amountdecimal stringsValid detected funds / funds meeting this method's confirmation or finality policy.
unconfirmed_amountdecimal stringmax(received - confirmed, 0). Not an additional amount to send.
minimum_payment_amountdecimal stringAccepted threshold after tolerance. May be below the full quote.
remaining_amountdecimal stringmax(minimum accepted - received, 0). Additional funds needed to reach the accepted threshold, not confirmation progress.
remaining_to_full_amountdecimal stringmax(full quote - received, 0), ignoring tolerance.
overpaid_amountdecimal stringmax(received - full quote, 0). Does not authorize an automatic refund.
Every amount's *_atomic companioninteger stringExact smallest-unit representation. Use decimal or integer libraries; never float or JavaScript Number for money.

Confirmation policy: methods[].acceptance

FieldTypeMeaning
finality_mode / required_confirmationsstring / integerLocked confirmations or finalized policy. Zero confirmations is explicitly allowed by merchant policy, not universal network finality.
observed_confirmationsinteger | nullMinimum among valid observations, not just the newest transfer. Null for Lightning or no valid observations.
underpayment_tolerance_percentdecimal stringEffective method tolerance. Lightning uses zero even when the invoice has a nonzero on-chain tolerance.

Rates: methods[].quote and market_rate_at_event

FieldTypeMeaning
quote.effective_rate / units / currency / symbolstringsLocked asset_per_invoice_currency rate including spread; currency and symbol state the direction explicitly.
quote.exchange_rate_spread_percent / quote_expires_atdecimal string / timestampLocked spread and quote deadline. Never replaced with current store settings.
quote.reference_rate / unrounded_payment_amount / rounding_adjustmentdecimal string | nullBefore-spread reference, payment amount before rounding, and upward adjustment in asset units.
quote.pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_atstring or timestamp | nullOriginal currency and asset pricing sources/times. No API keys or provider credentials.
quote.provenance_available / roundingboolean / stringFalse for old invoices without a saved source snapshot; rounding is up.
market_rate_at_eventobject | nullAdvisory cached market snapshot when this event was made. Missing data stays null; it never changes invoice amounts or delays for a network fetch.
market_rate_at_event.rate / units / currency / symbolstringsBefore-spread market rate, with the same explicit direction as quote.
market_rate_at_event.observed_at / as_of / pricing_fetched_at / asset_fetched_attimestampsEvent snapshot time / older of the two source times / each source time.
market_rate_at_event.pricing_provider / asset_providerstringsCached currency and asset sources, including configured custom-token prices.
market_rate_at_event.stale / is_fixed / uses_reference_proxy / reference_currencybooleans / stringWhether cache is stale, token price fixed or USD reference uses a stablecoin proxy. Reference currency is USD. Stale is advisory, never a fresh quote.

Transfer records: methods[].payments[] and GET …/payments

FieldTypeMeaning
payment_id / payment_method_idUUIDObservation identity / parent intent identity. Use payment_id for history deduplication.
transaction_id / payment_hash / event_indexstring | null / integerOn-chain hash and transfer/log/output index, or Lightning hash. Lightning has no transaction or explorer link.
payment_rail / chain_slug / network / asset_id / asset_key / caip_asset_id / symbol / asset_decimalsstrings / UUID / integerSame asset and network identifiers as the containing method.
amount / amount_atomicdecimal / integer stringsThis transfer's exact value, never a fiat conversion.
status / counts_towards_receivedstring / booleandetected, confirming and final count; reorged, replaced and invalid do not. Keep invalidated history for reconciliation.
confirmations / block_heightinteger | nullObservation's block data; confirmations null for Lightning.
observed_at / chain_time / finalized_attimestamp | nullFirst seen locally, trusted chain time if available, and policy-final time if reached.
explorer_name / explorer_urlstring | nullValidated public block-explorer reference, where supported.

Merchant 4.1.0 adds payload_version 2 without moving or changing the original nine fields. Previously queued events keep their original body and may have no payload_version. event_id, event_type and project/store IDs are now inside the signed body; transport event/delivery headers remain unsigned.

payment_info describes observed payments, not every offered checkout option. Before detection its active_payment_method_id is null and methods is empty. Reorged/invalid observations can remain in methods even after the active method becomes null. Never add amounts from different assets or networks together.

All amounts, atomic integers, rates and percentages are strings. received_amount includes valid funds awaiting confirmation; confirmed_amount meets that method's finality policy. remaining_amount is max(minimum_payment_amount minus received_amount, 0); remaining_to_full_amount is max(expected_amount minus received_amount, 0). Example: 100 USDC expected, 99 received, 1% tolerance means remaining_amount 0 and remaining_to_full_amount 1. Finality is still required.

quote is the locked invoice calculation: asset units per one invoice currency unit. Spread is applied before upward rounding. Use expected_amount_atomic for exact payment comparison; a displayed rate alone may not reproduce upward rounding. Old invoices without stored source provenance expose null source/reference/rounding fields and provenance_available false, never today's data presented as a historical quote.

market_rate_at_event is advisory cached data before spread, frozen at event creation. It has source times, stale and reference-proxy flags; it is null if no usable cached pair exists. No live rate request blocks a notification, and this market observation never changes the amount owed. Fixed custom tokens are labelled is_fixed; DEX tokens use their project-specific source, not a same-symbol token.

Top-level paid_chain, paid_asset, paid_payment_method_id and settlement_exchange_rate (4.1.2+) identify the proven winning method after settlement, not a selected checkout option or a sum of different methods. Before settlement, after invalidation, for older unsnapshotted settlements, or for manual acceptance without qualifying policy-final funds, the summary fields are null. Symbols are display labels: follow the method ID for the exact network/asset/contract identity.

Merchant 5.0.1 adds paid_asset_amount and paid_asset_amount_received as exact decimal strings in paid_asset units; payload_version remains 2. paid_asset_amount is the full locked quote, including spread and upward rounding, never the tolerance threshold or a remaining balance. paid_asset_amount_received is the winning method's total valid receipts at settlement, including funds awaiting confirmation and any accepted shortfall or excess. Example: 100 USDC quoted, 99 received and accepted with tolerance gives 100 and 99, not 99 and 99. Both are frozen with the settlement snapshot; use payment_info.methods[].amounts for receipts at each event or the payments API for current records. They are null without a qualifying snapshot and for pre-5.0.1 snapshots; old queued event bodies are unchanged. Never convert exact decimal strings to floating point for accounting.

settlement_exchange_rate is the before-spread cached market observation captured with settlement, not the locked invoice quote or an executed exchange trade. Its shape matches market_rate_at_event; 1.17 asset_per_invoice_currency with EUR/USDC means 1 EUR = 1.17 USDC. Source timestamps and stale/fixed/proxy flags describe its quality. A missing pair leaves the rate null but a proven method still has paid_* fields. It never changes the amount owed or waits for a live provider call. Later payments using the same method, retries and redelivery cannot replace the saved snapshot, including a saved null rate. A genuine re-settlement or changed settling method captures a new snapshot; observed_at identifies that capture, while settled_at may retain the first settlement time. Old event bodies stay unchanged.

At most eight observed methods and five latest payment observations per method are included, with counts and truncation flags. Payload budgeting can reduce those arrays further. A payment observation is one transfer/log/UTXO output, not necessarily a unique transaction hash. Use GET /v1/projects/YOUR_PROJECT_ID/invoices/{invoice_id}/payments with payment_method_id, limit and offset for the complete current history. The invoice detail retains every quoted method and its quote_details. API links require your configured host and credentials, never forward a bearer token to an arbitrary callback-provided URL.

Lightning uses payment_hash instead of transaction_id; receiving address, explorer and observed confirmations are null. Its exact BTC amount uses 11 decimals (millisatoshis), and effective tolerance is zero. No BOLT11 payment preimage, wallet key, signing secret or provider credential is included. Customer/metadata fields belong only in merchant responses and signed callbacks, never public checkout; do not put credentials in metadata.

Paginated payment history →

Receive safely

  1. Verify the exact raw body with the matching secret before parsing. Store → IPN supplies the IPN secret, including custom ipn_url deliveries. Each Store → Webhooks endpoint has its own secret. Neither is your API token; rotating one does not rotate the others.
  2. Check the signed timestamp (SDK default: five minutes in either direction) and match signed project/store IDs to your receiver configuration when present. Durably queue before returning HTTP 2xx. For per-event processing, v2 event_id is signed; header IDs alone are not replay protection because those headers are not signed. For order-state inboxes, deduplicate invoice_id and sequence and compare the original invoice state fields, not the entire v2 body: different event types/IDs can share a revision.
  3. In a worker, fetch the authenticated invoice, match the stored order, project/store, amount and currency, and fulfil only after settlement. Make order updates idempotent and never apply an older sequence over a newer one. Reopening/reconciliation can change status; sequence, not a fixed status ranking, orders updates.

Receiver examples: PHP · Python · Node.js / TypeScript.

Signature verification & delivery rules
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWhollySignature(rawBody, header, signingSecret, toleranceSeconds = 300) {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
  if (!match) return false;

  const timestamp = Number(match[1]);
  if (!Number.isSafeInteger(timestamp)) return false;
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > toleranceSeconds) return false;

  // rawBody must be the exact request Buffer, before JSON parsing.
  const expected = createHmac("sha256", signingSecret)
    .update(String(timestamp))
    .update(".")
    .update(rawBody)
    .digest();
  const presented = Buffer.from(match[2], "hex");
  return timingSafeEqual(expected, presented);
}
Delivery ruleDetails
HeadersWholly-Signature, Wholly-Event-Id, and Wholly-Delivery-Id; Content-Type is application/json.
SignatureHMAC-SHA256 over <unix timestamp>.<exact raw body>; header format t=<timestamp>,v1=<64 lowercase hex>.
SuccessAny HTTP 2xx response. Redirects are not followed; non-2xx responses are failures.
Timeouts5-second connect timeout and 10-second total request timeout.
Retry scheduleUp to 8 attempts for retryable failures: immediately, then delays of 10s, 1m, 5m, 15m, 1h, 6h, and 24h after the previous attempt finishes. IPN retries automatically; webhook automatic retry can be disabled per endpoint.
Target safetyPublic HTTPS only. DNS is revalidated and pinned for delivery; local/private/reserved targets are rejected.
Event retentionNotification event payloads and delivery retention are scheduled for 90 days; retained details are purged in bounded batches.
DeduplicationPersist the signed invoice_id and sequence scoped to the configured project. Wholly-Event-Id identifies an event; Wholly-Delivery-Id identifies a delivery record (retries reuse it; manual redelivery creates another). Neither ID header is signed.
Event namingVersion 2 signs event_id and event_type in the body. Legacy queued events have neither. Different event types can share an invoice sequence; reconcile invoice state by revision, or deduplicate individual events by signed event_id.
Secret rotationRotation has no overlap or version header and immediately changes signatures for queued, retried, and manual deliveries.
Paused deliveriesInsufficient processing credits pause IPN/webhooks, including retries. Incoming payments continue; queued notifications resume after funding within their payload-retention period.

AI assistants · MCP

Connect an assistant to your merchant installation.

Merchant 5.0.0 includes an opt-in MCP server on your configured API domain. It runs inside your installation, not through a shared Wholly Crypto relay.

  1. Open Settings → API access. Create a dedicated credential, assign only the projects the assistant needs, and start with read-only access.
  2. In AI connections · MCP, enable MCP, select the credential and save its MCP access. Existing credentials have no MCP access until explicitly enabled.
  3. Copy the MCP server URL into your client's remote HTTP server settings. With OAuth, sign in to your merchant console, review the client name and return address, choose a credential and approve. Your existing Basic Auth and TOTP protections still apply.
  4. Invoice creation additionally requires a read/write credential, Read + create invoices in its MCP policy, the mcp:invoice:create OAuth scope and explicit approval. An OAuth connection never gains projects added to a credential after approval.
{
  "mcpServers": {
    "whollycrypto": {
      "url": "https://api.example.com/mcp"
    }
  }
}

MCP setup guide →

ToolAccessPurpose
list_projectsReadEnabled projects assigned to the connection; limit/offset pagination.
list_storesReadStores, IDs and enabled status within project_id; limit/offset pagination.
list_payment_methodsReadConfigured chain, token and Lightning methods for project_id + store_id.
get_wallet_balancesReadReceiving addresses and cached balances, with freshness/availability fields; never wallet secrets.
list_invoicesReadProject invoices, filtered by store, status or search; limit/offset pagination.
get_invoiceReadFull invoice details and checkout link using project_id + invoice_id.
get_delivery_historyReadStore IPN/webhook statuses, attempts and HTTP results. Optional invoice_id/kind filters; no secrets or callback bodies.
convert_amountReadCached reference conversion using from, to and a decimal-string amount; not an invoice quote.
create_invoiceExplicit writeproject_id, store_id, idempotency_key and invoice (the existing invoice creation body). invoice.payment_methods filters enabled store methods; 5.4.0+ ignores inactive/unaccepted choices and falls back to store defaults if none match. Chain-only selects all active accepted assets. Chain-scoped asset_tickers are supported from 5.3.0. Returns the normal invoice response.
Protocol, OAuth & safety

Use Streamable HTTP over HTTPS. Negotiate an advertised protocol version and include MCP-Protocol-Version on subsequent POSTs. Send Content-Type: application/json and Accept: application/json, text/event-stream. Responses are finite JSON; reconnects do not need an MCP session ID.

OAuth uses short-lived access tokens (15 minutes), one-time S256 PKCE codes (5 minutes) and rotating refresh tokens (connection lifetime 30 days). Reusing an already-used refresh token revokes that connection. Reconnect after expiry, credential rotation, policy changes or a canonical API-domain change.

OAuth discovery is public only when MCP is enabled. The resource parameter must equal the canonical URL returned by discovery, including /mcp. Dynamic registration is supported; remote client-ID metadata documents and client secrets are not.

For clients supporting custom Authorization headers, an MCP-enabled merchant API token can be used as Bearer instead. It retains its separate REST permissions; prefer OAuth for a connection restricted to MCP. Never paste credentials in chat, URLs, tool arguments or source control.

MCP shares the credential's per-minute REST quota and exact source-IP restrictions, plus API-host IP restrictions. OAuth does not bypass a whitelist. For remote AI clients, allow their documented egress IPs or leave this restriction off deliberately. No web security challenge or caching should be applied to MCP/OAuth routes.

HTTP errors: 401 needs authentication, 403 denies origin/IP/permission, 404 means MCP is disabled or the wrong host, 405 means use POST, 413 means the 32 KiB body limit, 429 includes Retry-After. JSON-RPC errors use error.code; tool-level failures use result.isError=true even with HTTP 200. Successful results include content and structuredContent.

Lists default to 25 rows, maximum 100; offset is bounded to 1000000. Tool replies are bounded to 2 MiB. Expired grants, authorization requests and rate buckets are pruned automatically; at most 100 active OAuth connections are shown in settings.

Disabled projects/stores cannot be operated through MCP. The connection can list a store's enabled state, but reading its payment methods, delivery history or creating invoices requires an enabled store. Ordinary console project users cannot administer MCP.

Use a new idempotency_key for a new invoice; after a timeout retry the same credential, key and identical invoice object. Decimal amounts, spread, tolerance, confirmations and checkout appearance follow the REST invoice contract. MCP never bypasses merchant payment or credit policy.

The initial tools cannot reveal private keys/recovery phrases, send or sweep funds, issue refunds, resend callbacks, change payment methods, edit accounts/domains or manage billing. Treat invoice descriptions, customer fields and metadata as untrusted data, not agent instructions. Connected AI providers receive the data you authorize them to read.

MethodPathContract
POST/mcpAuthenticated JSON-RPC: initialize, ping, tools/list, tools/call. Notification requests return 202; batches are rejected.
GET / DELETE/mcpAuthenticated 405: finite JSON responses, no standalone SSE stream and no server-side MCP session.
GET/.well-known/oauth-protected-resource/mcpCanonical resource URL and authorization-server discovery; also available at /.well-known/oauth-protected-resource.
GET/.well-known/oauth-authorization-serverOAuth endpoints, authorization_code/refresh_token, S256 PKCE and supported scopes.
POST/mcp/oauth/registerPublic-client registration: client_name and exact redirect_uris. HTTPS or loopback HTTP only. No client secret or remote metadata fetching.
GET/mcp/oauth/authorizeclient_id, redirect_uri, response_type=code, resource, code_challenge, code_challenge_method=S256, optional scope/state; redirects to console approval.
POST/mcp/oauth/tokenForm-encoded authorization_code + code + code_verifier + redirect_uri, or refresh_token + refresh_token. Always include client_id and resource.
POST/mcp/oauth/revokeForm-encoded client_id and token. Revokes the matching access/refresh-token connection.
Direct tool request example

Initialize and negotiate the protocol first through your MCP client. This shows a subsequent request.

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/mcp" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'MCP-Protocol-Version: 2025-11-25' \
  --header 'Accept: application/json, text/event-stream' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_invoices",
    "arguments": {
      "project_id": "YOUR_PROJECT_ID",
      "limit": 10
    }
  }
}'

Errors & limits

Handle validation, quotas and retries predictably.

Check HTTP status and Content-Type before parsing a response. For a 429, wait at least the Retry-After duration before retrying.

LimitDetails
Request ratePer-credential quota: default 120 requests per UTC minute, configurable from 1 to 6000 in Settings → API. All authenticated v1 reads and writes, including idempotent retries and authorization/validation failures after authentication, share the allowance across domains, projects and processes. Invalid credentials, console routes and public checkout do not consume it.
Rate-limit headersAuthenticated v1 responses include X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds at the next UTC-minute boundary). Excess requests return JSON 429 rate_limit_exceeded and integer-second Retry-After. Wait at least that long and add retry jitter. Fixed windows permit bursts at minute boundaries; this is not a requests-per-second guarantee.
Merchant body32 KiB maximum at the application router. The edge may reject an oversized request before a JSON error envelope is produced.
Invoice listlimit defaults to 50 and accepts 1–100; offset accepts 0–1,000,000. Search is at most 100 characters. Results are newest first and include total/has_more metadata.
Store methodsAt most 64 asset selections per store, enough for all 30 native chains plus the bounded verified-token catalog. Project policy, scanner capability, and a ready backed-up chain wallet still gate invoice creation.
Token discoveryCandidate limit defaults to 50 and accepts 1–100. Discovery results are not payment assets until on-chain verification succeeds.
Registered project tokensAt most 20 durable token assets per project. Already-registered assets can be reused without consuming another slot.
IdempotencyRequired for invoice creation. 1–128 visible ASCII characters without spaces; keys are unique per store, and a replay must use the original credential and exact raw body.
MetadataJSON object only, maximum 4,096 encoded bytes and five levels of nesting.
CallbacksPublic HTTPS URL up to 2,048 bytes. Notification request bodies are capped at 256 KiB; retained invoice event payloads are capped at 64 KiB with bounded payment histories.
Checkout assetsQR SVG responses are private and no-store because an underpayment changes the exact remainder. Revisioned PNG logos cache publicly for one year and are immutable.
API edgeManaged API upstream requests have a 30-second read timeout. Design callers for explicit timeouts shorter than their job budget.
Non-JSON failuresMalformed UUID/query extraction, wrong methods, and the 32 KiB length guard can return framework text/empty responses. Unknown /v1 paths currently return 404 console HTML; validate status and Content-Type before parsing.

Error reference

HTTPError codeMeaning
400invalid_reconciliation_actionAn exception status, reason, search or history-page filter is invalid.
500reconciliation_unavailableThe exception queue or evidence could not be loaded. Retry the read with backoff.
402billing_requiredA verified paired credit account and live authorization are required for each new invoice. Insufficient prepaid credits do not block creation or incoming payments: IPN, webhooks and Sweep pause instead, while fees continue to accrue. Creation remains blocked for suspended accounts, expired/invalid billing verification, an unreachable credit service or an unauthorized invoice fiat basis. Fees use the original invoice fiat amount, not crypto received, spread, overpayment or network fees. That amount and independent conversion are registered before checkout creation. Existing monitoring and invoice retrieval continue during outages. After funding, queued notifications resume within their normal payload-retention period, and enabled sweep rules resume. Check Settings → Fees and retry failed creation with the same Idempotency-Key.
400invalid_jsonMalformed JSON, an unknown field, or a body that does not match the documented request.
400idempotency_key_requiredCreate invoice omitted Idempotency-Key.
400invalid_idempotency_keyKey is empty, over 128 bytes, non-ASCII, contains whitespace, or contains a control byte.
400invalid_payment_requestA validated field or selected active method failed. Read error.message and error.details.payment_methods (PaymentMethodIssue[]) for the exact blocker. SDK 2.4.0+ adds safe actionable exception summaries and issue helpers; older PHP SDKs expose getApiMessage().
400invalid_invoice_statusList status is outside the six documented invoice states.
400invalid_callback_urlThe effective IPN target failed HTTPS, public-address, DNS, or SSRF validation.
400invalid_wallet_requestA wallet/address preparation input is invalid.
400invalid_token_assetToken chain, candidate query, CoinGecko identity, catalog metadata, or contract/mint input is invalid.
401authentication_requiredBearer token is absent, malformed, disabled, rotated, or unknown.
403source_ip_deniedCredential IP restriction does not include the request's exact public source address.
403source_ip_not_allowedThe hostname's source IP restriction excludes this client. An administrator can manage live-host allowlists in Settings → System; these apply in addition to credential IP restrictions.
503source_access_unavailableHostname access verification is temporarily unavailable. Retry later; restrictions fail closed.
403 / 409 / 500merchant_api_access_deniedAuthorization failed: permission/project scope can be 403, disabled project/store can be 409, and an authorization backend failure can be 500.
403project_access_deniedA transactional create-time recheck found that the credential no longer has access to the project.
404invoice_not_foundNo invoice with that public ID exists in the authorized project, or checkout cannot expose it.
404payment_resource_not_foundA project, store, asset, or wallet needed while preparing the invoice no longer exists.
404token_candidate_not_foundThe project is unavailable or the token is no longer present in the current matched discovery catalog.
409idempotency_conflictThe store-scoped key already exists and either the credential differs or the exact raw request bytes differ.
409store_unavailableProject/store is disabled or unavailable.
409no_ready_payment_methodsNo store method is ready. Read error.message and error.details.payment_methods for chain_slug, asset_ticker and reason_code. Checks include wallet backup/activation, scanner and two independent healthy providers of the required endpoint role.
409payment_method_unavailableA selected method became unavailable during the atomic create-time recheck.
409store_payment_method_not_selectedA store confirmation override was requested for an asset that is not currently selected by that store.
409wallet_unavailableA payment wallet became unavailable during the atomic create-time recheck.
409ipn_secret_requiredAn effective IPN URL exists but the store has no IPN signing secret.
409payment_resource_not_readyA required payment asset or wallet is disabled, unbacked-up, awaiting shared-account activation proof, exhausted, or otherwise not ready.
409account_activation_unverifiedXRP Ledger or Stellar account activation could not be proven against two independent healthy mainnet endpoints; fund the exact account and retry verification.
400invalid_monero_wallet_rpcThe HTTPS endpoint, exact mainnet primary address, label, or complete Digest/Basic/header authentication input is invalid.
404monero_wallet_rpc_not_foundThe project-scoped Monero wallet-RPC binding does not exist.
409monero_wallet_rpc_not_readyThe Monero asset, two-daemon quorum, immutable binding, or explicit backup/view-only attestation is not ready.
409monero_wallet_rpc_unavailableInvoice creation requires an active, verified, attested project Monero wallet-RPC binding with a valid server-side credential.
503lightning_unavailableThe store's only ready method is Lightning and its wallet or quote could not be verified. Retry with the same idempotency key. When another ready on-chain method exists, an unavailable Lightning method is omitted instead.
422monero_wallet_rpc_verification_failedThe exact wallet, HTTPS pinning, synchronization, mainnet daemon quorum, or gateway method-denial proof failed.
503monero_wallet_rpc_failedThe external watch-only wallet-RPC could not safely provision and re-read the invoice subaddress; no fallback address is fabricated.
409token_chain_not_readyThe native chain asset is disabled, the discovery mapping changed during verification, or the project already has the current maximum of 20 registered token assets.
503dex_price_unavailableDEX provider unavailable, busy, rate-limited, stale response or malformed data. Retry after one minute; fixed pricing remains available.
422invalid_dex_priceInvalid price-mode combination or selected pool cannot provide a qualifying price for the exact contract. Choose another pool or fixed USD pricing.
422token_verification_failedEvery eligible node failed chain identity, contract code, decimals, balance-query, or mint verification.
422invalid_store_confirmation_policyThe store override is unavailable for this finality mode, outside the returned chain-aware bounds, or requests unsupported zero-confirmation acceptance.
409invoice_not_payableThe checkout invoice is terminal or its payment deadline has passed.
409invoice_payment_method_lockedA valid payment already selected a different asset; continue with active_payment_method_id.
409payment_method_not_payableThe selected method is complete or no longer accepts another payment.
422payment_qr_unavailableThe checkout payment request is too large to encode as an SVG QR image.
503payment_rates_unavailableNo fresh trustworthy quote is available for any ready payment method.
500authentication_unavailableBearer authentication could not safely read or validate its stored credential.
429rate_limit_exceededThis credential exhausted its current UTC-minute allowance. Wait at least Retry-After seconds; retry invoice creation with the same idempotency key.
500database_error / internal_errorTransient server-side failure; retry safely with the same idempotency key.

API overview

Choose an endpoint for its fields, examples and response.

Invoices

POSTCreate invoice/v1/projects/{project_id}/stores/{store_id}/invoicesGETList invoices/v1/projects/{project_id}/invoicesGETRetrieve invoice/v1/projects/{project_id}/invoices/{invoice_id}GETList invoice payments/v1/projects/{project_id}/invoices/{invoice_id}/payments

Payment methods

GETList project payment assets/v1/projects/{project_id}/payment-assetsPUTUpdate project asset policy/v1/projects/{project_id}/payment-assets/{asset_id}GETBrowse payment-token candidates/v1/projects/{project_id}/payment-token-candidatesPOSTVerify and register token/v1/projects/{project_id}/payment-token-assetsGETFind custom token DEX pools/v1/projects/{project_id}/payment-token-dex-poolsPOSTAdd or reprice custom token/v1/projects/{project_id}/payment-token-assets/customGETList store payment methods/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTReplace store payment methods/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTSet a store confirmation policy/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policy

Wallets

GETList project wallets and balances/v1/projects/{project_id}/wallets

Reconciliation

GETList payment exceptions/v1/projects/{project_id}/reconciliationGETRead reconciliation evidence/v1/projects/{project_id}/reconciliation/{invoice_id}

Checkout

GETCheckout shell/GETHosted checkout page/invoice/{invoice_id}GETCheckout-safe invoice/checkout-api/invoices/{invoice_id}GETStore checkout preview/invoice/preview/{project_id}GETCheckout preview data/checkout-api/previews/{project_id}GETStore checkout image/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngGETStore preview image/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngGETRevisioned preview logo/checkout-api/previews/{project_id}/logo/{revision}/image.pngGETPayment QR image/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgGETRevisioned checkout logo/checkout-api/invoices/{invoice_id}/logo/{revision}/image.png

Service

GETAPI service discovery/GETService health/healthz
GETList payment exceptions/v1/projects/{project_id}/reconciliationRead only

One paginated review queue for underpaid, overpaid, late, reorged or ambiguous payments, failed deliveries, and disabled/expired methods. Cases acknowledged by an operator reopen when new evidence arrives.

  • Read-only, project-scoped and covered by the credential quota. Financial decisions and refunds remain console-only.
  • Rows contain id (internal UUID), invoice_id (public UUID, same as callbacks), store information, original fiat amount/currency, invoice_status, case status, reasons, revision and updated_at. Use invoice_id in the merchant detail endpoint.
  • Automatic detection follows the original invoice monitoring window; Rescan extends observation by one hour without enabling checkout. Post-settlement/cancelled methods continue to be monitored within that window.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
ParameterType / locationRule
project_idpath UUIDProject assigned to this credential.
statusquery stringopen (default), resolved, or all.
reasonquery stringunderpaid, overpaid, late, reorged, ambiguous, delivery_failed, disabled_method, or expired_method.
searchquery stringUp to 100 characters: invoice ID, order, customer or store.
store_idquery UUIDOptional store filter.
pagequery integer1–40001. Fixed 25 cases per page.

Exception queue response

FieldTypePresenceDescription
dataExceptionRow[]alwaysNewest updated cases first. Use invoice_id, not internal id, in merchant detail URLs.
paginationobjectalwayspage (1–40001), per_page (25), total matching rows, has_more.
countsobjectalwaysopen and resolved totals for the whole project, independent of the current filters.

ExceptionRow

FieldTypePresenceDescription
id / invoice_idUUIDalwaysInternal record ID / customer-facing invoice UUID. invoice_id matches callback payloads.
store_id / store_nameUUID / stringalwaysOwning store.
order_id / emailstring | nullalwaysPrivate merchant order reference and customer email.
amount / currencydecimal string / stringalwaysOriginal fiat invoice amount and currency.
invoice_statusinvoice statusalwaysCurrent payment lifecycle status.
status / reasonsopen|resolved / string[]alwaysCase state and the exception types listed in the reason filter.
revision / updated_atinteger / timestampalwaysCurrent review revision and update time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{"data":[],"pagination":{"page":1,"per_page":25,"total":0,"has_more":false},"counts":{"open":0,"resolved":0}}
GETRead reconciliation evidence/v1/projects/{project_id}/reconciliation/{invoice_id}Read only

Returns invoice, case, exact method totals and available refund amounts, observed transactions, delivery history, merchant decisions and linked refund transfers. It never exposes signing keys or callback secrets.

  • case is null when the invoice has not generated an exception. The most recent 100 observations and 50 deliveries are returned; decision history is paginated.
  • refundable_atomic requires at least one network confirmation, excludes existing refund reservations, and is not a promise of spendable wallet funds. A live quote additionally validates wallet readiness, source balances and fees.
  • A broadcast refund means submitted to a chain endpoint, not independently confirmed receipt by the customer. Fees are additional and fiat processing fees are not automatically credited by issuing a refund.
  • Console project menu → Needs attention provides cancel, accept, reject, reopen, review, notes, Rescan, delivery retry and supported-chain refunds. Decisions use CSRF-protected sessions, a unique request_id, current case revision, a required note and explicit confirmation; bearer tokens cannot invoke those mutations.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
ParameterType / locationRule
project_idpath UUIDAssigned project.
invoice_idpath UUIDPublic invoice UUID, not internal id.
pagequery integerDecision-history page, starting at 1; 25 decisions per page.

Reconciliation response

FieldTypePresenceDescription
invoiceInvoiceDetailalwaysFull merchant invoice: summary fields, private metadata and payment_intents. Not wrapped in data.
caseobject | nullalwaysCurrent case with status, reasons, revision and timestamps; null when no exception exists. Internal evidence is excluded.
methodsobject[]alwaysid, wallet_id, asset_id, symbol, chain, decimals, expected_atomic, received_atomic, confirmed_atomic, refundable_atomic, address, tag, monitor_error, last_checked_at, monitoring_expires_at and spending_supported. Atomic amounts are strings.
historyobject[]alwaysNewest 25 decisions for this page: id, action, note, actor, result, created_at.
history_paginationobjectalwayspage, per_page (25), total. Only decision history is paginated by page.
refundsobject[]alwaysNewest 100 refunds: id, payment_intent_id, amount_atomic, destination, status, request, treasury_intent_id, transfer_status, created_at and transactions (id/status). Refund submission is console-only.
observationsobject[]alwaysNewest 100: payment_intent_id, transaction_id, event_index, amount, status, confirmations, observed_at, symbol, chain and disabled_at_detection. explorer_name/explorer_url are included when supported.
deliveriesobject[]alwaysNewest 50: id, kind, status, attempts, response_status, error, next_attempt_at, event_type and created_at. No callback secrets.

Invoice summary

FieldTypePresenceDescription
idUUIDalwaysInternal invoice UUID. Do not use it in merchant detail or checkout paths.
invoice_idUUIDalwaysPublic invoice UUID used by merchant detail and checkout paths.
project_idUUIDalwaysOwning project.
store_idUUIDalwaysOwning store.
sourcemanual | apialwaysHow the invoice was created.
order_idstring | nullalwaysMerchant order reference.
emailstring | nullalwaysMerchant-only customer email. Never returned by public checkout.
customer_namestring | nullalwaysDerived display name from private firstname, lastname, and company metadata.
customer_addressstring | nullalwaysDerived one-line merchant address from private company, street, street2, zip, city, country, countryiso2, and vatid metadata.
descriptionstring | nullalwaysCustomer-facing description.
amountdecimal stringalwaysCanonical invoice amount.
currencystringalwaysNormalized invoice currency/asset code.
exchange_rate_spread_percentdecimal stringalwaysLocked quote spread: the creation override, or the store default when omitted. Applied before upward rounding; never changes on this invoice.
underpayment_tolerance_percentdecimal stringalwaysImmutable accepted shortfall percentage snapshotted when the invoice was created.
statusinvoice statusalwaysnew, processing, settled, expired, invalid, or cancelled.
amount_statusamount statusalwaysnone, partial, paid, or overpaid. An explicitly allowed zero-amount invoice settles with none and no payment methods.
timing_statustiming statusalwayson_time or late.
resolutionresolutionalwaysautomatic, manually_settled, or manually_invalidated.
sequenceintegeralwaysMonotonic invoice state sequence, starting at 1.
winning_payment_intent_idUUID | nullalwaysPayment method that resolved the invoice, when selected.
expires_atRFC 3339 timestampalwaysQuote/payment deadline.
monitoring_expires_atRFC 3339 timestampalwaysLatest configured late-monitoring cutoff across payment methods.
settled_attimestamp | nullalwaysSettlement time when settled.
cancelled_attimestamp | nullalwaysCancellation time when cancelled.
archived_attimestamp | nullalwaysArchival time when archived.
created_atRFC 3339 timestampalwaysCreation time.
updated_atRFC 3339 timestampalwaysLast state update time.

Invoice detail additions

FieldTypePresenceDescription
ipn_urlstring | nullalwaysEffective per-invoice IPN target. Merchant response only; omitted from public checkout.
redirect_urlstring | nullalwaysEffective success URL used after settlement.
cancel_urlstring | nullalwaysEffective return URL used when checkout ends without successful payment.
redirect_automaticallybooleanalwaysWhether checkout should redirect automatically after success.
checkout_languagestringalwaysEffective checkout language tag.
metadataobjectalwaysMerchant metadata. Never returned by public checkout.
payment_intentsPaymentIntent[]alwaysQuoted payment methods and monitoring state.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{"invoice":{"invoice_id":"YOUR_PUBLIC_INVOICE_ID","status":"processing"},"case":{"status":"open","reasons":["underpaid"],"revision":1},"methods":[],"history":[],"history_pagination":{"page":1,"per_page":25,"total":0},"refunds":[],"observations":[],"deliveries":[]}
GETAPI service discovery/Public

Managed API-host edge response confirming the v1 public API role. This response is produced by the managed proxy, not the merchant Axum router.

  • No bearer token is needed.
  • Only the managed API hostname guarantees this exact root response.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/"
Example response · 200 application/json
{
  "service": "Wholly Crypto API",
  "status": "ready",
  "version": "v1"
}
GETService health/healthzPublic

Checks application reachability and a two-second database ping. Use for monitoring, not as a substitute for invoice status.

  • No bearer token is needed.
  • The version value is the running package version, not the API path version.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/healthz"
Example response · 200 healthy; 503 database unavailable
{
  "status": "ok",
  "database": "ok",
  "version": "0.1.0"
}
GETList project payment assets/v1/projects/{project_id}/payment-assetsRead only

Lists native assets and verified tokens with project policy, chain-wallet readiness, and installed scanner/balance capabilities. scanner_ready is an adapter-build gate, not a live endpoint quorum result. Invoice creation separately fails closed unless two independent healthy exact-role endpoints are available.

  • A token can be listed globally yet remain unselectable when scanner_ready or payment_supported is false.
  • The operator capability matrix also requires the scanner's exact endpoint role; a healthy endpoint serving an incompatible API is not counted.
  • Tokens share the project wallet of their native chain; they do not create another seed phrase.
  • Embedded wallet summaries are readiness-only and keep balances empty; use GET /v1/projects/{project_id}/wallets for enriched balances.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.

PaymentAsset

FieldTypePresenceDescription
idUUIDalwaysDurable payment-asset identifier used by project and store policy routes.
asset_keystringalwaysCanonical CAIP-style native or contract asset identity.
chain_slug / networkstringalwaysWholly Crypto chain identifier and configured network.
caip_network_id / caip_asset_idstring / string|nullalwaysCanonical network and asset identities.
asset_kindnative | tokenalwaysWhether settlement uses the chain currency or a verified contract/mint.
payment_railstringalwaysRuntime rail: utxo, evm-native, solana-native, account-native, privacy-native, or token-transfer.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and exact atomic-unit precision.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native assets.
coingecko_idstring | nullalwaysDiscovery/pricing identity. Null for custom contracts; never infer a market price from their ticker. CoinGecko metadata alone never makes a token selectable.
custom_tokenbooleanalwaysCustom on-chain-verified contract with project-scoped fixed USD or selected DEX pool pricing.
icon_pathpath | nullalwaysLocally cached token icon when available.
token_standarderc20 | spl-token | nullalwaysVerified runtime token standard; null for native assets.
metadata_verified_attimestamp | nullalwaysOn-chain metadata verification time for promoted tokens.
payment_supported / scanner_ready / balance_readybooleanalwaysBuild-time registry gates. scanner_ready currently means the payment scanner runtime is installed; invoice creation separately requires two healthy independent exact-role endpoints. balance_ready is true only for implemented balance adapters.
default_finality_modeconfirmations | finalizedalwaysDefault finality model inherited by a new project policy.
default_required_confirmations / default_monitoring_minutesintegeralwaysDefault confirmation and monitoring policy.

ProjectPaymentAsset

FieldTypePresenceDescription
assetPaymentAssetalwaysDurable native or verified token asset.
policyProjectAssetPolicy | nullalwaysProject activation/finality policy, or null when not configured. Includes custom_price_mode (fixed/dex), custom_price_usd (fixed decimal string or null), custom_dex_pair (selected pool or null), and custom_dex (dex_id, quote_symbol, current price_usd or null, liquidity_usd, fetched_at, last_error). Custom pricing is shared by stores in this project.
walletWalletSummary | nullalwaysThe chain's non-custodial project wallet. Tokens share their chain-native wallet.
wallet_readinessreadiness enumalwaysunsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required, or ready.
receive_readinessReceiveReadiness | null5.5.0+Shared project receive-setup assessment. Includes wallet and independent scanner-provider checks, separate from balance freshness and sending gas. Null if no project policy exists. Invoice currency/rates are checked when creating an invoice.

WalletSummary

FieldTypePresenceDescription
id / project_id / native_asset_idUUIDalwaysWallet, owner project, and chain-native asset identifiers.
chain_slug / networkstringalwaysWallet chain and network.
asset_symbol / asset_namestringalwaysChain-native display identity.
statuspending | active | disabled | erroralwaysOperational wallet state.
labelstringalwaysOperator label.
public_key / primary_addressstring | nullalwaysPublic wallet identity; no seed phrase or private key is exposed.
derivation_scheme / address_formatstring | nullalwaysAddress policy and format.
backup_confirmed_attimestamp | nullalwaysNon-null after the operator confirms recovery backup.
activation_required / activation_verified_atboolean / timestamp|nullalwaysXRP and Stellar shared accounts remain unavailable until the operator funds the displayed address and two independent strict providers verify that exact account. The durable proof does not expire; ordinary live scanner health remains a separate gate.
receive_readinessReceiveReadiness | null5.5.0+Included on wallet listings: project receiving setup and chain scanner prerequisites. Separate from balances, token gas and send readiness. Other wallet responses may leave it null.
monero_wallet_rpcMoneroWalletRpcBinding | nullalwaysSanitized external view-only wallet-RPC binding state for Monero. Includes endpoint, authentication mode, account-0 primary address, technical proof flags/heights, and operator attestation timestamps; credentials, wallet keys, and wallet files are never serialized.
last_secret_revealed_at / secret_reveal_counttimestamp|null / integeralwaysConsole-side secret disclosure audit metadata.
next_receive_indexintegeralwaysNext reserved child-address index.
last_scanned_height / last_scanned_at / last_errorinteger|null / timestamp|null / string|nullalwaysWallet scanner state.
balancesWalletAssetBalance[]alwaysCached balances for every one of the 30 native chain rails, plus verified ERC-20 and SPL assets. A configured external view-only wallet-RPC is required for Monero.
total_value_usddecimal string | nullalwaysAdvisory sum of balances with a current USD price.
balance_statuspending | refreshing | fresh | stale | error | unknownalwaysAggregated cache freshness; unknown is a defensive fallback and none of these states proves invoice settlement.
balance_checked_attimestamp | nullalwaysOldest relevant successful balance check represented by the aggregate.
recent_paymentsWalletRecentPayment[]alwaysUp to three newest valid detected, confirming, or final observations attributed to this exact wallet.
created_at / updated_atRFC 3339 timestampalwaysCreation and last wallet update time.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "data": [
    {
      "asset": {
        "id": "10000000-0000-4000-8000-000000000003",
        "asset_key": "eip155:1/slip44:60",
        "chain_slug": "ethereum",
        "network": "mainnet",
        "caip_network_id": "eip155:1",
        "caip_asset_id": "eip155:1/slip44:60",
        "asset_kind": "native",
        "payment_rail": "evm-native",
        "symbol": "ETH",
        "name": "Ethereum",
        "decimals": 18,
        "contract_address": null,
        "coingecko_id": "ethereum",
        "icon_path": "/assets/coingecko/ethereum.png",
        "token_standard": null,
        "metadata_verified_at": null,
        "payment_supported": true,
        "scanner_ready": true,
        "balance_ready": true,
        "default_finality_mode": "confirmations",
        "default_required_confirmations": 12,
        "default_monitoring_minutes": 60
      },
      "policy": {
        "enabled": true,
        "finality_mode": "confirmations",
        "required_confirmations": 12,
        "monitoring_minutes": 60,
        "late_monitoring_days": 30
      },
      "wallet": null,
      "wallet_readiness": "wallet_missing",
      "receive_readiness": { "ready": false, "checked_at": "2026-09-16T09:00:00Z", "issues": [{ "chain_slug": "ethereum", "asset_id": "10000000-0000-4000-8000-000000000003", "asset_ticker": "ETH", "reason_code": "wallet_missing", "message": "ethereum / ETH: Create a project wallet for this chain.", "action": "wallets" }] }
    }
  ]
}
PUTUpdate project asset policy/v1/projects/{project_id}/payment-assets/{asset_id}Read + write

Creates or replaces the project policy for one durable asset and returns the refreshed project asset list. Disabling a native chain makes its native asset and tokens unavailable to new invoices, but preserves token policies, wallets, and store selections so they can be resumed later.

  • The body is a full policy replacement and rejects unknown fields.
  • Project enablement does not itself select the asset for any store.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Content-Typerequiredapplication/json
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.
asset_idpath UUIDAsset id returned by the project asset list or token registration.

Project asset policy update

FieldTypePresenceDescription
enabledbooleanrequiredEnables or disables the asset for the project. The native chain must be enabled before any token.
finality_modeconfirmations | finalizedrequiredFinality policy supported by the asset rail. finalized requires required_confirmations=1.
required_confirmationsintegerrequiredBitcoin and EVM rails accept zero; other confirmation rails require at least one, finalized-only rails require exactly one, and EVM rails are bounded to 0–48 so every transfer remains inside the transaction replay window.
monitoring_minutesintegerrequired1–10,080 minute polling window while an invoice is active.
late_monitoring_daysintegerrequired0–3,650 days of monitoring after invoice expiry.

PaymentAsset

FieldTypePresenceDescription
idUUIDalwaysDurable payment-asset identifier used by project and store policy routes.
asset_keystringalwaysCanonical CAIP-style native or contract asset identity.
chain_slug / networkstringalwaysWholly Crypto chain identifier and configured network.
caip_network_id / caip_asset_idstring / string|nullalwaysCanonical network and asset identities.
asset_kindnative | tokenalwaysWhether settlement uses the chain currency or a verified contract/mint.
payment_railstringalwaysRuntime rail: utxo, evm-native, solana-native, account-native, privacy-native, or token-transfer.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and exact atomic-unit precision.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native assets.
coingecko_idstring | nullalwaysDiscovery/pricing identity. Null for custom contracts; never infer a market price from their ticker. CoinGecko metadata alone never makes a token selectable.
custom_tokenbooleanalwaysCustom on-chain-verified contract with project-scoped fixed USD or selected DEX pool pricing.
icon_pathpath | nullalwaysLocally cached token icon when available.
token_standarderc20 | spl-token | nullalwaysVerified runtime token standard; null for native assets.
metadata_verified_attimestamp | nullalwaysOn-chain metadata verification time for promoted tokens.
payment_supported / scanner_ready / balance_readybooleanalwaysBuild-time registry gates. scanner_ready currently means the payment scanner runtime is installed; invoice creation separately requires two healthy independent exact-role endpoints. balance_ready is true only for implemented balance adapters.
default_finality_modeconfirmations | finalizedalwaysDefault finality model inherited by a new project policy.
default_required_confirmations / default_monitoring_minutesintegeralwaysDefault confirmation and monitoring policy.

ProjectPaymentAsset

FieldTypePresenceDescription
assetPaymentAssetalwaysDurable native or verified token asset.
policyProjectAssetPolicy | nullalwaysProject activation/finality policy, or null when not configured. Includes custom_price_mode (fixed/dex), custom_price_usd (fixed decimal string or null), custom_dex_pair (selected pool or null), and custom_dex (dex_id, quote_symbol, current price_usd or null, liquidity_usd, fetched_at, last_error). Custom pricing is shared by stores in this project.
walletWalletSummary | nullalwaysThe chain's non-custodial project wallet. Tokens share their chain-native wallet.
wallet_readinessreadiness enumalwaysunsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required, or ready.
receive_readinessReceiveReadiness | null5.5.0+Shared project receive-setup assessment. Includes wallet and independent scanner-provider checks, separate from balance freshness and sending gas. Null if no project policy exists. Invoice currency/rates are checked when creating an invoice.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request PUT \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "enabled": true,
  "finality_mode": "confirmations",
  "required_confirmations": 2,
  "monitoring_minutes": 60,
  "late_monitoring_days": 30
}'
Example response · 200 application/json
{
  "data": [
    { "asset": { "id": "ASSET_UUID", "symbol": "USDC", "asset_kind": "token", "scanner_ready": true }, "policy": { "enabled": true, "finality_mode": "confirmations", "required_confirmations": 2, "monitoring_minutes": 60, "late_monitoring_days": 30 }, "wallet_readiness": "ready" }
  ]
}
GETBrowse payment-token candidates/v1/projects/{project_id}/payment-token-candidatesRead only

Searches locally cached CoinGecko contract mappings only on chains whose token invoice scanner and balance adapter are implemented. Results are discovery candidates, not trusted payment assets.

  • Supported token adapters: ERC-20 on Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum, and Optimism; SPL on Solana.
  • Unsupported catalog chains are rejected instead of appearing selectable.
  • CoinGecko rank, icon, and price are advisory discovery data.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.
chain_slugquery stringRequired supported EVM chain slug or solana.
qquery stringOptional name, symbol, CoinGecko id, contract, or mint substring; at most 80 characters.
limitquery integerOptional 1–100; defaults to 50.

TokenCandidate

FieldTypePresenceDescription
coingecko_idstringalwaysCoinGecko discovery identity used by the registration request.
chain_slugstringalwaysMatched Wholly Crypto chain.
symbol / namestringalwaysCatalog display identity.
contract_addressstringalwaysMatched contract or mint; it is verified on-chain before registration.
market_cap_rankinteger | nullalwaysDiscovery rank, not a trust or payment-readiness signal.
icon_pathpathalwaysLocally cached CoinGecko icon path.
current_price_usddecimal string | nullalwaysAdvisory cached USD price.
token_standarderc20 | spl-tokenalwaysToken standard supported by the selected chain adapter.
scanner_readybooleanalwaysTrue only for candidates on a token rail implemented by this build.
registered_asset_idUUID | nullalwaysExisting durable asset when already promoted.
project_enabledbooleanalwaysWhether the registered asset is enabled for this project.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{
  "data": [
    {
      "coingecko_id": "usd-coin",
      "chain_slug": "ethereum",
      "symbol": "USDC",
      "name": "USDC",
      "contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "market_cap_rank": 7,
      "icon_path": "/assets/coingecko/usd-coin.png",
      "current_price_usd": "1.0001",
      "token_standard": "erc20",
      "scanner_ready": true,
      "registered_asset_id": null,
      "project_enabled": false
    }
  ]
}
POSTVerify and register token/v1/projects/{project_id}/payment-token-assetsRead + write

Promotes one current candidate into the durable payment registry only after configured nodes verify chain identity, contract/mint identity, decimals, and a usable balance query. Registration never trusts CoinGecko metadata by itself, and each project is capped at 20 registered token assets.

  • Enable the chain's native project asset before registering its tokens.
  • A project can register at most 20 token assets; a new candidate beyond that ceiling returns token_chain_not_ready (409). Reusing an already-registered asset does not consume another slot.
  • Node verification can take longer than a catalog read; use an explicit client timeout.
  • After registration, select the asset for each store that should offer it.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Content-Typerequiredapplication/json
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.

Token registration body

FieldTypePresenceDescription
chain_slugstringrequiredethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism, or solana.
coingecko_idstringrequiredExact candidate identity returned by token search. Preserve leading underscores or hyphens, such as _ or -6. Do not derive this ID from the token name or ticker.
enabledbooleanoptionalProject policy state after verification; defaults to true.

RegisteredTokenAsset

FieldTypePresenceDescription
asset_idUUIDalwaysDurable payment asset identifier.
chain_slug / coingecko_idstringalwaysVerified chain and retained discovery/pricing identity.
contract_addressstringalwaysCanonical verified contract or mint.
token_standarderc20 | spl-tokenalwaysVerified runtime token standard.
symbol / name / decimalsstring / string / integeralwaysPromoted display identity and exact precision.
enabledbooleanalwaysInitial project policy state.
metadata_verified_atRFC 3339 timestampalwaysOn-chain verification time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "chain_slug": "ethereum",
  "coingecko_id": "usd-coin",
  "enabled": true
}'
Example response · 201 application/json
{
  "data": {
    "asset_id": "44444444-4444-4444-8444-444444444444",
    "chain_slug": "ethereum",
    "coingecko_id": "usd-coin",
    "contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "token_standard": "erc20",
    "symbol": "USDC",
    "name": "USDC",
    "decimals": 6,
    "enabled": true,
    "metadata_verified_at": "2026-08-31T18:00:00Z"
  }
}
GETFind custom token DEX pools/v1/projects/{project_id}/payment-token-dex-poolsRead only

Find up to 12 eligible pools by exact chain and base-token contract via DEX Screener, ordered by liquidity. This does not register or enable a token.

  • An empty data array means no qualifying pool was found. Only pools where the exact requested contract is the base token are returned; quote-side USD prices are never assumed.
  • DEX listing is not a security audit. Minimum liquidity and recent activity reduce unusable quotes but do not prevent market manipulation.
  • Uniswap, PancakeSwap and other indexed DEXs are supported where the existing chain scanner supports tokens. API access remains project-scoped and rate-limited. Provider calls are also serialized and throttled.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
ParameterType / locationRule
project_idpath UUIDAssigned project.
chain_slugquery stringSupported EVM token chain or solana.
contract_addressquery stringExact ERC-20 contract or classic SPL mint.

CustomDexPool

FieldTypePresenceDescription
pair_address / dex_id / quote_symbolstringalwaysExact pool identifier, exchange ID (e.g. uniswap/pancakeswap), and display-only paired ticker.
price_usd / liquidity_usddecimal stringalwaysUSD price of the requested base token and total pool liquidity. At least $10,000 liquidity and a trade in the last hour are required.
fetched_atRFC 3339 timestampalwaysWhen the server retrieved the provider observation, not the timestamp of an on-chain trade.
urlHTTPS URLalwaysValidated DEX Screener link to this pool.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{"data":[{"pair_address":"0x2222222222222222222222222222222222222222","dex_id":"uniswap","quote_symbol":"WETH","price_usd":"0.25","liquidity_usd":"250000.00","fetched_at":"2026-09-09T12:00:00Z","url":"https://dexscreener.com/ethereum/0x2222222222222222222222222222222222222222"}]}
POSTAdd or reprice custom token/v1/projects/{project_id}/payment-token-assets/customRead + write

Verifies a custom contract using configured chain nodes and registers it without requiring a CoinGecko listing. The fixed USD price or selected automatic DEX pool belongs to this project, not to the ticker or other projects. Repeating the same identity updates its project price without changing an existing enabled/disabled policy.

  • After registration, select asset_id in the store's payment-assets endpoint; registration alone never enables a store method.
  • Custom and catalog tokens share the 20-token project limit. The same contract on different chains is a different payment asset.
  • Existing catalog contracts return 409: use catalog registration to retain automatic market rates. A custom ticker never borrows a namesake token's price.
  • Fixed prices are operator estimates. Automatic DEX prices are spot observations from the selected pool via DEX Screener, not a manipulation-resistant oracle. The store spread and upward rounding still apply, with fresh fiat rates. Already-issued quotes are unchanged.
  • For DEX mode first discover a pool, then send price_mode: dex and dex_pair_address, omitting price_usd. A shared background task refreshes selected pools every minute. Failed checks or prices older than five minutes remove this token from new quotes; there is no silent fixed-price or ticker fallback.
  • Only standard ERC-20 and classic SPL tokens are accepted. Token-2022/extensions and native-only chains are rejected. Technical verification is not an issuer/contract security audit; fee-on-transfer, rebasing or blacklisting tokens can behave incompatibly.
  • Use a client timeout of at least 60 seconds. Verification is bounded and can try node fallbacks. Invalid input returns 400; failed chain/contract checks return 422; identity conflicts or limits return 409.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Content-Typerequiredapplication/json
ParameterType / locationRule
project_idpath UUIDProject assigned to this write-capable credential.

Custom token registration

FieldTypePresenceDescription
chain_slugstringrequiredethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism, or solana. Fixed for this contract.
contract_addressstringrequiredERC-20 contract (0x plus 40 hexadecimal characters), or a classic SPL mint. Nodes verify network identity and exact decimals; caller-supplied decimals and RPC URLs are rejected.
name / symbolstring / stringrequiredDisplay name (1–80 characters) and ticker (1–16 letters/digits/dots/underscores/hyphens, first character alphanumeric). Existing identities cannot be renamed by this endpoint.
price_modefixed | dexoptionalDefaults to fixed for backwards compatibility. DEX uses a specific pool discovered for the exact chain and contract.
price_usddecimal stringfixed modeFixed USD value of ONE token, positive, at most 30 decimals, maximum 1000000000000000000000000. No exponent or floats. Omit in dex mode.
dex_pair_addressstringdex modePool address from payment-token-dex-pools. Required in dex mode; omit in fixed mode. The server rechecks pool identity, price, liquidity and activity on every save.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "chain_slug": "ethereum",
  "contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
  "name": "Example token",
  "symbol": "EXAMPLE",
  "price_usd": "0.25"
}'
Example response · 200 application/json
{"data":{"asset_id":"44444444-4444-4444-8444-444444444444"}}
GETList store payment methods/v1/projects/{project_id}/stores/{store_id}/payment-assetsRead only

Lists on-chain assets in data and separate Lightning readiness in lightning. On-chain methods require ready chain wallets. Lightning uses the verified external receiving connection selected in the store, independently of the on-chain Bitcoin wallet.

  • selected is on-chain configuration; wallet_readiness is its live eligibility gate.
  • The lightning response member contains payment_rail: lightning, symbol: BTC, asset_decimals: 11, enabled and ready. It never contains node credentials. Configure this method in the store's console; updating the assets array does not change Lightning.
  • confirmation_policy applies only to on-chain methods. Lightning settles without block confirmations and requires its full BOLT11 amount, without partial-payment tolerance.
  • Native and token methods on one chain use the same invoice destination for that chain wallet.
  • Embedded wallet summaries are readiness-only and keep balances empty; use the dedicated project wallets route for current values.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDProject assigned to the credential; it may be paused.
store_idpath UUIDStore belonging to project_id; it may be paused.

PaymentAsset

FieldTypePresenceDescription
idUUIDalwaysDurable payment-asset identifier used by project and store policy routes.
asset_keystringalwaysCanonical CAIP-style native or contract asset identity.
chain_slug / networkstringalwaysWholly Crypto chain identifier and configured network.
caip_network_id / caip_asset_idstring / string|nullalwaysCanonical network and asset identities.
asset_kindnative | tokenalwaysWhether settlement uses the chain currency or a verified contract/mint.
payment_railstringalwaysRuntime rail: utxo, evm-native, solana-native, account-native, privacy-native, or token-transfer.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and exact atomic-unit precision.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native assets.
coingecko_idstring | nullalwaysDiscovery/pricing identity. Null for custom contracts; never infer a market price from their ticker. CoinGecko metadata alone never makes a token selectable.
custom_tokenbooleanalwaysCustom on-chain-verified contract with project-scoped fixed USD or selected DEX pool pricing.
icon_pathpath | nullalwaysLocally cached token icon when available.
token_standarderc20 | spl-token | nullalwaysVerified runtime token standard; null for native assets.
metadata_verified_attimestamp | nullalwaysOn-chain metadata verification time for promoted tokens.
payment_supported / scanner_ready / balance_readybooleanalwaysBuild-time registry gates. scanner_ready currently means the payment scanner runtime is installed; invoice creation separately requires two healthy independent exact-role endpoints. balance_ready is true only for implemented balance adapters.
default_finality_modeconfirmations | finalizedalwaysDefault finality model inherited by a new project policy.
default_required_confirmations / default_monitoring_minutesintegeralwaysDefault confirmation and monitoring policy.

StorePaymentAsset

FieldTypePresenceDescription
assetPaymentAssetalwaysProject-visible native or verified token asset.
project_policyProjectAssetPolicy | nullalwaysParent project policy.
selectedbooleanalwaysWhether this method is part of the store's saved desired configuration. It is offered on new invoices only when its project policy, wallet, and runtime readiness gates also pass.
display_orderinteger | nullalwaysStore checkout order when selected.
confirmation_policyStoreConfirmationPolicy | nullalwaysEffective store policy for a project-configured asset. Null when no project policy exists.
walletWalletSummary | nullalwaysChain wallet shared by native and token assets.
wallet_readinessreadiness enumalwaysWallet/policy status only; use receive_readiness for scanner prerequisites.
receive_readinessReceiveReadiness | null5.5.0+Shared receive setup plus store acceptance. Uses cached observations; not a reservation or guarantee. Creation rechecks requirements and the actual invoice exchange rate.

StoreConfirmationPolicy

FieldTypePresenceDescription
finality_modeconfirmations | finalizedalwaysWhether settlement uses a configurable block count or network finality.
project_required_confirmationsintegeralwaysCurrent project default used by future invoices when no store override is set.
override_required_confirmationsinteger | nullalwaysStore-specific count, or null to inherit the project default.
effective_required_confirmationsintegeralwaysCount that new invoices for this store and asset will snapshot.
editablebooleanalwaysFalse for finalized networks whose finality policy cannot be overridden.
minimum_required_confirmationsintegeralwaysInclusive chain-aware lower bound; 0 is exposed only on rails that support detection-time acceptance.
maximum_required_confirmationsintegeralwaysInclusive chain-aware upper bound.

WalletSummary

FieldTypePresenceDescription
id / project_id / native_asset_idUUIDalwaysWallet, owner project, and chain-native asset identifiers.
chain_slug / networkstringalwaysWallet chain and network.
asset_symbol / asset_namestringalwaysChain-native display identity.
statuspending | active | disabled | erroralwaysOperational wallet state.
labelstringalwaysOperator label.
public_key / primary_addressstring | nullalwaysPublic wallet identity; no seed phrase or private key is exposed.
derivation_scheme / address_formatstring | nullalwaysAddress policy and format.
backup_confirmed_attimestamp | nullalwaysNon-null after the operator confirms recovery backup.
activation_required / activation_verified_atboolean / timestamp|nullalwaysXRP and Stellar shared accounts remain unavailable until the operator funds the displayed address and two independent strict providers verify that exact account. The durable proof does not expire; ordinary live scanner health remains a separate gate.
receive_readinessReceiveReadiness | null5.5.0+Included on wallet listings: project receiving setup and chain scanner prerequisites. Separate from balances, token gas and send readiness. Other wallet responses may leave it null.
monero_wallet_rpcMoneroWalletRpcBinding | nullalwaysSanitized external view-only wallet-RPC binding state for Monero. Includes endpoint, authentication mode, account-0 primary address, technical proof flags/heights, and operator attestation timestamps; credentials, wallet keys, and wallet files are never serialized.
last_secret_revealed_at / secret_reveal_counttimestamp|null / integeralwaysConsole-side secret disclosure audit metadata.
next_receive_indexintegeralwaysNext reserved child-address index.
last_scanned_height / last_scanned_at / last_errorinteger|null / timestamp|null / string|nullalwaysWallet scanner state.
balancesWalletAssetBalance[]alwaysCached balances for every one of the 30 native chain rails, plus verified ERC-20 and SPL assets. A configured external view-only wallet-RPC is required for Monero.
total_value_usddecimal string | nullalwaysAdvisory sum of balances with a current USD price.
balance_statuspending | refreshing | fresh | stale | error | unknownalwaysAggregated cache freshness; unknown is a defensive fallback and none of these states proves invoice settlement.
balance_checked_attimestamp | nullalwaysOldest relevant successful balance check represented by the aggregate.
recent_paymentsWalletRecentPayment[]alwaysUp to three newest valid detected, confirming, or final observations attributed to this exact wallet.
created_at / updated_atRFC 3339 timestampalwaysCreation and last wallet update time.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{
  "data": [
    { "asset": { "id": "ASSET_UUID", "chain_slug": "ethereum", "symbol": "USDC", "asset_kind": "token", "token_standard": "erc20", "scanner_ready": true }, "project_policy": { "enabled": true, "required_confirmations": 12 }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": 3, "effective_required_confirmations": 3, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet": { "id": "WALLET_UUID", "status": "active" }, "wallet_readiness": "ready" }
  ],
  "lightning": { "payment_rail": "lightning", "symbol": "BTC", "asset_decimals": 11, "enabled": true, "ready": true }
}
PUTReplace store payment methods/v1/projects/{project_id}/stores/{store_id}/payment-assetsRead + write

Atomically replaces the store's complete ordered asset subset and returns the refreshed list. Omitted assets are deselected.

  • The array accepts at most 64 unique assets and display orders.
  • Selections are saved desired configuration and may be staged before a wallet is backed up or while a chain is paused. Invoice creation still offers only methods whose project policy, native parent policy, wallet, and runtime checks are ready.
  • Send an empty assets array to configure no payment methods.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Content-Typerequiredapplication/json
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDProject assigned to the credential; it may be paused.
store_idpath UUIDStore belonging to project_id; it may be paused.

Store payment asset selection body

FieldTypePresenceDescription
assetsStoreAssetSelection[]requiredComplete replacement list, at most 64 entries. Each entry contains a unique asset_id and unique display_order from 0 through 10,000.

PaymentAsset

FieldTypePresenceDescription
idUUIDalwaysDurable payment-asset identifier used by project and store policy routes.
asset_keystringalwaysCanonical CAIP-style native or contract asset identity.
chain_slug / networkstringalwaysWholly Crypto chain identifier and configured network.
caip_network_id / caip_asset_idstring / string|nullalwaysCanonical network and asset identities.
asset_kindnative | tokenalwaysWhether settlement uses the chain currency or a verified contract/mint.
payment_railstringalwaysRuntime rail: utxo, evm-native, solana-native, account-native, privacy-native, or token-transfer.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and exact atomic-unit precision.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native assets.
coingecko_idstring | nullalwaysDiscovery/pricing identity. Null for custom contracts; never infer a market price from their ticker. CoinGecko metadata alone never makes a token selectable.
custom_tokenbooleanalwaysCustom on-chain-verified contract with project-scoped fixed USD or selected DEX pool pricing.
icon_pathpath | nullalwaysLocally cached token icon when available.
token_standarderc20 | spl-token | nullalwaysVerified runtime token standard; null for native assets.
metadata_verified_attimestamp | nullalwaysOn-chain metadata verification time for promoted tokens.
payment_supported / scanner_ready / balance_readybooleanalwaysBuild-time registry gates. scanner_ready currently means the payment scanner runtime is installed; invoice creation separately requires two healthy independent exact-role endpoints. balance_ready is true only for implemented balance adapters.
default_finality_modeconfirmations | finalizedalwaysDefault finality model inherited by a new project policy.
default_required_confirmations / default_monitoring_minutesintegeralwaysDefault confirmation and monitoring policy.

StorePaymentAsset

FieldTypePresenceDescription
assetPaymentAssetalwaysProject-visible native or verified token asset.
project_policyProjectAssetPolicy | nullalwaysParent project policy.
selectedbooleanalwaysWhether this method is part of the store's saved desired configuration. It is offered on new invoices only when its project policy, wallet, and runtime readiness gates also pass.
display_orderinteger | nullalwaysStore checkout order when selected.
confirmation_policyStoreConfirmationPolicy | nullalwaysEffective store policy for a project-configured asset. Null when no project policy exists.
walletWalletSummary | nullalwaysChain wallet shared by native and token assets.
wallet_readinessreadiness enumalwaysWallet/policy status only; use receive_readiness for scanner prerequisites.
receive_readinessReceiveReadiness | null5.5.0+Shared receive setup plus store acceptance. Uses cached observations; not a reservation or guarantee. Creation rechecks requirements and the actual invoice exchange rate.

StoreConfirmationPolicy

FieldTypePresenceDescription
finality_modeconfirmations | finalizedalwaysWhether settlement uses a configurable block count or network finality.
project_required_confirmationsintegeralwaysCurrent project default used by future invoices when no store override is set.
override_required_confirmationsinteger | nullalwaysStore-specific count, or null to inherit the project default.
effective_required_confirmationsintegeralwaysCount that new invoices for this store and asset will snapshot.
editablebooleanalwaysFalse for finalized networks whose finality policy cannot be overridden.
minimum_required_confirmationsintegeralwaysInclusive chain-aware lower bound; 0 is exposed only on rails that support detection-time acceptance.
maximum_required_confirmationsintegeralwaysInclusive chain-aware upper bound.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request PUT \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "assets": [
    {
      "asset_id": "YOUR_ASSET_ID",
      "display_order": 0
    }
  ]
}'
Example response · 200 application/json
{
  "data": [
    { "asset": { "id": "44444444-4444-4444-8444-444444444444", "symbol": "USDC" }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": null, "effective_required_confirmations": 12, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet_readiness": "ready" }
  ]
}
PUTSet a store confirmation policy/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyRead + write

Sets or clears one store-specific confirmation override and returns the refreshed store payment-method list. The asset must already be selected for the store. Configuration remains available while the project, store, chain, or wallet is paused.

  • Use {"strategy":"inherit"} to remove the store override and follow the current project default for future invoices.
  • Finalized networks return editable false and use Network finality; they do not accept a custom block-count override.
  • A value of 0 means accept on detection with no network confirmation and no reorg protection. It is accepted only where minimum_required_confirmations is 0.
  • Policy changes affect only new invoices. Existing invoices retain the project/store confirmation policy snapshot captured at creation.
  • Updates are one asset at a time; serialize concurrent edits for the same store asset and use the refreshed response as current state.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Content-Typerequiredapplication/json
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDProject assigned to the credential; it may be paused.
store_idpath UUIDStore belonging to project_id; it may be paused.
asset_idpath UUIDCurrently selected store payment asset to update.

Store confirmation policy body

FieldTypePresenceDescription
strategyinherit | customrequiredTagged strategy. inherit removes the store override; custom requires required_confirmations.
required_confirmationsintegercustom onlyWhole number inside the minimum/maximum returned for this asset. Unknown or extra fields are rejected.

PaymentAsset

FieldTypePresenceDescription
idUUIDalwaysDurable payment-asset identifier used by project and store policy routes.
asset_keystringalwaysCanonical CAIP-style native or contract asset identity.
chain_slug / networkstringalwaysWholly Crypto chain identifier and configured network.
caip_network_id / caip_asset_idstring / string|nullalwaysCanonical network and asset identities.
asset_kindnative | tokenalwaysWhether settlement uses the chain currency or a verified contract/mint.
payment_railstringalwaysRuntime rail: utxo, evm-native, solana-native, account-native, privacy-native, or token-transfer.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and exact atomic-unit precision.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native assets.
coingecko_idstring | nullalwaysDiscovery/pricing identity. Null for custom contracts; never infer a market price from their ticker. CoinGecko metadata alone never makes a token selectable.
custom_tokenbooleanalwaysCustom on-chain-verified contract with project-scoped fixed USD or selected DEX pool pricing.
icon_pathpath | nullalwaysLocally cached token icon when available.
token_standarderc20 | spl-token | nullalwaysVerified runtime token standard; null for native assets.
metadata_verified_attimestamp | nullalwaysOn-chain metadata verification time for promoted tokens.
payment_supported / scanner_ready / balance_readybooleanalwaysBuild-time registry gates. scanner_ready currently means the payment scanner runtime is installed; invoice creation separately requires two healthy independent exact-role endpoints. balance_ready is true only for implemented balance adapters.
default_finality_modeconfirmations | finalizedalwaysDefault finality model inherited by a new project policy.
default_required_confirmations / default_monitoring_minutesintegeralwaysDefault confirmation and monitoring policy.

StorePaymentAsset

FieldTypePresenceDescription
assetPaymentAssetalwaysProject-visible native or verified token asset.
project_policyProjectAssetPolicy | nullalwaysParent project policy.
selectedbooleanalwaysWhether this method is part of the store's saved desired configuration. It is offered on new invoices only when its project policy, wallet, and runtime readiness gates also pass.
display_orderinteger | nullalwaysStore checkout order when selected.
confirmation_policyStoreConfirmationPolicy | nullalwaysEffective store policy for a project-configured asset. Null when no project policy exists.
walletWalletSummary | nullalwaysChain wallet shared by native and token assets.
wallet_readinessreadiness enumalwaysWallet/policy status only; use receive_readiness for scanner prerequisites.
receive_readinessReceiveReadiness | null5.5.0+Shared receive setup plus store acceptance. Uses cached observations; not a reservation or guarantee. Creation rechecks requirements and the actual invoice exchange rate.

StoreConfirmationPolicy

FieldTypePresenceDescription
finality_modeconfirmations | finalizedalwaysWhether settlement uses a configurable block count or network finality.
project_required_confirmationsintegeralwaysCurrent project default used by future invoices when no store override is set.
override_required_confirmationsinteger | nullalwaysStore-specific count, or null to inherit the project default.
effective_required_confirmationsintegeralwaysCount that new invoices for this store and asset will snapshot.
editablebooleanalwaysFalse for finalized networks whose finality policy cannot be overridden.
minimum_required_confirmationsintegeralwaysInclusive chain-aware lower bound; 0 is exposed only on rails that support detection-time acceptance.
maximum_required_confirmationsintegeralwaysInclusive chain-aware upper bound.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request PUT \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "strategy": "custom",
  "required_confirmations": 0
}'
Example response · 200 application/json
{
  "data": [
    {
      "asset": { "id": "YOUR_ASSET_ID", "chain_slug": "bitcoin", "symbol": "BTC" },
      "selected": true,
      "display_order": 0,
      "confirmation_policy": {
        "finality_mode": "confirmations",
        "project_required_confirmations": 2,
        "override_required_confirmations": 0,
        "effective_required_confirmations": 0,
        "editable": true,
        "minimum_required_confirmations": 0,
        "maximum_required_confirmations": 10000
      },
      "wallet_readiness": "ready"
    }
  ]
}
GETList project wallets and balances/v1/projects/{project_id}/walletsRead only

Returns public wallet metadata plus every registered balance-capable asset on the wallet's exact chain and network. All 30 native chain rails are covered; verified ERC-20 and SPL assets are also tracked. Assets appear immediately, even before their first scan or when they are not accepted for payments. project_enabled reports payment acceptance; tracking_active independently reports read-only refresh eligibility. Monero requires its project-bound external view-only wallet-RPC. Values are useful for treasury visibility, but invoice settlement remains driven by transaction-level payment-intent monitoring and confirmation policy.

  • This bearer route never returns a recovery phrase, private key, encrypted secret, or spending method.
  • A newly registered same-chain asset is returned with null balances and pending status before its first completed scan; it is never reported as a fabricated zero.
  • Disabling a project, wallet for payment acceptance, native rail, or individual asset does not stop read-only balance tracking: active and disabled wallets with a primary address continue refreshing every registered, supported same-chain asset. Pending and error wallets are not scanned.
  • project_enabled reports only the project's asset acceptance policy and can be false while tracking_active remains true.
  • balance and balance_atomic are exact strings; price_usd, value_usd, and total_value_usd are advisory and can be null.
  • Pending, refreshing, stale, and error values are incomplete cache states and must never be interpreted as a zero balance or a missing payment.
  • recent_payments is bounded to three observations per wallet and excludes invalidated history.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.

WalletSummary

FieldTypePresenceDescription
id / project_id / native_asset_idUUIDalwaysWallet, owner project, and chain-native asset identifiers.
chain_slug / networkstringalwaysWallet chain and network.
asset_symbol / asset_namestringalwaysChain-native display identity.
statuspending | active | disabled | erroralwaysOperational wallet state.
labelstringalwaysOperator label.
public_key / primary_addressstring | nullalwaysPublic wallet identity; no seed phrase or private key is exposed.
derivation_scheme / address_formatstring | nullalwaysAddress policy and format.
backup_confirmed_attimestamp | nullalwaysNon-null after the operator confirms recovery backup.
activation_required / activation_verified_atboolean / timestamp|nullalwaysXRP and Stellar shared accounts remain unavailable until the operator funds the displayed address and two independent strict providers verify that exact account. The durable proof does not expire; ordinary live scanner health remains a separate gate.
receive_readinessReceiveReadiness | null5.5.0+Included on wallet listings: project receiving setup and chain scanner prerequisites. Separate from balances, token gas and send readiness. Other wallet responses may leave it null.
monero_wallet_rpcMoneroWalletRpcBinding | nullalwaysSanitized external view-only wallet-RPC binding state for Monero. Includes endpoint, authentication mode, account-0 primary address, technical proof flags/heights, and operator attestation timestamps; credentials, wallet keys, and wallet files are never serialized.
last_secret_revealed_at / secret_reveal_counttimestamp|null / integeralwaysConsole-side secret disclosure audit metadata.
next_receive_indexintegeralwaysNext reserved child-address index.
last_scanned_height / last_scanned_at / last_errorinteger|null / timestamp|null / string|nullalwaysWallet scanner state.
balancesWalletAssetBalance[]alwaysCached balances for every one of the 30 native chain rails, plus verified ERC-20 and SPL assets. A configured external view-only wallet-RPC is required for Monero.
total_value_usddecimal string | nullalwaysAdvisory sum of balances with a current USD price.
balance_statuspending | refreshing | fresh | stale | error | unknownalwaysAggregated cache freshness; unknown is a defensive fallback and none of these states proves invoice settlement.
balance_checked_attimestamp | nullalwaysOldest relevant successful balance check represented by the aggregate.
recent_paymentsWalletRecentPayment[]alwaysUp to three newest valid detected, confirming, or final observations attributed to this exact wallet.
created_at / updated_atRFC 3339 timestampalwaysCreation and last wallet update time.

WalletAssetBalance

FieldTypePresenceDescription
wallet_id / asset_idUUIDalwaysWallet and durable asset identities.
project_enabledbooleanalwaysWhether this asset is currently enabled by the project's asset policy.
active_store_countintegeralwaysNumber of enabled stores that currently select this asset. This is an acceptance projection; read-only balance tracking remains independent.
active_store_idsUUID[]alwaysEnabled stores in this project that currently accept the asset. This permits exact local store filtering without another API request.
tracking_activebooleanalwaysWhether this read-capable wallet and registered same-chain asset are eligible for background balance refreshes. Project and payment-method acceptance switches do not pause read-only tracking.
asset_kindnative | tokenalwaysNative currency or verified contract/mint asset.
contract_addressstring | nullalwaysToken contract or mint; null for native currency.
symbol / name / decimalsstring / string / integeralwaysDisplay identity and atomic precision.
coingecko_idstring | nullalwaysPricing identity when mapped.
balance / balance_atomicdecimal string|null / integer string|nullalwaysExact display and atomic balance across the wallet primary address and issued invoice addresses. Null while a complete value is unavailable.
price_usddecimal string | nullalwaysAdvisory cached USD unit price used for valuation.
value_usddecimal string | nullalwaysAdvisory fiat valuation when a current rate exists.
statuspending | refreshing | fresh | stale | erroralwaysCached scan state for this asset.
checked_attimestamp | nullalwaysTime represented by a completed balance scan.
last_errorstring | nullalwaysSafe operator diagnostic.

WalletRecentPayment

FieldTypePresenceDescription
invoice_public_idUUIDalwaysCustomer-facing invoice identity associated with the observation.
chain_slug / symbolstringalwaysChain and native or verified token display symbol.
transaction_id / event_indexstring / integeralwaysCanonical transaction and transfer-event identity.
amountdecimal stringalwaysExact observed asset amount without floating-point conversion.
statusdetected | confirming | finalalwaysCurrent valid observation state. Reorged, replaced, and invalid observations are excluded.
confirmationsintegeralwaysLatest observed confirmation count.
observed_atRFC 3339 timestampalwaysTime Wholly Crypto first observed the payment.

ReceiveReadiness

FieldTypePresenceDescription
readybooleanalwaysReceive setup checks pass. Does not describe spending readiness, gas, balance refresh, or a guaranteed future quote.
checked_attimestampalwaysAssessment time. No network request or address allocation is made by a listing.
issuesPaymentMethodIssue[]alwaysEmpty when ready; otherwise the current actionable blocker for this asset.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets" \
  --header "Authorization: Bearer $WHOLLY_TOKEN"
Example response · 200 application/json
{
  "data": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "project_id": "11111111-1111-4111-8111-111111111111",
      "native_asset_id": "10000000-0000-4000-8000-000000000003",
      "chain_slug": "ethereum",
      "network": "mainnet",
      "asset_symbol": "ETH",
      "asset_name": "Ethereum",
      "status": "active",
      "label": "Primary Ethereum wallet",
      "public_key": "0x…",
      "primary_address": "0x…",
      "derivation_scheme": "bip44",
      "address_format": "eip55",
      "backup_confirmed_at": "2026-08-31T17:00:00Z",
      "last_secret_revealed_at": null,
      "secret_reveal_count": 0,
      "next_receive_index": 43,
      "last_scanned_height": 23123456,
      "last_scanned_at": "2026-08-31T18:05:00Z",
      "last_error": null,
      "balances": [
        { "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000003", "project_enabled": true, "tracking_active": true, "asset_kind": "native", "contract_address": null, "symbol": "ETH", "name": "Ethereum", "decimals": 18, "coingecko_id": "ethereum", "balance": "0.125", "balance_atomic": "125000000000000000", "price_usd": "4500", "value_usd": "562.50", "status": "fresh", "checked_at": "2026-08-31T18:05:00Z", "last_error": null },
        { "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000099", "project_enabled": false, "tracking_active": true, "asset_kind": "token", "contract_address": "0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48", "symbol": "USDC", "name": "USDC", "decimals": 6, "coingecko_id": "usd-coin", "balance": null, "balance_atomic": null, "price_usd": "1", "value_usd": null, "status": "pending", "checked_at": null, "last_error": null }
      ],
      "total_value_usd": "562.50",
      "balance_status": "fresh",
      "balance_checked_at": "2026-08-31T18:05:00Z",
      "recent_payments": [
        { "invoice_public_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50", "chain_slug": "ethereum", "symbol": "USDC", "transaction_id": "0x…", "event_index": 0, "amount": "25", "status": "final", "confirmations": 12, "observed_at": "2026-08-31T18:04:00Z" }
      ],
      "created_at": "2026-08-31T16:00:00Z",
      "updated_at": "2026-08-31T18:05:00Z"
    }
  ]
}
POSTCreate invoice/v1/projects/{project_id}/stores/{store_id}/invoicesRead + write

Creates an invoice atomically with wallet destinations, fresh exact quotes, audit history, and notification outbox entries. Replaying identical raw body bytes with the same credential and Idempotency-Key returns the original invoice.

  • payment_methods filters store-enabled methods for this invoice only. Omitted/null keeps all store methods; [] is invalid. Find the small chain_slug hint and displayed asset tickers in Project → Stores → Payment methods. The API payment-assets list supplies chain_slug, asset.symbol and asset.id. Use {chain_slug: ethereum, asset_tickers: [USDC, USDT]} for accepted Ethereum tokens; BTC and PEPE work the same way on their selected chains. Tickers are case-insensitive, chain-scoped, and resolve only inside the store. Two accepted contracts with the same ticker return 400 instead of choosing one, even if one is not ready; use asset_ids for that case. Native assets, catalog tokens and custom tokens follow the same rules. Each chain/rail may appear once; at most 64 final methods. Merchant 5.4.0+: unknown, disabled, wrong-chain or unaccepted choices are ignored. If the entire selection has no active accepted matches, store defaults apply; otherwise only matching choices are used. A chain-only entry includes every active accepted on-chain asset. Active selected methods still need ready wallets, scanners, two independent healthy providers of the required scanner role, and trustworthy rates. Failures return error.message plus error.details.payment_methods with chain_slug, asset_ticker, reason_code and, for scanner diagnostics, required_endpoint_role, healthy_endpoints and required_independent_providers. TRON needs tron-indexer providers; healthy general full nodes alone do not qualify. Pricing failures identify the asset/currency. Nothing enables an unaccepted asset or changes store policy. On merchant versions before 5.4.0, unknown/inactive explicit choices fail instead. Existing invoice methods never expand when store settings change. Lightning must be selected separately. Replays keep the original methods, and changing selections with the same Idempotency-Key returns 409.
  • checkout_appearance supports every presentation setting listed above. Omitted fields inherit, arrays replace, and nested message fields merge; an empty message object clears that scope. The resolved design and images are saved for this invoice without editing the store. Read appearance from public checkout JSON to inspect the result. The whole request is limited to 32 KiB and resolved settings to 20 KiB.
  • Changing checkout_appearance with the same Idempotency-Key returns 409; retry with identical raw bytes. Appearance does not change amounts, rates, accepted assets, confirmation requirements, real status or embedding permissions. No HTML, CSS, scripts or remote image fetching.
  • exchange_rate_spread_percent overrides the store default for this invoice: omit or send null to inherit, or send "0" to disable it. Existing invoice quotes never change.
  • Spread is applied before upward rounding. Fees remain based on the original invoice fiat amount, excluding the spread.
  • Always send the returned expected_amount or expected_amount_atomic. Rounding is upward, limited by asset precision, 0.1% of the amount and one fiat minor unit.
  • Retries must keep the same credential, Idempotency-Key and exact body bytes. Changing the spread with the same key returns 409 idempotency_conflict.
  • An exact replay is checked before new quote, callback DNS or address preparation. Credential scope and project/store authorization are still checked on every request.
  • An effective ipn_url requires the store IPN signing secret. Unknown body fields are rejected.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Idempotency-KeyrequiredUnique 1–128 visible ASCII characters without whitespace.
Content-Typerecommendedapplication/json. The current raw-body handler parses JSON without enforcing the media type.
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDCopy Project API ID from Project → Settings → API IDs. Must be assigned to the credential; a readable project identifier is not accepted.
store_idpath UUIDCopy Store API ID from Project → Stores → select a store → Basic → API IDs. Required even for the default store; must be enabled and belong to project_id.

Invoice creation body

FieldTypePresenceDescription
amountstringrequiredUnsigned plain decimal string; no sign or exponent, up to 48 integer digits and 30 decimal places. Must be positive by default. A store can allow zero-amount invoices in Stores → Invoice; zero totals settle without receiving funds, allocating addresses or processing fees.
currencystring | nulloptionalSupported three-letter fiat currency, normalized to uppercase. Omitted or null inherits the store invoice currency. Creation also requires an independently available billing conversion rate.
payment_methodsInvoicePaymentSelection[] | nulloptionalSelect store-enabled methods for this invoice. Merchant 5.4.0+: ignore unknown/inactive/unaccepted choices; if none match, use store defaults. Omitted/null also uses store defaults; [] is invalid. Never enables a method or changes store settings. See selection schema below.
order_idstring | nulloptionalMerchant order reference, 1–128 characters after trimming; control characters are rejected.
emailstring | nulloptionalMerchant-only customer email, normalized to a practical ASCII address with at most 254 characters. Omitted or null stores no email.
descriptionstring | nulloptionalCustomer-facing description, 1–500 characters; line breaks and tabs are allowed.
expires_in_secondsinteger | nulloptionalInvoice quote lifetime from 300 through 86,400 seconds; omitted or null inherits store policy.
exchange_rate_spread_percentdecimal string | nulloptionalQuote markup from 0 through 100, with at most two decimal places. Omitted or null inherits the store default; "0" disables it for this invoice. Applied before upward rounding, then locked. Does not change the invoice fiat amount or the processing-fee basis.
underpayment_tolerance_percentdecimal string | nulloptionalAccepted shortfall from 0 through 99.99 with at most two decimal places. Omitted or null inherits the store default.
ipn_urlstring | nulloptionalPublic HTTPS callback, at most 2,048 bytes and without credentials or fragment. Overrides the store default; null/omitted inherits it.
redirect_urlstring | nulloptionalHTTPS success URL used after settlement, at most 2,048 bytes and without embedded credentials. Omitted or null inherits the store default and cannot clear it.
cancel_urlstring | nulloptionalHTTPS return URL used when checkout ends without successful payment. Omitted or null inherits the store default and cannot clear it.
redirect_automaticallyboolean | nulloptionalOmitted or null inherits store policy. true requires an effective redirect_url.
languagestring | nulloptionalEnglish or German BCP 47 tag such as en, de, or de-DE; omitted or null inherits store policy.
checkout_appearanceCheckoutAppearanceOverride | nulloptionalPartial presentation settings for this invoice. Omitted/null follows the store's current design. An object, including {}, freezes the resolved design and images at creation. See the override schema below; no financial settings, HTML, CSS, JavaScript or remote image URLs.
metadataobject | nulloptionalMerchant-only JSON object; omitted or null becomes {}, maximum 4,096 encoded bytes and five nested levels. firstname, lastname, street, street2, zip, city, country, countryiso2, company, and vatid are validated, normalized, and projected into customer summary fields.

InvoicePaymentSelection · choose store chains and assets

FieldTypePresenceDescription
chain_slugstringrequiredCopy chain_slug in Project → Stores → Payment methods, or read it from GET /v1/projects/{project_id}/stores/{store_id}/payment-assets, such as ethereum, base or bitcoin. A chain/rail pair may occur only once.
asset_idsUUID[] | nulloptionalOn-chain asset.id UUIDs, not contract addresses or invoice payment-method IDs. Use this OR asset_tickers. Omit both selectors for all active accepted assets on this chain. [] and duplicate/nil IDs are invalid. On 5.4.0+, ignore IDs not active/accepted on this chain in this store; an entirely unmatched selection uses store defaults.
asset_tickersstring[] | nulloptionalMerchant 5.3.0+. Symbols such as BTC, USDC or PEPE, scoped to chain_slug and this store. 1–64 unique tickers; trim/case-insensitive, 1–40 ASCII letters/digits/dot/underscore/hyphen. Use this OR asset_ids. On 5.4.0+, ignore unknown/inactive/unaccepted tickers. Ambiguous accepted symbols still fail: use asset_ids. Active selected methods must pass readiness and pricing checks. Lightning optionally accepts only BTC.
payment_railonchain | lightningoptionalDefaults to onchain. To choose Bitcoin Lightning, use {chain_slug: bitcoin, payment_rail: lightning} with no asset_ids; asset_tickers may optionally be [BTC]. Bitcoin on-chain does not include Lightning. The store's Lightning connection must already be enabled and ready.

CheckoutAppearanceOverride · all fields optional

FieldTypePresenceDescription
inherit_default_storebooleanoptionaltrue selects the project's default-store design as the base; otherwise use the target store's effective design. Overrides are then applied and saved independently; the resolved invoice flag is false.
titlestringoptionalCheckout heading, up to 120 characters. Empty uses the standard heading.
intro / outrostringoptionalPlain text, up to 2,000 characters each. Intro appears at the top, Outro at the bottom in every state. Newlines are preserved; safe text URLs become links. Empty string clears. Legacy customer_message is accepted as an alias for intro; do not send both.
intro_font_size / outro_font_sizeintegeroptionalPixels: 12, 14, 16, 18, 20 or 24. Default 16 unless inherited differently.
themesystem | light | dim | darkoptionalFollow the customer device or use a fixed theme.
accent_color / background_color / card_color / button_colorstringoptional#RRGGBB. Background, card and button may be empty for automatic colors. Text contrast is automatic.
logo_size / logo_alignmentstringoptionalsmall, medium or large; left or center.
imagesobjectoptionalKeys logo_light, logo_dark, favicon. Omitted key keeps the base image; null removes it. An object {store_id: UUID, kind?: logo_light|logo_dark|favicon} reuses that store's effective uploaded image in the SAME project. kind defaults to the target key. Upload in Store → Checkout first; copy the Store API ID from Basic → API IDs. Missing images or cross-project IDs return 400. No external URLs or image data accepted.
show_order_id / show_description / details_expandedbooleanoptionalShow order ID details and a plain-text description below the title. details_expanded opens order ID details initially. Display only, not data redaction.
featured_chainsstring[]optionalOrdered chain slugs, at most 60 unique values (lowercase letters, digits, hyphens; up to 64 characters). [] clears. Only available invoice methods are reordered.
featured_asset_ids / default_asset_idUUID[] / UUID|nulloptionalUp to 100 unique ordered asset IDs; [] clears. Default asset may be null. IDs come from payment-assets, not payment-intent IDs. These never enable methods; received payments and valid customer preferences take priority.
messagesobjectoptionalen/de objects with waiting, confirming, paid, underpaid, expired plain strings (500 characters each). Only supplied languages/states change; {} clears all messages, {en:{}} clears English, and an empty state string clears that state. English is the fallback. Does not replace actual status.
support_emailstringoptionalASCII email, up to 254 characters. Empty clears.
support_url / terms_url / privacy_urlstringoptionalHTTPS URLs up to 2,048 characters, without credentials. Empty clears. Links open in a new window.
return_button_textstringoptionalLabel up to 60 characters. Use top-level redirect_url/cancel_url/redirect_automatically/language for invoice behavior.

Invoice summary

FieldTypePresenceDescription
idUUIDalwaysInternal invoice UUID. Do not use it in merchant detail or checkout paths.
invoice_idUUIDalwaysPublic invoice UUID used by merchant detail and checkout paths.
project_idUUIDalwaysOwning project.
store_idUUIDalwaysOwning store.
sourcemanual | apialwaysHow the invoice was created.
order_idstring | nullalwaysMerchant order reference.
emailstring | nullalwaysMerchant-only customer email. Never returned by public checkout.
customer_namestring | nullalwaysDerived display name from private firstname, lastname, and company metadata.
customer_addressstring | nullalwaysDerived one-line merchant address from private company, street, street2, zip, city, country, countryiso2, and vatid metadata.
descriptionstring | nullalwaysCustomer-facing description.
amountdecimal stringalwaysCanonical invoice amount.
currencystringalwaysNormalized invoice currency/asset code.
exchange_rate_spread_percentdecimal stringalwaysLocked quote spread: the creation override, or the store default when omitted. Applied before upward rounding; never changes on this invoice.
underpayment_tolerance_percentdecimal stringalwaysImmutable accepted shortfall percentage snapshotted when the invoice was created.
statusinvoice statusalwaysnew, processing, settled, expired, invalid, or cancelled.
amount_statusamount statusalwaysnone, partial, paid, or overpaid. An explicitly allowed zero-amount invoice settles with none and no payment methods.
timing_statustiming statusalwayson_time or late.
resolutionresolutionalwaysautomatic, manually_settled, or manually_invalidated.
sequenceintegeralwaysMonotonic invoice state sequence, starting at 1.
winning_payment_intent_idUUID | nullalwaysPayment method that resolved the invoice, when selected.
expires_atRFC 3339 timestampalwaysQuote/payment deadline.
monitoring_expires_atRFC 3339 timestampalwaysLatest configured late-monitoring cutoff across payment methods.
settled_attimestamp | nullalwaysSettlement time when settled.
cancelled_attimestamp | nullalwaysCancellation time when cancelled.
archived_attimestamp | nullalwaysArchival time when archived.
created_atRFC 3339 timestampalwaysCreation time.
updated_atRFC 3339 timestampalwaysLast state update time.

Invoice detail additions

FieldTypePresenceDescription
ipn_urlstring | nullalwaysEffective per-invoice IPN target. Merchant response only; omitted from public checkout.
redirect_urlstring | nullalwaysEffective success URL used after settlement.
cancel_urlstring | nullalwaysEffective return URL used when checkout ends without successful payment.
redirect_automaticallybooleanalwaysWhether checkout should redirect automatically after success.
checkout_languagestringalwaysEffective checkout language tag.
metadataobjectalwaysMerchant metadata. Never returned by public checkout.
payment_intentsPaymentIntent[]alwaysQuoted payment methods and monitoring state.

PaymentIntent

FieldTypePresenceDescription
idUUIDalwaysPayment intent identifier; also used as checkout QR intent_id.
payment_railonchain | lightningalwaysInvoice transport. Bitcoin on-chain and Lightning can share asset_id; use intent id plus this field, not symbol alone. This differs from the asset catalog's scanner payment_rail.
bolt11string | nullalwaysLightning payment request, otherwise null. Pay this request with a Lightning wallet, never send on-chain funds to its payment hash.
asset_idUUIDalwaysConfigured payment asset identifier.
asset_keystringalwaysCanonical CAIP-style asset key.
chain_slugstringalwaysWholly Crypto chain identifier.
networkstringalwaysConfigured network, currently mainnet for supported payment assets.
caip_network_idstringalwaysCanonical CAIP-2 network identifier.
caip_asset_idstring | nullalwaysCanonical CAIP-19 identifier where registered.
symbolstringalwaysAsset symbol.
asset_decimalsintegeralwaysAtomic-unit precision. Lightning BTC uses 11 (millisatoshis), not on-chain Bitcoin's 8. Quotes are whole satoshis; receipts retain millisatoshi precision.
statusintent statusalwayspending, partial, paid, overpaid, expired, or invalid.
finality_modeconfirmations | finalizedalwaysFinality policy.
required_confirmationsintegeralwaysRequired confirmations when applicable.
quote_ratedecimal stringalwaysAsset units per one invoice currency unit, including the locked spread. For example 1.02 USDC per USD. Not the inverse rate.
quote_detailsobject | nullalwaysLocked quote provenance: reference_rate before spread, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at and asset_fetched_at. Null on older invoices; no historical values are fabricated.
expected_amountdecimal stringalwaysExact locked asset amount to pay after spread and upward rounding. From 4.1.1, recognized verified fiat stablecoins (such as USDC, USDT, DAI, USDS, EURC) round up to at most two decimals; 1.321 becomes 1.33, never 1.32. This is the expected amount even with zero tolerance. Other assets keep adaptive precision. Existing invoices are never repriced.
expected_amount_atomicinteger stringalwaysExact amount in the asset's smallest unit.
minimum_payment_amountdecimal stringalwaysSmallest amount accepted as paid after applying the invoice tolerance.
minimum_payment_amount_atomicinteger stringalwaysExact accepted threshold in the asset's smallest unit.
received_amountdecimal stringalwaysObserved amount.
received_amount_atomicinteger stringalwaysObserved atomic amount.
confirmed_amountdecimal stringalwaysConfirmed/final amount.
confirmed_amount_atomicinteger stringalwaysConfirmed/final atomic amount.
destination_addressstringalwaysOn-chain receiving address, or the 64-character payment hash for Lightning. Use bolt11 to pay Lightning; its hash is not a Bitcoin address.
destination_tagstring | nullalwaysRequired public payment reference where the rail uses one: XRP destination tag, Stellar memo ID, or TON invoice comment. Null for unique-address rails.
derivation_indexintegeralwaysReserved wallet child index; merchant detail only.
quote_expires_atRFC 3339 timestampalwaysQuote expiry.
monitoring_expires_atRFC 3339 timestampalwaysLate-monitoring cutoff for this method.
next_check_attimestamp | nullalwaysNext scheduled chain check.
last_checked_attimestamp | nullalwaysLast chain check.
last_chain_heightinteger | nullalwaysLast trustworthy height observed by the monitor.
last_anchor_hashstring | nullalwaysLast monitor anchor/block hash.
last_monitor_errorstring | nullalwaysSafe monitoring diagnostic for operators.
first_payment_attimestamp | nullalwaysFirst observed payment time.
fully_paid_attimestamp | nullalwaysTime the accepted minimum amount was first reached.
finalized_attimestamp | nullalwaysTime payment met finality policy.

PaymentMethodIssue

FieldTypePresenceDescription
chain_slug / asset_id / asset_tickerstring / UUID / stringwhen knownIdentifies the affected chain and asset. Lightning can omit asset_id.
reason_codestringalwaysscanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled, or asset_not_accepted.
message / actionstringwhen availableMerchant-facing explanation and action identifier: chain_connections, wallets, rates, payment_methods, project_settings or store_settings. No credentials or private provider URLs.
required_endpoint_rolestring | nullon-chainScanner-compatible role, for example tron-indexer. A healthy general TRON node is not an indexer.
healthy_endpointsintegeron-chainHealthy matching endpoints, not the independent-provider count.
usable_independent_providers / required_independent_providersintegeron-chainUsable independent verification slots, capped at the required two. Different provider keys AND hosts are required. Disabled, stale (over ten minutes), or cooling-down sources do not fill a slot. Lightning uses its own connection rules.
last_checked_attimestamp | nullon-chainLatest matching endpoint health check, separate from assessment time.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
  --request POST \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Idempotency-Key: order-1042-attempt-1' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "payment_methods": [
    {
      "chain_slug": "bitcoin",
      "asset_tickers": [
        "BTC"
      ]
    }
  ],
  "amount": "49.90",
  "currency": "USD",
  "order_id": "order-1042",
  "email": "ada@example.com",
  "description": "Annual plan",
  "underpayment_tolerance_percent": "1",
  "ipn_url": "https://merchant.example/wholly/ipn",
  "redirect_url": "https://merchant.example/orders/1042",
  "cancel_url": "https://merchant.example/cart",
  "redirect_automatically": true,
  "language": "en",
  "metadata": {
    "cart_id": "cart-681",
    "firstname": "Ada",
    "lastname": "Lovelace",
    "street": "12 Example Street",
    "street2": "Suite 2",
    "zip": "10115",
    "city": "Berlin",
    "country": "Germany",
    "countryiso2": "DE",
    "company": "Example GmbH",
    "vatid": "DE123456789"
  },
  "exchange_rate_spread_percent": "0.5",
  "checkout_appearance": {
    "title": "Complete your order",
    "intro": "Thanks for choosing our annual plan.",
    "outro": "Questions? https://merchant.example/help",
    "intro_font_size": 18,
    "outro_font_size": 14,
    "theme": "light",
    "accent_color": "#1768CE",
    "messages": {
      "en": {
        "paid": "Your order is ready."
      }
    }
  }
}'
Example response · 201 new invoice; 200 exact idempotent replay
{
  "data": {
    "id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
    "invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
    "project_id": "11111111-1111-4111-8111-111111111111",
    "store_id": "22222222-2222-4222-8222-222222222222",
    "source": "api",
    "order_id": "order-1042",
    "email": "ada@example.com",
    "customer_name": "Ada Lovelace · Example GmbH",
    "customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
    "description": "Annual plan",
    "amount": "49.9",
    "currency": "USD",
    "exchange_rate_spread_percent": "0.5",
    "underpayment_tolerance_percent": "1",
    "status": "new",
    "amount_status": "none",
    "timing_status": "on_time",
    "resolution": "automatic",
    "sequence": 1,
    "winning_payment_intent_id": null,
    "expires_at": "2026-08-31T18:15:00Z",
    "monitoring_expires_at": "2026-09-07T18:15:00Z",
    "settled_at": null,
    "cancelled_at": null,
    "archived_at": null,
    "created_at": "2026-08-31T18:00:00Z",
    "updated_at": "2026-08-31T18:00:00Z",
    "ipn_url": "https://merchant.example/wholly/ipn",
    "redirect_url": "https://merchant.example/orders/1042",
    "cancel_url": "https://merchant.example/cart",
    "redirect_automatically": true,
    "checkout_language": "en",
    "metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
    "payment_intents": [
      {
        "id": "33333333-3333-4333-8333-333333333333",
        "asset_id": "10000000-0000-4000-8000-000000000001",
        "asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "chain_slug": "bitcoin",
        "network": "mainnet",
        "caip_network_id": "bip122:000000000019d6689c085ae165831e93",
        "caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "symbol": "BTC",
        "asset_decimals": 8,
        "status": "pending",
        "finality_mode": "confirmations",
        "required_confirmations": 1,
        "quote_rate": "0.000009218",
        "quote_details": null,
        "expected_amount": "0.00046",
        "expected_amount_atomic": "46000",
        "minimum_payment_amount": "0.0004554",
        "minimum_payment_amount_atomic": "45540",
        "received_amount": "0",
        "received_amount_atomic": "0",
        "confirmed_amount": "0",
        "confirmed_amount_atomic": "0",
        "destination_address": "bc1q…example",
        "destination_tag": null,
        "derivation_index": 42,
        "quote_expires_at": "2026-08-31T18:15:00Z",
        "monitoring_expires_at": "2026-09-07T18:15:00Z",
        "next_check_at": "2026-08-31T18:00:00Z",
        "last_checked_at": null,
        "last_chain_height": null,
        "last_anchor_hash": null,
        "last_monitor_error": null,
        "first_payment_at": null,
        "fully_paid_at": null,
        "finalized_at": null
      }
    ]
  },
  "links": {
    "checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
  }
}
GETList invoices/v1/projects/{project_id}/invoicesRead only

Returns a compact newest-first page of scoped invoice summaries, including merchant-only email and customer fields derived from recognized metadata. Search, status, and store filters are evaluated server-side; the response includes total and has_more for predictable paging.

  • Sorted by created_at descending, then internal id descending.
  • List items are InvoiceSummary objects; email, customer_name, and customer_address are merchant-only. Call detail for raw metadata and payment intents.
  • For the next page set offset to pagination.offset + pagination.limit only when has_more is true.
  • The count and page are read from one repeatable-read database snapshot; concurrent writes appear on a later request.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.
store_idquery UUIDOptional exact store filter.
statusquery enumOptional new, processing, settled, expired, invalid, or cancelled.
searchquery stringOptional case-insensitive public-id, order-id, or email prefix; exact public UUID; or substring across description and recognized customer metadata. Trimmed, at most 100 characters, no controls.
limitquery integerOptional 1–100; defaults to 50.
offsetquery integerOptional 0–1,000,000; defaults to 0.

Invoice summary

FieldTypePresenceDescription
idUUIDalwaysInternal invoice UUID. Do not use it in merchant detail or checkout paths.
invoice_idUUIDalwaysPublic invoice UUID used by merchant detail and checkout paths.
project_idUUIDalwaysOwning project.
store_idUUIDalwaysOwning store.
sourcemanual | apialwaysHow the invoice was created.
order_idstring | nullalwaysMerchant order reference.
emailstring | nullalwaysMerchant-only customer email. Never returned by public checkout.
customer_namestring | nullalwaysDerived display name from private firstname, lastname, and company metadata.
customer_addressstring | nullalwaysDerived one-line merchant address from private company, street, street2, zip, city, country, countryiso2, and vatid metadata.
descriptionstring | nullalwaysCustomer-facing description.
amountdecimal stringalwaysCanonical invoice amount.
currencystringalwaysNormalized invoice currency/asset code.
exchange_rate_spread_percentdecimal stringalwaysLocked quote spread: the creation override, or the store default when omitted. Applied before upward rounding; never changes on this invoice.
underpayment_tolerance_percentdecimal stringalwaysImmutable accepted shortfall percentage snapshotted when the invoice was created.
statusinvoice statusalwaysnew, processing, settled, expired, invalid, or cancelled.
amount_statusamount statusalwaysnone, partial, paid, or overpaid. An explicitly allowed zero-amount invoice settles with none and no payment methods.
timing_statustiming statusalwayson_time or late.
resolutionresolutionalwaysautomatic, manually_settled, or manually_invalidated.
sequenceintegeralwaysMonotonic invoice state sequence, starting at 1.
winning_payment_intent_idUUID | nullalwaysPayment method that resolved the invoice, when selected.
expires_atRFC 3339 timestampalwaysQuote/payment deadline.
monitoring_expires_atRFC 3339 timestampalwaysLatest configured late-monitoring cutoff across payment methods.
settled_attimestamp | nullalwaysSettlement time when settled.
cancelled_attimestamp | nullalwaysCancellation time when cancelled.
archived_attimestamp | nullalwaysArchival time when archived.
created_atRFC 3339 timestampalwaysCreation time.
updated_atRFC 3339 timestampalwaysLast state update time.

Invoice pagination

FieldTypePresenceDescription
limitintegeralwaysEffective page size, 1–100.
offsetintegeralwaysEffective zero-based row offset, 0–1,000,000.
totalintegeralwaysTotal rows matching project, store, status, and search filters in the page snapshot.
has_morebooleanalwaysTrue when offset plus the returned row count is below total.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "data": [
    {
      "id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
      "invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
      "project_id": "11111111-1111-4111-8111-111111111111",
      "store_id": "22222222-2222-4222-8222-222222222222",
      "source": "api",
      "order_id": "order-1042",
      "email": "ada@example.com",
      "customer_name": "Ada Lovelace · Example GmbH",
      "customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
      "description": "Annual plan",
      "amount": "49.9",
      "currency": "USD",
      "exchange_rate_spread_percent": "0.5",
      "underpayment_tolerance_percent": "1",
      "status": "processing",
      "amount_status": "paid",
      "timing_status": "on_time",
      "resolution": "automatic",
      "sequence": 3,
      "winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
      "expires_at": "2026-08-31T18:15:00Z",
      "monitoring_expires_at": "2026-09-07T18:15:00Z",
      "settled_at": null,
      "cancelled_at": null,
      "archived_at": null,
      "created_at": "2026-08-31T18:00:00Z",
      "updated_at": "2026-08-31T18:04:10Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 143,
    "has_more": true
  }
}
GETRetrieve invoice/v1/projects/{project_id}/invoices/{invoice_id}Read only

Returns a complete merchant invoice detail and the current active checkout URL. Use this route for polling and reconciliation.

  • A scoped lookup deliberately returns invoice_not_found when the public ID is not in the authorized project.
  • links.checkout is resolved from the installation's active primary checkout domain at response time.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDEnabled project assigned to the credential.
invoice_idpath UUIDThe invoice_id returned during creation/listing, not internal id.

Invoice summary

FieldTypePresenceDescription
idUUIDalwaysInternal invoice UUID. Do not use it in merchant detail or checkout paths.
invoice_idUUIDalwaysPublic invoice UUID used by merchant detail and checkout paths.
project_idUUIDalwaysOwning project.
store_idUUIDalwaysOwning store.
sourcemanual | apialwaysHow the invoice was created.
order_idstring | nullalwaysMerchant order reference.
emailstring | nullalwaysMerchant-only customer email. Never returned by public checkout.
customer_namestring | nullalwaysDerived display name from private firstname, lastname, and company metadata.
customer_addressstring | nullalwaysDerived one-line merchant address from private company, street, street2, zip, city, country, countryiso2, and vatid metadata.
descriptionstring | nullalwaysCustomer-facing description.
amountdecimal stringalwaysCanonical invoice amount.
currencystringalwaysNormalized invoice currency/asset code.
exchange_rate_spread_percentdecimal stringalwaysLocked quote spread: the creation override, or the store default when omitted. Applied before upward rounding; never changes on this invoice.
underpayment_tolerance_percentdecimal stringalwaysImmutable accepted shortfall percentage snapshotted when the invoice was created.
statusinvoice statusalwaysnew, processing, settled, expired, invalid, or cancelled.
amount_statusamount statusalwaysnone, partial, paid, or overpaid. An explicitly allowed zero-amount invoice settles with none and no payment methods.
timing_statustiming statusalwayson_time or late.
resolutionresolutionalwaysautomatic, manually_settled, or manually_invalidated.
sequenceintegeralwaysMonotonic invoice state sequence, starting at 1.
winning_payment_intent_idUUID | nullalwaysPayment method that resolved the invoice, when selected.
expires_atRFC 3339 timestampalwaysQuote/payment deadline.
monitoring_expires_atRFC 3339 timestampalwaysLatest configured late-monitoring cutoff across payment methods.
settled_attimestamp | nullalwaysSettlement time when settled.
cancelled_attimestamp | nullalwaysCancellation time when cancelled.
archived_attimestamp | nullalwaysArchival time when archived.
created_atRFC 3339 timestampalwaysCreation time.
updated_atRFC 3339 timestampalwaysLast state update time.

Invoice detail additions

FieldTypePresenceDescription
ipn_urlstring | nullalwaysEffective per-invoice IPN target. Merchant response only; omitted from public checkout.
redirect_urlstring | nullalwaysEffective success URL used after settlement.
cancel_urlstring | nullalwaysEffective return URL used when checkout ends without successful payment.
redirect_automaticallybooleanalwaysWhether checkout should redirect automatically after success.
checkout_languagestringalwaysEffective checkout language tag.
metadataobjectalwaysMerchant metadata. Never returned by public checkout.
payment_intentsPaymentIntent[]alwaysQuoted payment methods and monitoring state.

PaymentIntent

FieldTypePresenceDescription
idUUIDalwaysPayment intent identifier; also used as checkout QR intent_id.
payment_railonchain | lightningalwaysInvoice transport. Bitcoin on-chain and Lightning can share asset_id; use intent id plus this field, not symbol alone. This differs from the asset catalog's scanner payment_rail.
bolt11string | nullalwaysLightning payment request, otherwise null. Pay this request with a Lightning wallet, never send on-chain funds to its payment hash.
asset_idUUIDalwaysConfigured payment asset identifier.
asset_keystringalwaysCanonical CAIP-style asset key.
chain_slugstringalwaysWholly Crypto chain identifier.
networkstringalwaysConfigured network, currently mainnet for supported payment assets.
caip_network_idstringalwaysCanonical CAIP-2 network identifier.
caip_asset_idstring | nullalwaysCanonical CAIP-19 identifier where registered.
symbolstringalwaysAsset symbol.
asset_decimalsintegeralwaysAtomic-unit precision. Lightning BTC uses 11 (millisatoshis), not on-chain Bitcoin's 8. Quotes are whole satoshis; receipts retain millisatoshi precision.
statusintent statusalwayspending, partial, paid, overpaid, expired, or invalid.
finality_modeconfirmations | finalizedalwaysFinality policy.
required_confirmationsintegeralwaysRequired confirmations when applicable.
quote_ratedecimal stringalwaysAsset units per one invoice currency unit, including the locked spread. For example 1.02 USDC per USD. Not the inverse rate.
quote_detailsobject | nullalwaysLocked quote provenance: reference_rate before spread, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at and asset_fetched_at. Null on older invoices; no historical values are fabricated.
expected_amountdecimal stringalwaysExact locked asset amount to pay after spread and upward rounding. From 4.1.1, recognized verified fiat stablecoins (such as USDC, USDT, DAI, USDS, EURC) round up to at most two decimals; 1.321 becomes 1.33, never 1.32. This is the expected amount even with zero tolerance. Other assets keep adaptive precision. Existing invoices are never repriced.
expected_amount_atomicinteger stringalwaysExact amount in the asset's smallest unit.
minimum_payment_amountdecimal stringalwaysSmallest amount accepted as paid after applying the invoice tolerance.
minimum_payment_amount_atomicinteger stringalwaysExact accepted threshold in the asset's smallest unit.
received_amountdecimal stringalwaysObserved amount.
received_amount_atomicinteger stringalwaysObserved atomic amount.
confirmed_amountdecimal stringalwaysConfirmed/final amount.
confirmed_amount_atomicinteger stringalwaysConfirmed/final atomic amount.
destination_addressstringalwaysOn-chain receiving address, or the 64-character payment hash for Lightning. Use bolt11 to pay Lightning; its hash is not a Bitcoin address.
destination_tagstring | nullalwaysRequired public payment reference where the rail uses one: XRP destination tag, Stellar memo ID, or TON invoice comment. Null for unique-address rails.
derivation_indexintegeralwaysReserved wallet child index; merchant detail only.
quote_expires_atRFC 3339 timestampalwaysQuote expiry.
monitoring_expires_atRFC 3339 timestampalwaysLate-monitoring cutoff for this method.
next_check_attimestamp | nullalwaysNext scheduled chain check.
last_checked_attimestamp | nullalwaysLast chain check.
last_chain_heightinteger | nullalwaysLast trustworthy height observed by the monitor.
last_anchor_hashstring | nullalwaysLast monitor anchor/block hash.
last_monitor_errorstring | nullalwaysSafe monitoring diagnostic for operators.
first_payment_attimestamp | nullalwaysFirst observed payment time.
fully_paid_attimestamp | nullalwaysTime the accepted minimum amount was first reached.
finalized_attimestamp | nullalwaysTime payment met finality policy.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "data": {
    "id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
    "invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
    "project_id": "11111111-1111-4111-8111-111111111111",
    "store_id": "22222222-2222-4222-8222-222222222222",
    "source": "api",
    "order_id": "order-1042",
    "email": "ada@example.com",
    "customer_name": "Ada Lovelace · Example GmbH",
    "customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
    "description": "Annual plan",
    "amount": "49.9",
    "currency": "USD",
    "exchange_rate_spread_percent": "0.5",
    "underpayment_tolerance_percent": "1",
    "status": "settled",
    "amount_status": "paid",
    "timing_status": "on_time",
    "resolution": "automatic",
    "sequence": 4,
    "winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
    "expires_at": "2026-08-31T18:15:00Z",
    "monitoring_expires_at": "2026-09-07T18:15:00Z",
    "settled_at": "2026-08-31T18:05:00Z",
    "cancelled_at": null,
    "archived_at": null,
    "created_at": "2026-08-31T18:00:00Z",
    "updated_at": "2026-08-31T18:05:00Z",
    "ipn_url": "https://merchant.example/wholly/ipn",
    "redirect_url": "https://merchant.example/orders/1042",
    "cancel_url": "https://merchant.example/cart",
    "redirect_automatically": true,
    "checkout_language": "en",
    "metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
    "payment_intents": [
      {
        "id": "33333333-3333-4333-8333-333333333333",
        "asset_id": "10000000-0000-4000-8000-000000000001",
        "asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "chain_slug": "bitcoin",
        "network": "mainnet",
        "caip_network_id": "bip122:000000000019d6689c085ae165831e93",
        "caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "symbol": "BTC",
        "asset_decimals": 8,
        "status": "paid",
        "finality_mode": "confirmations",
        "required_confirmations": 1,
        "quote_rate": "0.000009218",
        "quote_details": null,
        "expected_amount": "0.00046",
        "expected_amount_atomic": "46000",
        "minimum_payment_amount": "0.0004554",
        "minimum_payment_amount_atomic": "45540",
        "received_amount": "0.0004554",
        "received_amount_atomic": "45540",
        "confirmed_amount": "0.0004554",
        "confirmed_amount_atomic": "45540",
        "destination_address": "bc1q…example",
        "destination_tag": null,
        "derivation_index": 42,
        "quote_expires_at": "2026-08-31T18:15:00Z",
        "monitoring_expires_at": "2026-09-07T18:15:00Z",
        "next_check_at": null,
        "last_checked_at": "2026-08-31T18:05:00Z",
        "last_chain_height": 912345,
        "last_anchor_hash": "000000000000000000example",
        "last_monitor_error": null,
        "first_payment_at": "2026-08-31T18:03:00Z",
        "fully_paid_at": "2026-08-31T18:03:00Z",
        "finalized_at": "2026-08-31T18:05:00Z"
      }
    ]
  },
  "links": {
    "checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
  }
}
GETList invoice payments/v1/projects/{project_id}/invoices/{invoice_id}/paymentsRead only

Complete current transfer history, including invalidated observations. Use this when a callback marks payments_truncated. This is current state, not a reconstruction of an older event.

  • An observation is a token log, UTXO output or other rail transfer, not necessarily a unique transaction hash. Deduplicate by payment_id; transaction_id plus event_index identifies the chain transfer.
  • status is detected, confirming, final, reorged, replaced or invalid. Only counts_towards_received observations contribute to received amounts. Never add amounts across different assets.
  • Lightning records use payment_hash with null transaction_id, confirmations and explorer links; BTC precision is 11 (millisatoshis). No preimages, BOLT11 or wallet secrets are exposed.
  • Sorted by observed_at descending, then payment_id descending. Count and page use one repeatable-read snapshot; later pages may change as payments arrive. Deduplicate by payment_id when paginating a live invoice.
  • Existing read-only project scope, IP restrictions and per-credential rate limits apply. Never follow a callback-provided link with your token unless its origin matches your configured API host.
HeaderPresenceRule
AuthorizationrequiredBearer YOUR_MERCHANT_API_TOKEN
Acceptrecommendedapplication/json
ParameterType / locationRule
project_idpath UUIDProject assigned to this credential.
invoice_idpath UUIDPublic invoice_id returned at creation.
payment_method_idoptional query UUIDLimit to one invoice payment method.
limitquery integer1–100; default 25.
offsetquery integer0–1,000,000; default 0.

Request

: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0" \
  --header "Authorization: Bearer $WHOLLY_TOKEN" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "invoice_id": "11111111-2222-4333-8444-555555555555",
  "data": [{
    "payment_id": "44444444-4444-4444-8444-444444444444",
    "payment_method_id": "33333333-3333-4333-8333-333333333333",
    "transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "payment_hash": null,
    "event_index": 12,
    "payment_rail": "onchain",
    "chain_slug": "ethereum",
    "network": "mainnet",
    "asset_id": "55555555-5555-4555-8555-555555555555",
    "asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "symbol": "USDC",
    "asset_decimals": 6,
    "amount": "58.17342",
    "amount_atomic": "58173420",
    "status": "final",
    "counts_towards_received": true,
    "confirmations": 2,
    "block_height": 25975377,
    "observed_at": "2026-09-14T12:03:00Z",
    "chain_time": "2026-09-14T12:02:48Z",
    "finalized_at": "2026-09-14T12:04:00Z",
    "explorer_name": "Etherscan",
    "explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  }],
  "pagination": {"limit": 25, "offset": 0, "total": 1, "has_more": false}
}
GETCheckout shell/Public

Managed checkout-host root that serves the checkout application without selecting an invoice. Customer integrations should normally use links.checkout instead.

  • No bearer token is needed.
  • The managed checkout edge allows GET/HEAD and rejects other methods.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/" \
  --output 'checkout.html'
Example response · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout shell -->
GETHosted checkout page/invoice/{invoice_id}Public

Customer-facing HTML checkout. The page fetches checkout-safe JSON from the same checkout host. Embedding is denied unless the store enables it and explicitly allows the parent HTTPS origin.

  • No bearer token is accepted or needed.
  • The HTML shell itself returns 200 even when an invoice is absent; its checkout JSON request then receives invoice_not_found.
  • The response is no-store, noindex, and has invoice-specific frame-ancestors CSP.
  • Disabled project/store or an unknown invoice does not expose checkout data.
ParameterType / locationRule
invoice_idpath UUIDPublic invoice UUID returned by the merchant API.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID" \
  --output 'checkout.html'
Example response · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout application -->
GETCheckout-safe invoice/checkout-api/invoices/{invoice_id}Public

Returns only fields needed to render checkout. It deliberately omits internal IDs, customer email and derived address fields, IPN URL, merchant metadata, wallet IDs, derivation paths, and monitor diagnostics.

  • No bearer token is needed.
  • Cache-Control is no-store and search indexing is disabled.
  • Treat invoice_id as customer-facing capability data; avoid publishing it unnecessarily.
  • asset_icon_url is a same-origin local asset; customer checkout never needs to contact CoinGecko to render it.
  • When destination_tag is non-null, display and copy it beside the address: it is a required XRP destination tag, Stellar memo ID, or TON invoice comment and must be sent exactly.
  • For verified tokens, asset_kind is token, contract_address identifies the exact ERC-20 contract or SPL mint, token_standard identifies the rail, and payment_uri carries that token identity.
ParameterType / locationRule
invoice_idpath UUIDPublic invoice UUID.

Public checkout invoice

FieldTypePresenceDescription
invoice_idUUIDalwaysPublic invoice UUID.
order_idstring | nullalwaysMerchant order reference.
descriptionstring | nullalwaysCustomer-facing description.
amountdecimal stringalwaysInvoice amount.
currencystringalwaysInvoice currency.
exchange_rate_spread_percentdecimal stringalwaysEffective quote spread locked at creation, including a per-invoice override.
underpayment_tolerance_percentdecimal stringalwaysAccepted shortfall percentage for this invoice.
statusinvoice statusalwaysCurrent invoice status.
amount_statusamount statusalwaysnone, partial, paid, or overpaid. An explicitly allowed zero-amount invoice settles with none and no payment methods.
timing_statustiming statusalwayson_time or late.
sequenceintegeralwaysCurrent state sequence.
active_payment_method_idUUID | nullalwaysThe listed payment method that has received funds. Checkout remains on this method so an underpayment is not continued with an incompatible asset.
payment_method_lockedbooleanalwaysTrue after a valid payment selects active_payment_method_id.
server_timeRFC 3339 timestampalwaysServer clock captured for this response; use it with expires_at to avoid customer-device clock skew.
expires_atRFC 3339 timestampalwaysInvoice deadline.
expires_in_secondsintegeralwaysWhole seconds remaining at server_time, rounded up and clamped to zero.
payment_openbooleanalwaysTrue only while a new or processing invoice is before its deadline and has at least one payable method with an amount remaining.
redirect_urlstring | nullalwaysCustomer return target after successful settlement.
cancel_urlstring | nullalwaysCustomer return target when leaving without successful settlement.
redirect_automaticallybooleanalwaysAutomatic redirect policy.
checkout_languagestringalwaysCheckout language.
projectobjectalwaysname, checkout_title, checkout_description, theme, accent_color, and logo_url.
storeobjectalwaysPublic store name.
appearanceCheckoutAppearancealwaysEffective presentation: frozen per-invoice override when supplied, otherwise the store's current design. Never changes financial fields or safety warnings.
payment_methodsCheckoutPaymentMethod[]alwaysCheckout-safe payment methods.

CheckoutAppearance

FieldTypePresenceDescription
inherit_default_storebooleanalwaysTrue when the project's default store supplies this appearance. False for independent stores and frozen invoice overrides.
invoice_overridebooleanalwaysTrue when checkout_appearance was supplied at invoice creation. Omitted/null keeps this false.
title / intro / outrostringalwaysPlain merchant heading, top message and bottom message. intro replaces customer_message; old stored copy is preserved. Never evaluate as markup.
intro_font_size / outro_font_sizeintegeralwaysFont sizes in pixels: 12, 14, 16, 18, 20 or 24.
customer_messagestringalwaysDeprecated compatibility alias of intro. Use intro for new integrations.
themesystem | light | dim | darkalwaysCustomer-device preference or a fixed theme.
accent_color / background_color / card_color / button_colorstringalwaysStrict #RRGGBB colors. Optional colors are empty for automatic values; foreground contrast is calculated.
logo_size / logo_alignmentstringalwayssmall, medium or large; left or center. Images are contained, not cropped.
imagesobjectalwaysOptional logo_light, logo_dark and favicon URLs: scoped, same-origin normalized PNG images.
show_order_id / show_description / details_expandedbooleanalwaysOrder ID visibility, description below the title and initial order ID expansion. Amount remains visible; these are display controls, not data redaction.
featured_chains / featured_asset_idsarrayalwaysOrdered preferences, applied only to methods already present in the invoice. Missing or disabled methods are ignored.
default_asset_idUUID | nullalwaysSuggested initial method. A valid remembered customer preference or a method already receiving funds takes priority.
messagesobjectalwaysen/de plain text keyed by waiting, confirming, paid, underpaid and expired. English fallback. Supplementary; never replaces actual status.
support_email / support_url / terms_url / privacy_urlstringalwaysOptional contact and HTTPS links, without URL credentials. External links open in a new window.
return_button_textstringalwaysOptional label only. Success/cancel targets and redirect policy still belong to the invoice.

CheckoutPaymentMethod

FieldTypePresenceDescription
payment_railonchain | lightningalwaysLightning remains a Bitcoin method, separate from on-chain BTC. Identify the choice by intent id and rail, not only asset_id.
bolt11string | nullalwaysSigned Lightning request; null for on-chain methods. Never pay after payable becomes false.
payment_hashstring | nullalwaysLightning payment hash for reconciliation, not a receiving address. Null for on-chain methods.
idUUIDalwaysPayment intent identifier.
asset_idUUIDalwaysAsset UUID used by appearance preferences; distinct from this invoice's payment intent id.
asset_keystringalwaysCanonical asset key.
chain_slug / chain_namestringalwaysMachine and display chain names.
networkstringalwaysPayment network.
caip_network_idstringalwaysCanonical network identity used to disambiguate the selected chain.
caip_asset_idstring | nullalwaysCanonical exact asset identity, including a verified token contract or mint when applicable.
asset_name / symbolstringalwaysPayment asset display values.
asset_icon_urlstring | nullalwaysSame-origin locally cached asset icon, or null when no verified CoinGecko mapping exists.
asset_kindnative | tokenalwaysDistinguishes native currency from contract/mint payment.
contract_addressstring | nullalwaysCanonical ERC-20 contract or SPL mint for tokens; null for native currency.
token_standarderc20 | spl-token | nullalwaysVerified token runtime, or null for native currency.
asset_decimalsintegeralwaysAtomic-unit precision: 11 for Lightning BTC millisatoshis, 8 for on-chain BTC satoshis.
statusintent statusalwaysCurrent payment-method status.
payablebooleanalwaysTrue only when this exact method can currently accept payment; false for inactive methods after another asset receives funds.
finality_mode / required_confirmationsstring / integeralwaysFinality policy.
expected_amount / expected_amount_atomicdecimal / integer stringalwaysFull locked quote in display and actual on-chain units. Recognized fiat stablecoins use at most two quote decimals, always rounded up after spread; other assets use adaptive precision. Actual token decimals, received funds and partial-payment remainders stay exact. Use returned amounts unchanged.
minimum_payment_amount / minimum_payment_amount_atomicdecimal / integer stringalwaysAccepted settlement threshold after applying underpayment tolerance.
received_amount / received_amount_atomicdecimal / integer stringalwaysObserved amount.
remaining_amountdecimal stringalwaysExact display amount still needed to reach the accepted threshold, clamped to zero.
remaining_amount_atomicinteger stringalwaysShortfall to the accepted threshold in atomic units. This is not the requested payment amount: tolerance affects acceptance only.
confirmed_amount / confirmed_amount_atomicdecimal / integer stringalwaysConfirmed/final amount.
destination_address / destination_tagstring / string|nullalwaysOn-chain destination and optional reference. For Lightning this is the payment hash with no tag; pay the bolt11/payment_uri instead.
quote_expires_atRFC 3339 timestampalwaysQuote expiry.
payment_uristring | nullalwaysChain-aware request: ERC-681, Solana Pay, native URI, or lightning:<bolt11>. Amount-bearing requests use the full expected amount minus received funds, never the tolerance threshold. Null when payable is false, including after a tolerated shortfall is accepted. Lightning QR encodes the full Lightning request, not the payment hash.
qr_urlpath | nullalwaysSequence- and exact-remainder-revisioned same-origin SVG QR path, or null when payable is false. The SVG is no-store.
address_explorer_name / address_explorer_urlstring|nullalwaysValidated mainnet explorer fallback where supported.
transaction_countintegeralwaysTotal distinct public, valid transactions observed for this method.
transactions_truncatedbooleanalwaysTrue when transaction_count exceeds the returned recent transaction list.
transactionsCheckoutTransaction[]alwaysUp to 10 most recent public, valid transactions. Exact received totals remain independent of this display bound.

CheckoutTransaction

FieldTypePresenceDescription
transaction_idstringalwaysObserved transaction identifier.
statusdetected | confirming | finalalwaysPublic observation state.
confirmationsintegeralwaysObserved confirmation count.
block_heightinteger | nullalwaysObserved block/ledger height.
explorer_namestringwhen returnedValidated fixed explorer name.
explorer_urlstringwhen returnedValidated fixed mainnet explorer URL.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "data": {
    "invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
    "order_id": "order-1042",
    "description": "Annual plan",
    "amount": "49.9",
    "currency": "USD",
    "exchange_rate_spread_percent": "0.5",
    "underpayment_tolerance_percent": "1",
    "status": "processing",
    "amount_status": "partial",
    "timing_status": "on_time",
    "sequence": 3,
    "active_payment_method_id": "33333333-3333-4333-8333-333333333333",
    "payment_method_locked": true,
    "server_time": "2026-08-31T18:10:00Z",
    "expires_at": "2026-08-31T18:15:00Z",
    "expires_in_seconds": 300,
    "payment_open": true,
    "redirect_url": "https://merchant.example/orders/1042",
    "cancel_url": "https://merchant.example/cart",
    "redirect_automatically": true,
    "checkout_language": "en",
    "project": {
      "name": "Example project",
      "checkout_title": "Complete your payment",
      "checkout_description": "Send the exact amount shown.",
      "theme": "system",
      "accent_color": "#42e39b",
      "logo_url": "/checkout-api/invoices/…/logo/…/image.png"
    },
    "store": { "name": "Online shop" },
    "payment_methods": [
      {
        "id": "33333333-3333-4333-8333-333333333333",
        "asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "chain_slug": "bitcoin",
        "chain_name": "Bitcoin",
        "network": "mainnet",
        "caip_network_id": "bip122:000000000019d6689c085ae165831e93",
        "caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
        "asset_name": "Bitcoin",
        "symbol": "BTC",
        "asset_icon_url": "/assets/coingecko/bitcoin.png",
        "asset_kind": "native",
        "contract_address": null,
        "token_standard": null,
        "asset_decimals": 8,
        "status": "partial",
        "payable": true,
        "finality_mode": "confirmations",
        "required_confirmations": 1,
        "expected_amount": "0.00046",
        "expected_amount_atomic": "46000",
        "minimum_payment_amount": "0.0004554",
        "minimum_payment_amount_atomic": "45540",
        "received_amount": "0.0002",
        "received_amount_atomic": "20000",
        "remaining_amount": "0.0002554",
        "remaining_amount_atomic": "25540",
        "confirmed_amount": "0",
        "confirmed_amount_atomic": "0",
        "destination_address": "bc1q…example",
        "destination_tag": null,
        "quote_expires_at": "2026-08-31T18:15:00Z",
        "payment_uri": "bitcoin:bc1q…example?amount=0.00026",
        "qr_url": "/checkout-api/invoices/…/payment-methods/…/qr.svg?sequence=3&amount_atomic=26000",
        "address_explorer_name": "mempool.space",
        "address_explorer_url": "https://mempool.space/address/…",
        "transaction_count": 0,
        "transactions_truncated": false,
        "transactions": []
      }
    ]
  }
}
GETStore checkout preview/invoice/preview/{project_id}Public

Renders the saved store appearance with an illustrative amount and real accepted-asset metadata. Switch between waiting, confirming, paid, underpaid and expired examples without creating payments.

  • The preview is branding-only and must never be sent to a customer as a payment request.
  • No receiving address, payable QR, wallet action, redirect or payment polling. Examples do not change actual invoice status.
  • The response is no-store, noindex, and cannot be embedded.
ParameterType / locationRule
project_idpath UUIDProject UUID copied into the preview link by the authenticated console.
store_idquery UUID, optionalStore belonging to this project. Omit to use its first/default store.
statequery string, optionalwaiting, confirming, paid, underpaid or expired. Browser-only illustration.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming" \
  --output 'checkout-preview.html'
Example response · 200 text/html
<!doctype html>
<!-- Hosted branding preview; no invoice is created -->
GETCheckout preview data/checkout-api/previews/{project_id}Public

Returns effective store appearance and safe accepted-asset metadata. payment_methods remains empty; preview_methods contains no payment addresses, quotes or private wallet data.

  • No bearer token is accepted or needed.
  • No invoice, destination, wallet, transaction, IPN, webhook, or merchant metadata is returned.
  • Use the authenticated console to obtain the correct pay-domain preview link.
ParameterType / locationRule
project_idpath UUIDProject UUID from the console preview link.
store_idquery UUID, optionalMust belong to this project; mismatched IDs return 404. Unknown query fields are rejected.

CheckoutAppearance

FieldTypePresenceDescription
inherit_default_storebooleanalwaysTrue when the project's default store supplies this appearance. False for independent stores and frozen invoice overrides.
invoice_overridebooleanalwaysTrue when checkout_appearance was supplied at invoice creation. Omitted/null keeps this false.
title / intro / outrostringalwaysPlain merchant heading, top message and bottom message. intro replaces customer_message; old stored copy is preserved. Never evaluate as markup.
intro_font_size / outro_font_sizeintegeralwaysFont sizes in pixels: 12, 14, 16, 18, 20 or 24.
customer_messagestringalwaysDeprecated compatibility alias of intro. Use intro for new integrations.
themesystem | light | dim | darkalwaysCustomer-device preference or a fixed theme.
accent_color / background_color / card_color / button_colorstringalwaysStrict #RRGGBB colors. Optional colors are empty for automatic values; foreground contrast is calculated.
logo_size / logo_alignmentstringalwayssmall, medium or large; left or center. Images are contained, not cropped.
imagesobjectalwaysOptional logo_light, logo_dark and favicon URLs: scoped, same-origin normalized PNG images.
show_order_id / show_description / details_expandedbooleanalwaysOrder ID visibility, description below the title and initial order ID expansion. Amount remains visible; these are display controls, not data redaction.
featured_chains / featured_asset_idsarrayalwaysOrdered preferences, applied only to methods already present in the invoice. Missing or disabled methods are ignored.
default_asset_idUUID | nullalwaysSuggested initial method. A valid remembered customer preference or a method already receiving funds takes priority.
messagesobjectalwaysen/de plain text keyed by waiting, confirming, paid, underpaid and expired. English fallback. Supplementary; never replaces actual status.
support_email / support_url / terms_url / privacy_urlstringalwaysOptional contact and HTTPS links, without URL credentials. External links open in a new window.
return_button_textstringalwaysOptional label only. Success/cancel targets and redirect policy still belong to the invoice.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID" \
  --header 'Accept: application/json'
Example response · 200 application/json
{
  "data": {
    "preview": true,
    "invoice_id": "YOUR_PROJECT_ID",
    "amount": "100.00",
    "currency": "USD",
    "project": {
      "name": "Example project",
      "checkout_title": "Complete your payment",
      "checkout_description": "Choose a network and send the exact amount shown.",
      "theme": "system",
      "accent_color": "#42e39b",
      "logo_url": "/checkout-api/previews/…/logo/…/image.png"
    },
    "appearance": {"inherit_default_store": true, "theme": "system", "accent_color": "#42E39B", "images": {}},
    "preview_methods": [],
    "payment_methods": []
  }
}
GETStore checkout image/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngPublic

Returns a normalized store logo or favicon belonging to this invoice. Use appearance.images URLs from checkout data.

  • Use appearance.images from checkout JSON. Frozen invoice images keep working after their source store replaces or removes an upload. Explicitly removed, wrong-invoice, wrong-kind and unknown revisions return 404; a snapshot never falls back to a current store image.
  • Without an invoice override, the current effective store image is used and replaced/removed revisions return 404. PNG only, nosniff and private caching.
  • Store image uploads accept bounded PNG, JPEG or WebP in the authenticated console; never SVG, HTML or remote image URLs.
ParameterType / locationRule
invoice_idpath UUIDPublic invoice UUID.
kindpath enumlogo_light, logo_dark or favicon.
revisionpath UUIDCurrent image revision.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
  --output 'store-logo.png'
Example response · 200 image/png
(binary PNG response)
GETStore preview image/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngPublic

Returns a normalized preview image only for the matching project, store, kind and current revision.

  • Use appearance.images from preview data. Unknown or mismatched IDs return 404. No wallet or payment information is exposed.
ParameterType / locationRule
project_idpath UUIDProject UUID.
store_idpath UUIDStore belonging to the project.
kindpath enumlogo_light, logo_dark or favicon.
revisionpath UUIDCurrent image revision.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
  --output 'store-preview-logo.png'
Example response · 200 image/png
(binary PNG response)
GETPayment QR image/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgPublic

Generates a 512×512 SVG QR for the exact chain-aware payment payload of an invoice payment method.

  • No bearer token is needed.
  • Use the sequence- and remainder-revisioned qr_url returned by checkout JSON; the SVG is private and no-store.
  • After a partial payment it requests the exact remaining amount and stays locked to that asset.
  • Returns 409 after expiry, completion, or when another method is active; returns payment_qr_unavailable (422) if the request is too large to encode.
ParameterType / locationRule
invoice_idpath UUIDPublic invoice UUID.
intent_idpath UUIDPayment method id from checkout JSON.

Request

curl --fail-with-body --max-time 30 \
  --request GET \
  --url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg" \
  --output 'payment-qr.svg'
Example response · 200 image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">…</svg>

Reference for Wholly Crypto 5.5.0. For your installed version, open Settings → API access → Documentation in your console. View releases.