# Merchant (/reference/merchant)

## Find a record by its MolnPay ID

`GET /v1/lookup`

Console sessions only. `ref` is the MolnPay ID printed first on every receipt (`MOLN-` and twelve letters and digits). Returns which kind of record it names — `deposit`, `payout`, `bridge_order`, `own_wallet_transfer`, `balance_release`, `conversion` or `swap` — and the record in the shape its own endpoint returns; a swap comes back as every conversion row it wrote.

### Parameters

- `ref` (query, required, string)

### Responses

- `200` — The record.
  - `object` (optional, string)
  - `ref` (optional, string)
  - `kind` (optional, string, one of: deposit, payout, bridge_order, own_wallet_transfer, balance_release, conversion, swap)
  - `records` (optional, array, of object)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — None of your records carries that ID — also returned for another project's ID, so a 404 never confirms that it exists.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your business profile

`GET /v1/merchant`

What a payer is shown about you on a checkout link: the trading name, your site, your logo, and what happens to money sent beyond the invoice price. `tolerance_bps` and `tolerance_min_cents` come with it and are PLATFORM-WIDE, not yours to set — they are here so you can state the same band we settle against.

### Responses

- `200` — Your profile.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `display_name` (required, string) — Your trading name, as a payer sees it on the hosted checkout.
  - `website_url` (required, string | null) — Your own site. Unverified — shown as a link you gave us, never as one we checked.
  - `logo_url` (required, string | null) — Set by uploading a file; never a URL you supply.
  - `overpayment_policy` (required, string, one of: keep, refund) — What happens to money above the invoice price. The checkout states it before the payer sends.
  - `settlement_asset` (required, string) — The default target coin for auto conversion on a network with no rule of its own.
  - `checkout_template` (required, string) — Which hosted checkout card the payer sees.
  - `status` (required, string, one of: active, paused, suspended)
  - `domain` (required, string | null) — A hostname we verified. Read-only: set by us, not by you.
  - `domain_verified` (required, boolean)
  - `tolerance_bps` (required, integer) — PLATFORM-WIDE, not yours: how far either side of the price still settles as `paid`. 50 = ±0.5%.
  - `tolerance_min_cents` (required, string) — The floor under that band, so a small invoice does not have a zero-wide one.
  - `created_at` (required, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `merchant_not_found` — this key's project is not a merchant account.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Change your business profile

`PATCH /v1/merchant`

Send only the fields you are changing. Requires `merchant:write`, which is NOT granted by default on any key kind — everything this writes is read by strangers on a payment page, so a leaked storefront key must not be able to rename your business.

`domain` and `logo_url` are deliberately not writable here: the first is a claim we verified, the second is set by uploading a file. `website_url` is your own unverified link and is stated as such wherever it is shown.

### Request body (required)

- `display_name` (optional, string) — Shown to payers. Cannot be blank — whitespace is trimmed first.
- `website_url` (optional, string | null) — Absolute http(s) URL, or null to clear. Any other scheme is refused.
- `overpayment_policy` (optional, string, one of: keep, refund) — What happens to money above the invoice price. Stated to the payer before they send, either way.
- `settlement_asset` (optional, string) — The coin auto conversion targets by default on a network you have not set a rule for. Stored upper-case.

### Responses

- `200` — The profile after the change.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `display_name` (required, string) — Your trading name, as a payer sees it on the hosted checkout.
  - `website_url` (required, string | null) — Your own site. Unverified — shown as a link you gave us, never as one we checked.
  - `logo_url` (required, string | null) — Set by uploading a file; never a URL you supply.
  - `overpayment_policy` (required, string, one of: keep, refund) — What happens to money above the invoice price. The checkout states it before the payer sends.
  - `settlement_asset` (required, string) — The default target coin for auto conversion on a network with no rule of its own.
  - `checkout_template` (required, string) — Which hosted checkout card the payer sees.
  - `status` (required, string, one of: active, paused, suspended)
  - `domain` (required, string | null) — A hostname we verified. Read-only: set by us, not by you.
  - `domain_verified` (required, boolean)
  - `tolerance_bps` (required, integer) — PLATFORM-WIDE, not yours: how far either side of the price still settles as `paid`. 50 = ±0.5%.
  - `tolerance_min_cents` (required, string) — The floor under that band, so a small invoice does not have a zero-wide one.
  - `created_at` (required, string (date-time))
- `400` — `invalid_body` — a blank trading name, a non-http(s) website, or an unknown overpayment policy. `nothing_to_change` — an empty body.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `merchant_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your domain and its proof

`GET /v1/merchant/domain`

The verified domain (or null), how it was proven, and — for a pending claim or a verified domain — the three proofs you can publish: a DNS TXT record, a meta tag, or an HTML file. Once a domain is verified, every API key's `origin_allowlist`, every webhook endpoint URL and every invoice `return_url` / `cancel_url` on a live project must be on it.

### Responses

- `200` — Where your domain stands.
  - `object` (required, string)
  - `domain` (required, string | null) — The verified domain. API key origins, webhook URLs and invoice return URLs must sit on it.
  - `verified` (required, boolean)
  - `verified_at` (required, string | null (date-time))
  - `method` (required, string | null, one of: dns, meta, file, staff, null)
  - `last_checked_at` (required, string | null (date-time))
  - `unverifies_at` (required, string | null (date-time)) — Set when a re-check can no longer find the proof: it must be back before then, or the domain is un-verified.
  - `claim` (required, object | null)
    - `domain` (optional, string)
    - `expires_at` (optional, string (date-time))
    - `last_attempt_at` (optional, string | null (date-time))
    - `state` (optional, string, one of: idle, watching, gave_up) — `watching`: retried every 10 minutes until `watching_until`. `gave_up`: the hour ran out — fix the proof and check again.
    - `watching_until` (optional, string | null (date-time))
    - `gave_up_at` (optional, string | null (date-time))
  - `proof` (required, object | null) — Publish any ONE of these.
    - `dns` (optional, object)
      - `type` (optional, string)
      - `host` (optional, string)
      - `value` (optional, string)
    - `meta` (optional, object)
      - `url` (optional, string)
      - `tag` (optional, string)
    - `file` (optional, object)
      - `url` (optional, string)
      - `content` (optional, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Claim a domain to verify

`POST /v1/merchant/domain`

Mints a random token, good for 72 hours, and returns the three proofs. A verified domain stays verified until the new one passes. Signed-in owner or admin only — never a key.

### Request body (required)

- `domain` (required, string)

### Responses

- `200` — The claim and what to publish.
  - `object` (required, string)
  - `domain` (required, string | null) — The verified domain. API key origins, webhook URLs and invoice return URLs must sit on it.
  - `verified` (required, boolean)
  - `verified_at` (required, string | null (date-time))
  - `method` (required, string | null, one of: dns, meta, file, staff, null)
  - `last_checked_at` (required, string | null (date-time))
  - `unverifies_at` (required, string | null (date-time)) — Set when a re-check can no longer find the proof: it must be back before then, or the domain is un-verified.
  - `claim` (required, object | null)
    - `domain` (optional, string)
    - `expires_at` (optional, string (date-time))
    - `last_attempt_at` (optional, string | null (date-time))
    - `state` (optional, string, one of: idle, watching, gave_up) — `watching`: retried every 10 minutes until `watching_until`. `gave_up`: the hour ran out — fix the proof and check again.
    - `watching_until` (optional, string | null (date-time))
    - `gave_up_at` (optional, string | null (date-time))
  - `proof` (required, object | null) — Publish any ONE of these.
    - `dns` (optional, object)
      - `type` (optional, string)
      - `host` (optional, string)
      - `value` (optional, string)
    - `meta` (optional, object)
      - `url` (optional, string)
      - `tag` (optional, string)
    - `file` (optional, object)
      - `url` (optional, string)
      - `content` (optional, string)
- `400` — `invalid_domain`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `409` — `domain_unavailable` — verified on another account; contact support if it is yours.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## I've added it — check now

`POST /v1/merchant/domain/check`

Looks for the proof now: DNS first (at your domain's own nameservers), then the meta tag, then the HTML file — or only `method` if given. The first that passes verifies the domain at once.

On a miss, MolnPay keeps looking **every 10 minutes for one hour**, then stops and reports `claim.state: gave_up` with what it found on each method. Nothing is retried after that until you call this again.

### Request body

- `method` (optional, string, one of: dns, meta, file)

### Responses

- `200` — The domain after the check, plus `check.outcome` and each method's `results`.
  - `object` (required, string)
  - `domain` (required, string | null) — The verified domain. API key origins, webhook URLs and invoice return URLs must sit on it.
  - `verified` (required, boolean)
  - `verified_at` (required, string | null (date-time))
  - `method` (required, string | null, one of: dns, meta, file, staff, null)
  - `last_checked_at` (required, string | null (date-time))
  - `unverifies_at` (required, string | null (date-time)) — Set when a re-check can no longer find the proof: it must be back before then, or the domain is un-verified.
  - `claim` (required, object | null)
    - `domain` (optional, string)
    - `expires_at` (optional, string (date-time))
    - `last_attempt_at` (optional, string | null (date-time))
    - `state` (optional, string, one of: idle, watching, gave_up) — `watching`: retried every 10 minutes until `watching_until`. `gave_up`: the hour ran out — fix the proof and check again.
    - `watching_until` (optional, string | null (date-time))
    - `gave_up_at` (optional, string | null (date-time))
  - `proof` (required, object | null) — Publish any ONE of these.
    - `dns` (optional, object)
      - `type` (optional, string)
      - `host` (optional, string)
      - `value` (optional, string)
    - `meta` (optional, object)
      - `url` (optional, string)
      - `tag` (optional, string)
    - `file` (optional, object)
      - `url` (optional, string)
      - `content` (optional, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `409` — `no_domain_claim` — nothing to check, or the 72-hour token expired. (A second check within a few seconds is `429 check_too_soon`.)
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Who you are in this account

`GET /v1/merchant/me`

Console sessions only. The signed-in member's role (owner, admin, finance, developer, viewer) and what it may do — the console's chrome reads this. An API key has no role: it carries scopes instead.

### Responses

- `200` — Your membership.
  - `object` (required, string, one of: membership)
  - `user_id` (required, string | null)
  - `merchant_id` (required, string (uuid))
  - `organization_id` (required, string | null)
  - `role` (required, string, one of: owner, admin, finance, developer, viewer)
  - `role_summary` (required, string)
  - `can` (required, object)
    - `manage_team` (required, boolean)
    - `manage_keys` (required, boolean)
    - `move_money` (required, boolean)
    - `update_profile` (required, boolean)
    - `update_billing` (required, boolean)
    - `transfer_ownership` (required, boolean)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Everyone in the account

`GET /v1/merchant/team`

Console sessions only. The owner, every member with their role, the open invitations, and the seats: how many members beyond the owner your plan includes, what one more costs a month, and how many are in use or invited.

Members and invitations are written through Better Auth's organization endpoints under `/v1/auth/organization/` (`invite-member`, `cancel-invitation`, `update-member-role`, `remove-member`), which enforce the roles and the seats.

### Responses

- `200` — The team.
  - `object` (required, string, one of: team)
  - `your_role` (required, string, one of: owner, admin, finance, developer, viewer)
  - `organization_id` (required, string | null)
  - `owner` (required, object | null)
    - `user_id` (optional, string)
    - `email` (optional, string)
    - `name` (optional, string)
  - `members` (required, array)
    - `id` (required, string)
    - `user_id` (required, string)
    - `email` (required, string)
    - `name` (required, string)
    - `role` (required, string)
    - `since` (required, string (date-time))
  - `invitations` (required, array)
    - `id` (required, string)
    - `email` (required, string)
    - `role` (required, string)
    - `expires_at` (required, string (date-time))
    - `invited_by` (required, string | null)
  - `seats` (required, object)
    - `included` (required, integer | null) — Members beyond the owner the plan includes; null = agreed.
    - `extra_seat_cents` (required, string | null) — Monthly price of one more, cents; null = none can be added.
    - `members` (required, integer)
    - `pending` (required, integer)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your passkeys

`GET /v1/merchant/passkeys`

Console sessions only, and per person: the signed-in member's own passkeys. They confirm withdrawals and withdrawal-address changes; the authenticator code remains the fallback.

### Responses

- `200` — Passkeys.
  - `object` (required, string)
  - `data` (required, array)
  - `has_more` (required, boolean)
  - `next_cursor` (optional, string | null) — Opaque. Pass as `cursor` for the next page. Do not construct or parse it.
  - `data` (optional, array)
    - `id` (required, string (uuid))
    - `label` (required, string)
    - `device_type` (required, string | null) — `singleDevice` (lives on one authenticator) or `multiDevice` (synced).
    - `backed_up` (required, boolean) — Synced by its provider — recoverable if the device is lost.
    - `created_at` (required, string (date-time))
    - `last_used_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Finish registering a passkey

`POST /v1/merchant/passkeys`

The browser's `RegistrationResponseJSON` for the options from `POST /v1/merchant/passkeys/options`. Takes effect immediately, and the person is emailed.

### Request body (required)

- `label` (required, string)
- `response` (required, object)

### Responses

- `201` — Registered.
  - `id` (required, string (uuid))
  - `total` (required, integer)
- `400` — `challenge_expired`, `passkey_invalid`, or `passkey_already_registered`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## A challenge to sign for the next guarded request

`POST /v1/merchant/passkeys/challenge`

WebAuthn `PublicKeyCredentialRequestOptionsJSON`. Single use, five minutes. Sign it with the browser and send the result as `passkey` on a withdrawal, an address change, or a passkey change.

### Responses

- `200` — Authentication options.
  object
- `400` — `no_passkey`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Start registering a passkey

`POST /v1/merchant/passkeys/options`

Proof first: once you have any factor, a `passkey` or `totp_code`; the very first factor is bought with your `password`. Returns WebAuthn `PublicKeyCredentialCreationOptionsJSON`.

### Request body (required)

- `passkey` (optional, object) — Console sessions: the browser's WebAuthn `AuthenticationResponseJSON` over a challenge from `POST /v1/merchant/passkeys/challenge`. Single use.
- `totp_code` (optional, string) — Console sessions: a current authenticator code, when not using a passkey. Each code is spent once.
- `password` (optional, string)

### Responses

- `200` — Registration options.
  object
- `401` — `second_factor_required`, `password_required`, `invalid_password`, `invalid_totp`, `passkey_invalid`.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Remove a passkey

`DELETE /v1/merchant/passkeys/{id}`

Needs a second factor. Removing the last one while no authenticator is enrolled pauses withdrawals until a factor is set up again. The person is emailed.

### Parameters

- `id` (path, required, string (uuid))

### Request body (required)

- `passkey` (optional, object) — Console sessions: the browser's WebAuthn `AuthenticationResponseJSON` over a challenge from `POST /v1/merchant/passkeys/challenge`. Single use.
- `totp_code` (optional, string) — Console sessions: a current authenticator code, when not using a passkey. Each code is spent once.

### Responses

- `200` — Removed.
  - `removed` (required, boolean)
  - `remaining` (required, integer)
- `401` — `second_factor_required`, `invalid_totp`, `code_already_used`, `passkey_invalid`, `challenge_expired`, or no valid credential.
- `403` — `session_required`, `totp_not_enrolled`, or `no_passkey`.
- `404` — `passkey_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## What is mailed, and where

`GET /v1/merchant/notifications`

Console sessions only; any member may read. The topics (each with whether it is on), the notification addresses (`pending` until the code mailed to them is entered), the daily summary's zone and hour, and whether a private feed is live.

Notices are built from the same events `GET /v1/events` and your webhooks carry. Topics marked `required` — a payout awaiting approval, a failed payout, a reversed deposit, a balance moved by MolnPay staff, a disabled webhook endpoint — cannot be switched off.

### Responses

- `200` — Your notification settings.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Switch topics, set the summary's time

`PATCH /v1/merchant/notifications`

Owner or admin. Send only what changes: `topics` as `{ "deposits": false }`, `include_test_mode`, `timezone` (IANA, e.g. `Europe/Stockholm`), `summary_hour` (0–23, local). Every optional topic starts off. Turning a required topic off is refused with `topic_required`; turning on a topic your plan does not include (`available: false`) is refused with `alerts_not_on_plan` (422).

### Request body (required)

- `topics` (optional, object)
- `include_test_mode` (optional, boolean)
- `timezone` (optional, string)
- `summary_hour` (optional, integer)

### Responses

- `200` — The settings after the change.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `400` — `unknown_topic`, `topic_required`, `invalid_timezone`, `invalid_summary_hour`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `422` — `alerts_not_on_plan` — turning on a topic your plan does not include.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Add a notification address

`POST /v1/merchant/notifications/recipients`

Owner or admin. Mails a six-digit code to the address; nothing else is sent to it until the code is entered (`…/verify`). Up to five addresses.

### Request body (required)

- `email` (required, string (email))

### Responses

- `201` — The settings, with the new address pending.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `400` — `invalid_email`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `recipient_limit` — five live addresses already; or the role cannot change notifications.
- `409` — `recipient_exists`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Remove a notification address

`DELETE /v1/merchant/notifications/recipients/{id}`

Owner or admin. The last confirmed address cannot be removed (`last_recipient`): the always-on notices must reach someone.

### Parameters

- `id` (path, required, string (uuid)) — The recipient id.

### Responses

- `200` — The settings after the change.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `last_recipient` — the only confirmed address; or the role cannot change notifications.
- `404` — `recipient_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Confirm an address with its code

`POST /v1/merchant/notifications/recipients/{id}/verify`

Owner or admin. The code expires after 30 minutes and allows five wrong tries.

### Parameters

- `id` (path, required, string (uuid)) — The recipient id.

### Request body (required)

- `code` (required, string)

### Responses

- `200` — The settings, with the address confirmed.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `400` — `invalid_code`, `wrong_code`, `code_expired`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `too_many_attempts` — send a new code; or the role cannot change notifications.
- `404` — `recipient_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Mail a new code

`POST /v1/merchant/notifications/recipients/{id}/resend`

Owner or admin. At most once a minute per address.

### Parameters

- `id` (path, required, string (uuid)) — The recipient id.

### Responses

- `200` — The settings.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `recipient_not_found`.
- `409` — `already_verified`.
- `429` — `code_recently_sent` — at most one code a minute.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Send a test notice

`POST /v1/merchant/notifications/test`

Owner or admin. One test mail to every confirmed address; it appears in the delivery log.

### Responses

- `200` — How many went out.
  - `object` (required, string, one of: notification_test)
  - `recipients` (required, integer)
  - `sent` (required, integer)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Create or replace the private feed URL

`POST /v1/merchant/notifications/feed`

Owner or admin. Returns the Atom and RSS URLs ONCE — the token in them is the feed's only credential and is stored hashed. Any previous URL stops working.

### Responses

- `200` — The new feed.
  - `object` (required, string, one of: notification_feed)
  - `token` (required, string)
  - `atom_url` (required, string (uri))
  - `rss_url` (required, string (uri))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `422` — `alerts_not_on_plan` — the feed is included from a higher plan.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Turn the private feed off

`DELETE /v1/merchant/notifications/feed`

### Responses

- `200` — The settings.
  - `object` (required, string, one of: notification_settings)
  - `plan` (required, object) — Deposit and withdrawal alerts and the always-on notices are on every plan; the other topics and the feed need `required_plan` or above in live mode.
    - `alerts_available` (required, boolean)
    - `required_plan` (required, string)
  - `can_manage` (required, boolean) — Whether this member may change these settings (owner, admin).
  - `mail_configured` (required, boolean) — False when the platform cannot send mail right now.
  - `include_test_mode` (required, boolean)
  - `timezone` (required, string) — IANA zone the daily summary's day is cut in.
  - `summary_hour` (required, integer)
  - `topics` (required, array)
    - `key` (required, string)
    - `label` (required, string)
    - `description` (required, string)
    - `group` (required, string, one of: money_in, money_out, account, digest)
    - `required` (required, boolean) — Always on; cannot be switched off.
    - `available` (required, boolean) — False while your plan does not include this topic; it then reads off.
    - `enabled` (required, boolean)
  - `recipients` (required, array)
    - `id` (required, string (uuid))
    - `email` (required, string)
    - `status` (required, string, one of: pending, verified)
    - `verified_at` (required, string | null (date-time))
    - `code_sent_at` (required, string | null (date-time))
    - `created_at` (required, string (date-time))
  - `max_recipients` (required, integer)
  - `feed` (required, object)
    - `active` (required, boolean)
    - `token_prefix` (required, string | null)
    - `created_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Notices sent, newest first

`GET /v1/merchant/notifications/deliveries`

Console sessions only. Every notice we tried to send, to which address, and whether it went.

### Parameters

- `limit` (query, optional, integer)

### Responses

- `200` — The delivery log.
  - `object` (required, string)
  - `data` (required, array)
  - `has_more` (required, boolean)
  - `next_cursor` (optional, string | null) — Opaque. Pass as `cursor` for the next page. Do not construct or parse it.
  - `data` (optional, array)
    - `id` (required, string (uuid))
    - `topic` (required, string)
    - `topic_label` (required, string)
    - `channel` (required, string, one of: email)
    - `recipient` (required, string)
    - `subject` (required, string)
    - `livemode` (required, boolean)
    - `status` (required, string, one of: sending, sent, failed)
    - `error` (required, string | null)
    - `event_id` (required, string | null)
    - `created_at` (required, string (date-time))
    - `sent_at` (required, string | null (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## The console's notification centre

`GET /v1/merchant/notifications/inbox`

Deposits, payouts, invoices, refunds, conversions, balance adjustments and funding top-ups worth telling a person about, newest first, within your plan's history window — each as a notice in plain words (the same as the notification email). `unread` is across the account for the calling member, up to 100.

### Parameters

- `limit` (query, optional, integer)
- `before` (query, optional, string) — `next_cursor` of the previous page.
- `category` (query, optional, string) — Comma list of categories.
- `unread` (query, optional, string, one of: true, false)

### Responses

- `200` — A page of notices.
  - `object` (required, string, one of: inbox)
  - `data` (required, array)
    - `id` (required, string) — For URLs only; never shown to a person.
    - `category` (required, string, one of: invoices, deposits, payouts, refunds, conversions, balance, top_ups)
    - `tone` (required, string, one of: success, info, warning, error)
    - `subject` (required, string) — One line, for a list row.
    - `heading` (required, string)
    - `summary` (required, string) — What happened, in a sentence.
    - `amount` (optional, object)
      - `value` (required, string)
      - `asset` (required, string)
      - `tone` (required, string, one of: sent, received)
    - `facts` (optional, array)
      - `label` (required, string)
      - `value` (required, string)
      - `mono` (optional, boolean)
    - `hashes` (optional, array)
      - `label` (required, string)
      - `value` (required, string)
      - `href` (optional, string | null)
    - `alert` (optional, string) — Something the reader should act on.
    - `cta` (optional, object)
      - `label` (required, string)
      - `path` (required, string)
    - `livemode` (required, boolean)
    - `created` (required, string (date-time))
    - `unread` (required, boolean)
  - `has_more` (required, boolean)
  - `next_cursor` (required, string | null)
  - `unread` (required, integer)
  - `unread_capped` (required, boolean)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `422` — `invalid_query`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## One notice

`GET /v1/merchant/notifications/inbox/{id}`

### Parameters

- `id` (path, required, string)

### Responses

- `200` — The notice.
  - `id` (required, string) — For URLs only; never shown to a person.
  - `category` (required, string, one of: invoices, deposits, payouts, refunds, conversions, balance, top_ups)
  - `tone` (required, string, one of: success, info, warning, error)
  - `subject` (required, string) — One line, for a list row.
  - `heading` (required, string)
  - `summary` (required, string) — What happened, in a sentence.
  - `amount` (optional, object)
    - `value` (required, string)
    - `asset` (required, string)
    - `tone` (required, string, one of: sent, received)
  - `facts` (optional, array)
    - `label` (required, string)
    - `value` (required, string)
    - `mono` (optional, boolean)
  - `hashes` (optional, array)
    - `label` (required, string)
    - `value` (required, string)
    - `href` (optional, string | null)
  - `alert` (optional, string) — Something the reader should act on.
  - `cta` (optional, object)
    - `label` (required, string)
    - `path` (required, string)
  - `livemode` (required, boolean)
  - `created` (required, string (date-time))
  - `unread` (required, boolean)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `notice_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Mark one notice read

`POST /v1/merchant/notifications/inbox/{id}/read`

For the calling member only.

### Parameters

- `id` (path, required, string)

### Responses

- `200` — Marked.
  - `object` (required, string, one of: inbox_read)
  - `id` (required, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `notice_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Mark every notice read

`POST /v1/merchant/notifications/inbox/read-all`

For the calling member only; every notice there is, not just a loaded page.

### Responses

- `200` — Marked.
  - `object` (required, string, one of: inbox_read_all)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your activity as an Atom feed

`GET /v1/feeds/{token}/events.atom`

Unauthenticated: the `nf_` token in the path is the whole credential (create it with `POST /v1/merchant/notifications/feed`). The latest 50 events, newest first, within your plan's history window. For feed readers and the RSS apps of Slack and Teams.

### Parameters

- `token` (path, required, string)

### Responses

- `200` — Atom 1.0.
  string
- `404` — `feed_not_found` — unknown or replaced token.
- `422` — `alerts_not_on_plan` — the account's plan no longer includes the feed.
- `429` — Rate limit exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your activity as an RSS feed

`GET /v1/feeds/{token}/events.rss`

The same entries as the Atom feed, as RSS 2.0.

### Parameters

- `token` (path, required, string)

### Responses

- `200` — RSS 2.0.
  string
- `404` — `feed_not_found` — unknown or replaced token.
- `422` — `alerts_not_on_plan` — the account's plan no longer includes the feed.
- `429` — Rate limit exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Which chains you have switched on

`GET /v1/merchant/chains`

Your plan includes a number of chains (`included`); every chain you switch on beyond that is charged `extra_chain_cents` a month, prorated for the rest of the month you switch it on in and in full on the 1st after that. Enterprise includes every chain.

The switches limit the WALLET SERVICE only — where `POST /v1/wallets` may issue a static deposit address to one of your customers. Invoices and hosted checkout, your own wallet, payouts and conversions use every chain the platform serves your project (`available`) whatever is switched on here.

A LIVE project that has never chosen has no wallet-service chains: `POST /v1/wallets` answers `chain_not_enabled` until you switch something on here or in the dashboard. A test project with no choice gets every chain the platform serves, so trying the API needs no setup. Once you choose, the choice applies in both modes.

`effective` is what `POST /v1/wallets` gets right now — your selection narrowed to what the platform serves your project, the same list `GET /v1/ping` reports as `wallet_chains`.

### Responses

- `200` — Your switches.
  - `object` (required, string, one of: merchant_chains)
  - `plan` (required, string) — Your plan's key.
  - `included` (required, integer | null) — How many chains the plan includes. `null` = every chain.
  - `extra_chain_cents` (required, string | null) — Monthly price of a chain beyond the included count, in cents. `null` = none can be added.
  - `chosen` (required, boolean) — True once you have chosen at least once. Until then a LIVE project has no chains and a test project has every chain the platform serves.
  - `selected` (required, array, of string) — Your switches, in the order you flipped them on. The first `included` are free; the rest are extras.
  - `effective` (required, array, of string) — What your requests actually get right now: the selection narrowed to what the platform serves you, or the test-mode default.
  - `chains` (required, array)
    - `chain` (required, string)
    - `name` (required, string)
    - `available` (required, boolean) — The platform serves this chain to your project, so it can be switched on.
    - `on` (required, boolean)
    - `role` (required, string | null, one of: included, extra, null) — `included` = free on your plan, `extra` = charged monthly, null = off.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `merchant_not_found`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Switch chains on or off

`PUT /v1/merchant/chains`

Send the full list of chains you want ON. Chains already on keep their place in the order (and so whether they are included or extra); new ones go on the end; anything missing is switched off. Requires `merchant:write`.

Switching an EXTRA chain on charges the rest of this month to your operational balance at once and is refused if the balance cannot carry it. Switching a chain off stops new addresses and invoices on it immediately, credits nothing back for the month, and never touches addresses your customers already hold — they keep receiving.

### Request body (required)

- `chains` (required, array, of string) — Every chain you want on, e.g. `["bsc", "polygon"]`.

### Responses

- `200` — Your switches after the change.
  - `object` (required, string, one of: merchant_chains)
  - `plan` (required, string) — Your plan's key.
  - `included` (required, integer | null) — How many chains the plan includes. `null` = every chain.
  - `extra_chain_cents` (required, string | null) — Monthly price of a chain beyond the included count, in cents. `null` = none can be added.
  - `chosen` (required, boolean) — True once you have chosen at least once. Until then a LIVE project has no chains and a test project has every chain the platform serves.
  - `selected` (required, array, of string) — Your switches, in the order you flipped them on. The first `included` are free; the rest are extras.
  - `effective` (required, array, of string) — What your requests actually get right now: the selection narrowed to what the platform serves you, or the test-mode default.
  - `chains` (required, array)
    - `chain` (required, string)
    - `name` (required, string)
    - `available` (required, boolean) — The platform serves this chain to your project, so it can be switched on.
    - `on` (required, boolean)
    - `role` (required, string | null, one of: included, extra, null) — `included` = free on your plan, `extra` = charged monthly, null = off.
- `400` — `invalid_body`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `merchant_not_found`.
- `409` — `merchant_not_active`.
- `422` — `chain_not_available` — the platform does not serve that chain to your project. `extra_chains_need_live` — a chain beyond the included count needs a live, funded plan. `extra_chains_not_on_plan` — the plan cannot add chains. `billing_balance_exhausted` — top up to cover the rest of the month.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.