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

POST /external/v1/payments/prompt

payment_prompts:write

201 { prompt, checkoutUrl }

POST /external/partner/payments/:id/refund

payment_prompts:write

201 { refundId }

GET /external/partner/overview

partner_overview:read

200 { partner, metrics, attention, volumeTrend }

GET /external/partner/merchants

partner_merchants:read

200 { data, total, page, limit }

GET /external/partner/merchants/:merchantId

partner_merchants:read

200 merchant details, branches, aggregates and recent payments

GET /external/partner/payments

partner_payments:read

200 { data, total, page, limit }

GET /external/partner/payment-filters

partner_payments:read

200 { merchants: [{ id, name }], blockchainIds }

GET /external/partner/payments/:paymentId

partner_payments:read

200 partner payment reporting row

GET /external/partner/team

partner_team:read

200 { data }

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 returns partner_api_refund_disabled (E2104, HTTP 403).

  • Merchant-admin keys may also call this same endpoint for their own payments, sending {} instead of merchantId. 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

parameter_invalid (E1001)

Invalid ID/body, missing partner merchantId, merchant key supplying merchantId, or failed refund eligibility such as a non-successful payment or missing shopper email.

400

parameter_unknown (E1002)

An unsupported body field, such as amount or a destination wallet.

400

merchant_not_configured (E1030)

ZeroHash is disabled globally or inactive for the selected merchant.

403

partner_api_refund_disabled (E2104)

The partner's API refunds permission is disabled.

404

resource_missing (E3001)

Unowned/missing merchant or payment, branchless payment, non-ZeroHash payment, or a refund record supplied as the original payment.

409

conflict (E1040)

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

search

Partial match across merchant name/ID, payment ID, address, transaction hash and customer reference.

status

pending, successful, unsuccessful, expired or cancelled.

transactionId

Partial payment ID, not a blockchain transaction hash.

merchantId

Exact merchant UUID.

merchantName

Partial merchant name.

addressSearch

Partial deposit address.

txHash

Partial transaction hash.

blockchainId

Supported blockchain ID.

startDate, endDate

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.