Merchant-Partner API
Merchant partners can create payments and initiate refunds for their assigned merchants, and read partner-scoped reports. These endpoints use the five HMAC signing headers from Authentication, not dashboard JWT authentication. Sign the full path, including /external/partner for refunds and reporting or /external/v1 for payment creation, and the exact query string.
This API is separate from acquisition-partner onboarding. A partner account must be active and the key must have the scope required by the route. Merchant keys cannot access partner reporting routes; partner keys cannot access the merchant-only GET routes under /external/v1.
Scopes and Routes
Method and path |
Required scope |
Success response |
|---|---|---|
|
|
201 |
|
|
201 |
|
|
200 |
|
|
200 |
|
|
200 merchant details, branches, aggregates and recent payments |
|
|
200 |
|
|
200 |
|
|
200 partner payment reporting row |
|
|
200 |
Partner developer keys are currently restricted to partner_overview:read, even if other scopes are stored on the key. Partner owner keys can use the partner scope set above. The application rate limiter and structured errors also apply to these routes.
Create on Behalf of a Merchant
Use the External Payments API create contract, adding merchantId. The ID must be a UUID for an active merchant assigned to your partner. Missing merchantId returns E1001; an unowned merchant returns E3001. The target merchant's default Main branch, onboarding, network configuration and redirect allowlist apply.
// Use partner credentials with the signed-fetch helper.
// Load a persisted key for this intended payment; reuse on retries.
const response = await miraclePayFetch(
"POST",
"/external/v1/payments/prompt",
{
merchantId: "550e8400-e29b-41d4-a716-446655440000",
amount: 1099, // $10.99 in cents
customerReferenceId: "order_12345",
},
{ "Idempotency-Key": idempotencyKey },
);
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.errorCode}: ${error.message}`);
}
const { prompt, checkoutUrl } = await response.json();
The response uses the same cents-based payment object as merchant creation. To read it afterwards using partner credentials, use GET /external/partner/payments/:paymentId and its reporting contract below.
A linked merchant can instead use its own key only when its partner has enabled self-service API payments. Otherwise merchant payment routes return E2103. That setting does not replace ownership checks on partner-key creation.
Refund on Behalf of a Merchant
POST /external/partner/payments/:id/refund starts the same full-refund flow as the merchant dashboard. The path id is the original payment UUID. This endpoint is under the partner namespace, not /external/v1.
Use a merchant-partner owner key with payment_prompts:write and provide the target merchantId in the body. The server verifies both that the active merchant belongs to the authenticated partner and that the payment belongs to that merchant through its branch. Another partner's merchant, a mismatched payment, or a payment without a branch cannot be refunded. Partner developer keys cannot use this route.
const response = await miraclePayFetch(
"POST",
`/external/partner/payments/${paymentId}/refund`,
{ merchantId: "550e8400-e29b-41d4-a716-446655440000" },
);
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.errorCode}: ${error.message}`);
}
const { refundId } = await response.json();
// Store this ID for reconciliation. The refund is not yet paid out.
Access and eligibility
A MiraclePay administrator must enable API refunds on the partner's create/edit form. This partner-level permission (
apiRefundEnabled) defaults to disabled and allows the partner to refund its linked merchants' payments via API. Disabled access returnspartner_api_refund_disabled(E2104, HTTP 403).Merchant-admin keys may also call this same endpoint for their own payments, sending
{}instead ofmerchantId. A partner-linked merchant needs both its partner's API refunds permission and the existing self-service API payment permission; the latter returns E2103 when disabled. A merchant without a partner is not subject to the partner toggles.Toggle changes apply to existing keys on subsequent requests. Turning API refunds off blocks new API initiation, not merchant-dashboard refunds or the continuation of existing refunds. It does not disable payment creation or cancellation.
ZeroHash must be enabled globally and active for the target merchant. The original payment must be a successful ZeroHash payment, have a positive refundable amount and a shopper email on file, and must not already have a refund.
Request and result
Only merchantId is accepted in the JSON body, and only for partner keys. It must be a UUID. Do not send amount, destination wallet or other fields: partial refunds are not supported. The five signing headers, exact-body signature, nonce rules and structured errors are the same as payment creation.
HTTP 201 returns { "refundId": "<uuid>" }. The server creates a separate pending refund record, links it to the original payment, and attempts to email the shopper a claim link. Email delivery is best-effort; a successful response does not guarantee delivery or payout. The original payment remains successful. Do not treat this response or the original payment status as confirmation that funds were returned.
Subscribe the partner's webhook endpoint to refund.initiated for creation and refund.completed for confirmed settlement. The merchant may independently subscribe its own endpoint to either event; existing subscriptions are not automatically extended. Match data.object.id to the returned refundId and data.object.paymentPromptId to the original payment. See Refund Notifications for the payload, partner merchant identity, delivery ordering and recovery limits.
Unlike payment creation, this route does not provide Idempotency-Key response replay. Repeated or concurrent initiation returns HTTP 409 once a refund exists. After an uncertain response, reconcile the existing refund through the merchant dashboard or support rather than interpreting a conflict as payout confirmation.
Refund-specific errors
HTTP |
Code |
Meaning |
|---|---|---|
400 |
|
Invalid ID/body, missing partner |
400 |
|
An unsupported body field, such as |
400 |
|
ZeroHash is disabled globally or inactive for the selected merchant. |
403 |
|
The partner's API refunds permission is disabled. |
404 |
|
Unowned/missing merchant or payment, branchless payment, non-ZeroHash payment, or a refund record supplied as the original payment. |
409 |
|
A refund already exists or another request won the concurrent initiation. |
Standard authentication, onboarding, scope and rate-limit errors also apply; see Error Reference.
Reporting Queries
Both merchant and payment lists use page (minimum 1, default 1) and limit (1–100, default 20), not merchant payment cursors. Responses contain data, total, page and limit. Lists are ordered newest-first by creation time.
Merchant list accepts search (name, email or ID, at most 255 characters) and actionRequired=true to restrict results to merchants awaiting verification.
Partner payment list accepts:
Parameter |
Meaning |
|---|---|
|
Partial match across merchant name/ID, payment ID, address, transaction hash and customer reference. |
|
|
|
Partial payment ID, not a blockchain transaction hash. |
|
Exact merchant UUID. |
|
Partial merchant name. |
|
Partial deposit address. |
|
Partial transaction hash. |
|
Supported blockchain ID. |
|
ISO date/time bounds on creation time, inclusive. |
Text filters have a 255-character maximum. actionRequired is inherited by the payment query DTO but does not filter payment results. Detail path IDs must be UUIDs; unowned or missing details return E3001.
Reporting Response Differences
Warning
Partner reporting is not the canonical merchant payment DTO. Payment reporting amount and commissionAmount are database decimal USD values in dollars, not integer cents. Do not reuse the merchant DTO's money decoder. Partner overview and merchant aggregate volumes also use dollars. Payment creation still takes and returns cents.
Partner payment list rows and payment detail contain these fields:
id, amount, commissionAmount, tokenAmount, status, sessionStatus,
flow, paymentSystem, blockchainId, address, transactionHash,
customerReferenceId, note, createdAt, expiresAt, paidAt,
branchId, branchName, merchantId, merchantName
There is no object: "payment_prompt" discriminator on these reporting rows. They expose reporting fields such as paymentSystem and address that merchant payment responses deliberately omit. Do not assume settlementModel, lastError or cancellationReason are present.
Overview metrics include totalMerchants, approvedMerchants, actionRequired, volume30d, totalVolume and totalPayments. Merchant detail includes branches, merchant payment aggregates and up to ten recent payment entities, rather than the canonical merchant DTO. payment-filters returns assigned merchant choices and the non-null blockchain IDs present in the partner's payment history; it is not an active-network discovery endpoint.