Skip to main content

Merchant Public API

The Merchant Public API is the programmatic integration surface for server-to-server and embedded-checkout use cases. Merchant-scoped routes use the same Merchant API keys as the dashboard (Developer Tools → API Keys). Partners that onboard sub-merchants use those keys on Partner Onboarding — there is no separate platform credential.

Authentication​

Send your key as a Bearer token:

Authorization: Bearer pai_test_<your_sandbox_key>
EnvironmentKey prefixBase URL
Sandboxpai_test_*https://sandbox.mor.payments.ai/api
Livepai_live_*https://mor.payments.ai/api

Merchant keys (pai_test_* / pai_live_*) are scoped to their own merchant and to any sub-merchant that merchant has onboarded. The {merchantId} path segment must match the key's merchant or one of its sub-merchants. There is no separate platform credential — see Partner Onboarding.

Route prefix​

Merchant-scoped routes live under:

/v1/public-api/merchants/{merchantId}/...

Partner onboarding is not merchant-scoped. Create a sub-merchant with POST /v1/public-api/merchants using your own Merchant API Key; the parent is derived from the key, not from the path. Reading it back with GET /v1/public-api/merchants/{merchantId} is merchant-scoped like every other route. Details: Partner Onboarding.

Resources​

AreaMethodsNotes
Partner onboardingPOST /v1/public-api/merchants; GET /v1/public-api/merchants/{merchantId}; PATCH /v1/public-api/merchants/{merchantId}; POST .../join-linkCreate a sub-merchant, read ownerAttached and activation, set HTTPS websiteUrl, and remint owner-claim links. See Partner Onboarding.
Team (assign)POST /membersCopy an active member of your team onto a managed merchant. See Partner Onboarding.
API keysGET, POST /api-keys; DELETE /api-keys/{id}Mint, list, and revoke keys for your merchant or sub-merchants.
ProductsGET, POST /products; GET, POST /products/{id}/plansCreate catalog entries and plans. Plan responses may include taxBehavior (per-plan override, or null to inherit the merchant default). Product detail may include optional productTaxCodeId.
Tax settingsGET, PATCH /settings/taxRead or update { defaultTaxBehavior: "exclusive" | "inclusive" } for the merchant. Plan overrides use taxBehavior on create/update plan routes.
CheckoutGET /checkout/plans/{planId}; POST .../session; POST .../confirm; GET .../receipts/{receiptId}Resolve embed context, checkout sessions, and payment confirmation
OrdersPOST /orders; GET /orders/{orderId}Create and read an order. See Order checkout.
CustomersGET /customers, /customers/{id}Read-only
TransactionsPOST /transactions; GET /transactions, /transactions/{id}Charge a bare amount with a ctok_ token; list/read ledger rows after settlement
InvoicesGET /invoices, /invoices/{id}, /invoices/{id}/downloadRead-only + PDF download. List and detail include payUrl for open and past_due invoices with a positive amount, and null otherwise.
BalancesGET /balances/summaryAvailable, pending, and total funds by currency.
Payout methodsGET /payouts/payout-methodsSaved payout methods. Add a payout method in the dashboard.
PayoutsPOST /payoutsRequest a payout of available funds. Requires Idempotency-Key.

Browse operation details in the API Reference (filter or search for /v1/public-api/).

Request a payout​

Send this from your server with your API key. Do not put the API key in a buyer's browser.

Read available funds first:

GET /v1/public-api/merchants/{merchantId}/balances/summary

The summary lists each currency with available, pending, total, and isNegative.

List saved payout methods:

GET /v1/public-api/merchants/{merchantId}/payouts/payout-methods

You add a payout method in the dashboard. This API lists those methods so you can choose one. It does not create them.

Request the payout:

POST /v1/public-api/merchants/{merchantId}/payouts

Send:

  • amount — how much to pay out, as a number greater than zero
  • currency — optional three-letter code such as usd. When you omit it, the request uses usd
  • payout_method_id — the id of a saved payout method

You get back { withdrawalId }, the id of the payout you requested.

Send header Idempotency-Key (max 255 characters). Retries with the same key and body return the original withdrawalId without creating a second payout.

The request is rejected when available funds are too low, when the merchant is not set up to receive payouts, or when the API key is for a different merchant.

Payouts are limited to the API key's own merchant. A parent merchant's key can read a sub-merchant's balance and payout methods, but it cannot request a payout for that sub-merchant: use a key minted for the sub-merchant.

Charge a bare amount (payment token)​

For funnels where you compute the total (no PAIM catalogue plan on the page), call:

POST /v1/public-api/merchants/{merchantId}/transactions

Send a payment token (ctok_…) from your client SDK — never a card number or PAN. Card data stays in the hosted payment fields. The body includes amount, currency, returnUrl, and optional description, recurring, and metadata. amount is what the buyer pays today. With recurring and no trial, it includes the first period, so it must be at least recurring.amount.

The response is { paymentId, clientSecret, status } for handleNextAction on the client. A transaction row in PAIM is upserted asynchronously via payment webhooks (payment.created, pending, failed, and succeeded); the 201 response is the payment id, not a ledger transaction id.

Optional header: Idempotency-Key. When set, a retry reuses the same charge. metadata is limited (25 keys; keys ≤64 chars, values ≤500 chars) and must not contain card numbers, CVV, or PAN-like data.

Embedded checkout flow​

  1. GET /checkout/plans/{planId} — product/plan metadata (productName, amount, currency), customization, and hosted checkoutUrl. Hosted checkout renders price from this payload; the embed script does not — your page should fetch it and display the offer.
  2. POST /checkout/plans/{planId}/session — create an embed session (sessionId, optional purchaseUrl) for the hosted iframe checkout.
  3. POST /checkout/plans/{planId}/confirm — confirm a payment token (confirmationToken, ctok_…) and return { paymentId, clientSecret, status } for handleNextAction on the client. Use this when you mount payment fields yourself instead of the iframe embed.
  4. GET /checkout/plans/{planId}/receipts/{receiptId} — receipt totals after payment (receiptId is the paymentId from step 3). Includes taxBehavior (exclusive or inclusive) with subtotal, taxAmount, and total.

For catalogue checkouts, POST /v1/checkout/plans/{planId}/tax (no API key) previews tax for a buyer address and returns the same taxBehavior field plus calculated.

The same confirm route is also available without an API key at POST /v1/checkout/plans/{planId}/confirm (buyer checkout surface). The embed script calls that keyless route by default; use the Merchant Public API variant when your backend already holds an API key and you want merchant scoping on the path.

See Embedded checkout for putting payment fields on your page, and Quickstart for create-merchant → product+plan → checkout URL. To charge a price your code works out, see Custom amount checkout. Several items in one payment is Order checkout.

Dashboard vs Public API​

Use Public API routes for integrations. Dashboard routes (/v1/merchants/{merchantId}/...) are for the dashboard UI only.

Merchant-scoped Public API routes (/v1/public-api/merchants/{merchantId}/...) require a Merchant API key whose merchant is {merchantId} or its parent. Partner onboarding (POST /v1/public-api/merchants) uses the same Merchant API key.

Webhook destinations, HMAC secrets, test pings, and KYC (GET /v1/merchants/{merchantId}/kyc, POST /v1/merchants/{merchantId}/kyc/session) are dashboard-only. There is no Public API route to start KYC yet; follow its progress with activation.identityVerification on GET /v1/public-api/merchants/{merchantId}. See Partner Onboarding and Webhooks.

Next steps​