Every operation below is rendered during the static build from the curated
OpenAPI artifact. The JSON examples are the same generated files exercised by
the contract validator.
GET/api/channel/api-keys
List the calling channel's API keys
List every API key issued to the calling channel, newest first, revoked ones included — which key was live when is part of the record. This listing never returns secrets; plaintext is available only from creation or the invitation flow's bounded response recovery. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
Issue an additional key for the calling channel and return its plaintext **once** — it is stored only as a hash and cannot be read again. This is the first half of a rotation: mint, deploy, then revoke the old key. There is a cap on how many keys a channel may hold active at a time; revoke one you no longer deploy before minting past it. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
Stop a key working, immediately and permanently. Revoking a key that is already revoked succeeds and changes nothing, so a retry after a lost response is safe. A channel may revoke the key it is calling with — including its last one. That is deliberate: a leaked credential is worth more urgency than a locked-out integration. Recovering from it is not instant, so know the cost before you spend it: an operator can no longer hand over a replacement key directly, only issue an invitation, which your registered owner has to receive and redeem before your integration can call again. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
Every merchant owned by the calling channel, newest first, paged. Authenticate with a channel API key (Authorization: Bearer sk_test_...); the key names the channel, so no channel id is taken from the request and a merchant belonging to anyone else — or to no channel at all — is never returned.
Each row carries onboarding_status and payments_enabled, the two fields that decide what you can do with a merchant today, alongside the business_name and email you supplied when you provisioned it. That makes this the way back to a merchant_id you no longer hold: email is unique across the platform and you chose it, so ?email= returns exactly the merchant you mean — an exact, case-insensitive equality match, never a substring.
Narrow with onboarding_status, repeatable — ?onboarding_status=pending&onboarding_status=in_progress is every merchant not yet through PayPal, in one call instead of a scan. search matches a case-insensitive substring of business_name or email, for browsing. Page with limit (default 50, max 200) and offset; total is the count under the same filters, so it says when to stop.
This read does not call PayPal. It reports what we last recorded, which is what a webhook or the onboarding sweep last told us. Use GET /api/channel/onboarding/status for the live, PayPal-refreshed state of one merchant and for the per-capability map this listing omits.
Authentication
ChannelApiKey
Parameters
onboarding_statusqueryOptional
Keep only merchants in these onboarding states. Repeat the parameter for more than one. Filtering is on the stored state.
onboarding_status parameter schema
{
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/OnboardingStatus"
},
"type": "array"
},
{
"type": "null"
}
],
"description": "Keep only merchants in these onboarding states. Repeat the parameter for more than one. Filtering is on the stored state.",
"title": "Onboarding Status"
}
searchqueryOptional
Case-insensitive substring of business_name or email, for browsing. Stripped before it is measured, so a value of only whitespace is a 422 rather than a filter that silently matches everything. For recovering a merchant_id, prefer email.
search parameter schema
{
"anyOf": [
{
"maxLength": 255,
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
],
"description": "Case-insensitive substring of `business_name` or `email`, for browsing. Stripped before it is measured, so a value of only whitespace is a `422` rather than a filter that silently matches everything. For recovering a `merchant_id`, prefer `email`.",
"title": "Search"
}
emailqueryOptional
Exact, case-insensitive match on the address you supplied at provisioning — the lost-merchant_id recovery key. Returns at most one merchant, because emails are unique across the platform. A value that is not an email address is a 422.
email parameter schema
{
"anyOf": [
{
"format": "email",
"type": "string"
},
{
"type": "null"
}
],
"description": "Exact, case-insensitive match on the address you supplied at provisioning — the lost-`merchant_id` recovery key. Returns at most one merchant, because emails are unique across the platform. A value that is not an email address is a `422`.",
Create a new merchant owned by the calling channel. Authenticate with a channel API key (Authorization: Bearer sk_test_...); ownership always comes from the credential, never from the request body — a channel_id field in the body is rejected as unknown input. country_code is required and must identify the merchant's real ISO country; omission is rejected rather than defaulted to US. The created merchant is an ordinary top-level merchant marked with this channel's id; see docs/CHANNELS.md.
Start the PayPal Integrated Sign-Up flow for one of the caller's merchants and return action_url. Maxana renders no onboarding UI of its own: open action_url inside PayPal's own iframe or mini-browser. 409 means either that the merchant already completed onboarding, which retrying cannot change, or that its PayPal link state changed while the referral was being minted. In the latter case no action_url is returned; read current onboarding status and retry only after any conflict or hold is resolved. Retrying a call that failed is safe: each attempt carries an idempotency key of its own, so a retry after a 503 gets back the referral PayPal may already have minted rather than a second one — while a call made after a successful answer is a new attempt, and mints a new referral. To send a connected merchant back to PayPal on purpose — to grant scopes PayPal reports missing, or to move to another PayPal account — send {"reconnect": true}; the merchant stays connected and keeps taking payments until they finish at PayPal. A failure says whose it was: 503 if PayPal could not be reached, or answered 429/5xx meaning ask again later — carrying PayPal's own Retry-After when it sent one; 502 if PayPal refused the referral, which retrying unchanged will not fix; 500 only for a fault on Maxana's side. Authenticate with a merchant or channel API key (Authorization: Bearer sk_test_...). A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the call is for; an ordinary merchant key does not need it.
Read onboarding and PayPal capability status for a channel merchant
Read one of the caller's merchants' onboarding progress and PayPal capability status — which payment features (Advanced Card Checkout, Venmo/Pay Later platform-fee checkout, ...) PayPal has granted so far — so the channel can show its merchant where things stand. A capability PayPal has not granted also says why: in_review means PayPal is still underwriting it and nobody needs to act, while need_more_data means PayPal has asked the merchant for supplementary information and will not move until it arrives. Authenticate with a merchant or channel API key (Authorization: Bearer sk_test_...). A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the call is for; an ordinary merchant key does not need it.
Every statement issued to the calling channel, newest period first. A statement is a closed period's record of what Maxana owes the channel: it is stored as issued and never recomputed, so a merchant that later moves to another channel cannot change what a past period says. Amounts are counted at **capture**, not at settlement, and every statement says so in volume_basis. A statement covers exactly one currency and never mixes them. Statements that have been superseded by a correction are hidden unless include_superseded=true; the correction links back to the one it replaced. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
One statement, exactly as it was issued. Alongside the totals it carries a per-rail breakdown covering **all** of the channel's volume in the period, including rails whose platform fee this platform does not record: those show platform_fee_accrued_cents: null — unknown, not zero — and are excluded from the amount owed, with the reason stated. Volume that is present in your webhook feed but absent from the settlement is therefore accounted for rather than missing. Amounts are counted at **capture**, not at settlement, and every statement says so in volume_basis. A statement covers exactly one currency and never mixes them. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
The per-transaction detail of a statement, oldest capture first, so it can be reconciled line by line against your own webhook feed: each line carries the transaction id you were sent, the provider's capture id, the merchant, the platform fee and what that line owes you. Paging is by limit/offset and is stable — an issued statement's lines cannot change, so page two is still page two however long the walk takes; total says when to stop. Lines of type fee_refund_adjustment are platform fee given back on a capture an earlier statement already counted: they carry no volume and a negative amount. Authenticate with a channel API key (Authorization: Bearer sk_test_...). The key acts on the channel that issued it and no other, so no channel id is taken from the request.
Return every endpoint this channel owns, newest first.
Secrets are never included — only the hint kept for recognising which
secret a row holds. The plaintext exists once, in the create and rotate
responses.
Subscribe the calling channel to its merchants' events
Create an endpoint receiving events for every merchant in this channel.
The response carries the signing secret in plaintext, and it is the only
time it is readable — the row stores it encrypted alongside a hash and a
hint. Store it on receipt; there is no endpoint that returns it again, only
one that replaces it. A temporary DNS resolution failure returns 424 so the
caller can retry without treating an otherwise valid URL as permanently bad.
Partially update one of the channel's reusable webhook endpoints
Update any non-empty subset of url, description, and event_types. event_types is a complete replacement list, never a merge. To replace a URL, first disable the endpoint; PATCH keeps the endpoint id and signing secret for same-host changes. A different host requires rotate_secret=true; the response returns the new secret once. Then POST .../enable resumes future delivery. A successful enable does not replay retained NO_SUBSCRIBER events; replay those explicitly if needed. If an echoed current URL becomes stale before serialization, PATCH returns 409 so the caller can read the latest state and retry. A temporary DNS resolution failure returns 424 and leaves the endpoint unchanged. For example, PATCH a disabled endpoint with {"url": "https://receiver.example/webhooks", "rotate_secret": true}, install the returned secret, and then enable it.
Resume delivering to one of the channel's disabled endpoints
Flip a DISABLED endpoint back to ACTIVE. It keeps the same id, URL, signing secret, and event_types; only delivery resumes. This does not retry events enqueued while the endpoint was disabled and already preserved in the no_subscriber state. Contact Maxana if those events need to be replayed explicitly.
Issue a new signing secret for an endpoint and return it once.
Before this, replacing a secret meant deleting and recreating the endpoint,
which for a channel means its entire book stops being delivered to while the
endpoint does not exist, and the endpoint id changes.
**This is a hard cutover.** The previous secret stops signing immediately,
so accept the new one before calling this — signature verification against
the old secret will fail on the very next delivery.
Create an immutable server-side checkout session and return the hosted checkout_url and embed_url. Authenticate with a merchant API key (Authorization: Bearer sk_test_…). Pass an optional Idempotency-Key header (<=128 chars) to make retries safe. A channel-scoped key (acting for several merchants) must also pass X-Merchant-Id naming which of its merchants the session is for; an ordinary merchant key does not need it.
Mint the short-lived PayPal client token a browser needs before it can render hosted card fields (Advanced Credit and Debit Card). Without it there is no card form, so any integration that takes cards on its own page calls this. Pass the returned client_token to the PayPal JS SDK as its data-client-token attribute.
No credential is sent: the checkout session id is the capability, and the call is made from the buyer's page, cross-origin, with no Authorization header. Mint one token per page load rather than caching it — tokens are short-lived. Returns 409 while the session is capturing or after it has finished, and 422 if the merchant has no connected PayPal account.
List the disputes and chargebacks raised against the authenticated merchant, newest first. Maxana mirrors them from the payment provider and keeps them in sync from provider webhooks, so there is nothing to poll upstream. Page with limit (1-100, default 20) and offset. Authenticate with a dashboard bearer token, a merchant API key, or a channel API key naming one of its own merchants in X-Merchant-Id.
Fetch one dispute by its Maxana id. Returns everything the list returns, plus available_actions — the response endpoints this dispute currently accepts, which depend on its status and lifecycle stage — the sanitized provider_payload, and any notes the Maxana operations team has shared with the merchant. Check available_actions before calling a response endpoint: an action the dispute does not accept is rejected. Same authentication as the list. Returns 404 if the dispute does not belong to this merchant.
Accept liability on a native PayPal dispute claim for the authenticated merchant. This action instructs PayPal to refund the buyer and close the claim.
Give a buyer their money back, in full or in part.
Omit the body (or omit amount) to refund the whole capture; send
{"amount": 10.00} to refund part of it. The capture must belong to the
merchant the credential acts for.
Authenticate with a dashboard session holding refunds.manage (Finance
tier), a merchant API key, or a channel API key naming one of its merchants
with X-Merchant-Id. A channel's merchants have no dashboard by design, so
without the key paths nobody but a Maxana employee could refund their
buyers.
Send an Idempotency-Key header if you retry: a repeat of a key already
used replays the original outcome instead of refunding twice.
Mint a Gr4vy Embed token for a buyer checkout session.
Requires a session whose merchant is routed to Gr4vy **and** holds their own
Gr4vy credential — minting is gated on the resolved provider so a PPCP-routed
session can never obtain a Gr4vy token. Availability is a per-merchant question,
so the session is loaded before it can be answered.
Fetch the Collect.js tokenization key for a checkout session
Return the public tokenization key that Collect.js needs for a checkout session.
Card entry stays on the Maxana checkout page: Collect.js renders NMI's own
iframed fields inside our form and hands back a single-use payment_token. The
key it needs is public by design — NMI restricts it to minting tokens — but it
belongs to one merchant, so it is served per session rather than shipped in the
checkout bundle.
Requires a session whose merchant is routed to NMI, so a PayPal- or Gr4vy-routed
session can never obtain an NMI key. A POST rather than a GET keeps the session
id out of URLs, logs and referrers, matching the Gr4vy token endpoint beside it.
Create a new provider order for payment.
Public buyer checkout must pass checkout_session_id. The amount, currency,
description, items, and merchant are loaded from the immutable merchant-created
checkout session; browser-supplied financial fields are rejected/ignored.
A merchant-hosted checkout (X-Maxana-Checkout-Mode: direct) must also pass
checkout_page_url — the URL of the page the widgets are mounted on. It
becomes PayPal's return_url/cancel_url, so a redirect-based approval
(mobile web, Pay Later) brings the buyer back to the merchant's own page with
?paypal_return=1 or ?paypal_cancel=1 appended, instead of to Maxana's
/pay page. It must be https and on the very origin the request came from —
a session's allowed_origins may hold several, and a page on one of them
must not be able to send the buyer back to a different merchant's page.
Anything else is refused rather than quietly redirected to Maxana. Hosted and
embedded checkouts must not send it.
Capture an approved payment order and return the resulting transaction.
A buyer page calls this with no credentials: it identifies the checkout with
the X-Maxana-Checkout-Session-Id header, declares which kind of page it is
with X-Maxana-Checkout-Mode (hosted, embedded or direct) and repeats
its own origin in X-Maxana-Checkout-Origin. The response is then the
sanitized public view — internal ids and merchant account fields are not
leaked to it. A merchant calling with their own API key gets the full
transaction instead, and needs none of those headers: the merchant is taken
from the key, and for a public call from the stored transaction. A channel
calls it the same way with its channel API key, naming which of its
merchants the order belongs to in X-Merchant-Id; it can capture for the
merchants it carries and for no others.
Calling it from a server is how an approved payment is recovered when the
buyer's browser never came back to finish it. An approval is not a capture
and nothing completes it on its own, and the approved order cannot be
replaced by starting a new one — so until this call is made the money has
not moved.
List your payment requests, newest first.
Authenticate with a dashboard session or merchant API key. A channel API key can act
for one of its merchants by sending X-Merchant-Id; the named merchant must belong to
that channel.
Readable by any dashboard role: knowing that a customer was asked for money is part of
reading the account, even for a role that cannot ask.
Send a customer a text message asking them to pay a given amount.
Authenticate with a dashboard session or merchant API key. A channel API key can act
for one of its merchants by sending X-Merchant-Id; the named merchant must belong to
that channel.
The customer receives a message composed by the payment provider, containing a link to
a hosted wallet where they can pay by card. The message is sent from the provider's own
number, not yours, and cannot be recalled once accepted — so a request is rate-limited,
capped in amount, and refused if an identical one went out moments ago.
Your account also has a **daily send allowance**, counted over a rolling 24 hours.
Spending it answers 429 naming the allowance and the time it frees up — the same
status as a duplicate, and lifted the same way, by waiting. Ask support if your volume
needs a higher one.
Requires a connected NMI gateway account. Pass external_order_id to tie the request
to your own order; it is stored on our side and is the only field of yours that
survives, since the provider carries none.
The request comes back pending. It stays pending until the customer pays or their
window closes — the provider offers no delivery receipt and no callback, so poll
GET /api/text-to-pay/requests/{id} rather than waiting for a webhook.
**Do not retry every 502.** Two of them mean opposite things, so they are told apart
by detail.code rather than by their status:
- text_to_pay_provider_refused (retryable: true) — the provider rejected the
request and nothing was created. Retrying is safe.
- text_to_pay_unconfirmed (retryable: false) — we could not confirm the request and
the message may already be on your customer's phone. Retrying is what charges them
twice. detail.request_id names the request; it shows as unconfirmed until support
settles it, and a later identical send is refused with 409text_to_pay_unsettled until then.
Read one payment request, to find out whether the customer has paid.
Authenticate with a dashboard session or merchant API key. A channel API key can act
for one of its merchants by sending X-Merchant-Id; the named merchant must belong to
that channel.
This is how you learn the outcome. The provider offers no webhook and no delivery
receipt, so a request stays pending until our reconciliation reads the conversation
back — poll this rather than waiting to be told.
Withdraw a request the customer has not paid, so its link stops working.
Authenticate with a dashboard session or merchant API key. A channel API key can act
for one of its merchants by sending X-Merchant-Id; the named merchant must belong to
that channel.
The cancellation happens at the provider first and is recorded here only if it
succeeded — a request marked canceled on our side alone would leave your customer
holding a link they can still pay.
Only a pending request can be canceled, and every other answer means it was not:
- 404 — no such request under your account.
- 409 — it cannot be canceled. Either it has already been paid, expired or canceled,
or it is still sending, meaning we never got confirmation from the provider. That
last one does not clear on its own and retrying will not change it: the message may
be live and only support can settle it. Do not resend the request.
- 502 — we reached the provider and the cancellation did not take. **Treat the link
as still payable**, and do not tell your customer it is dead.
- 503 — the rail is not configured on this deployment.
List a merchant's transactions with optional filters and pagination. A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Authentication
HTTPBearer
MerchantApiKey
ChannelApiKey + ChannelMerchantId
Parameters
statusqueryOptional
Filter by status
status parameter schema
{
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Filter by status",
"title": "Status"
}
payment_methodqueryOptional
Filter by payment method
payment_method parameter schema
{
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Filter by payment method",
"title": "Payment Method"
}
processorqueryOptional
Filter by processor (e.g. stripe)
processor parameter schema
{
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Filter by processor (e.g. stripe)",
Daily revenue and transaction counts for a merchant, bucketed by UTC calendar day over a bounded window. A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Authentication
HTTPBearer
MerchantApiKey
ChannelApiKey + ChannelMerchantId
Parameters
daysqueryOptional
Number of days
days parameter schema
{
"default": 30,
"description": "Number of days",
"maximum": 90,
"title": "Days",
"type": "integer"
}
X-Merchant-IdheaderOptional
X-Merchant-Id parameter schema
{
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "X-Merchant-Id"
}
Responses
200 response
Successful Response
Inline 200 response schema
{
"items": {
"$ref": "#/components/schemas/DailyRevenue"
},
"title": "Response Get Daily Revenue Api Transactions Daily Revenue Get",
List the payment methods and processors present in a merchant's transactions. A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Aggregated stats, the completed provider-history scope, and whether a widened scope still awaits backfill. A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Get a single transaction by id, scoped to the authenticated merchant. A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Everything the payment provider reports about a transaction (AVS/CVV, connector, etc.). A channel-scoped key must also pass X-Merchant-Id naming which of its merchants the read is for; an ordinary merchant key does not need it.
Charge a customer's saved method with merchant-initiated semantics.
Authenticate with a dashboard session holding finance.manage, a
merchant API key, or a channel API key naming one of its merchants with
X-Merchant-Id. Obtain customer_ref from GET /api/vault/tokens
and provide a unique idempotency_key for each intended charge; retrying
that key returns the original transaction instead of charging twice.
List each customer's selected instrument without provider vault secrets.
Authenticate with a dashboard session holding finance.manage, a merchant
API key, or a channel API key naming one of its merchants with
X-Merchant-Id. The returned customer_ref is the value accepted by
POST /api/vault/charge. Use limit and offset to page through a
merchant with more than 100 customers holding active instruments. When a
customer has multiple active tokens, only the newest selectable token is
returned.
Authentication
HTTPBearer
MerchantApiKey
ChannelApiKey + ChannelMerchantId
Parameters
limitqueryOptional
limit parameter schema
{
"default": 100,
"maximum": 100,
"minimum": 1,
"title": "Limit",
"type": "integer"
}
offsetqueryOptional
offset parameter schema
{
"default": 0,
"minimum": 0,
"title": "Offset",
"type": "integer"
}
X-Merchant-IdheaderOptional
X-Merchant-Id parameter schema
{
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "X-Merchant-Id"
}
Responses
200 response
Successful Response
Inline 200 response schema
{
"items": {
"$ref": "#/components/schemas/VaultTokenResponse"
},
"title": "Response List Vault Tokens Api Vault Tokens Get",
Only operations in the curated Public API appear here. Generated operation
cards do not show dashboard, admin, or internal routes and do not replace the
audience-specific tutorial that explains when an operation belongs in a flow.