Merchant API

Create a payout intent

Create an idempotent Payout and read its single closed public status.

Create one Payout to a bank account. The response exposes one stable public status. Merchant API creation starts execution without a separate Merchant approval command.

Endpoint

POST /payout_intents
curl -s -X POST "$API_BASE/payout_intents" \
  -u "$API_PUBLIC_KEY:$API_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "50000",
    "currency": "MYR",
    "country": "MY",
    "merchant_reference": "payout_98765",
    "recipient": {
      "type": "bank_account",
      "account_number": "1234567890",
      "bank_code": "MBBEMYKL",
      "holder_name": "Alice Tan"
    }
  }'

The API validates the request and resolves Operational Configuration before dispatch. After durable insert and acknowledged Workflow dispatch, it returns 201 with the latest Payout projection. An uncertain dispatched create remains processing; it never invites a second Payout.

{
  "object": "payout_intent",
  "id": "word_01HZX...",
  "amount": "50000",
  "currency": "MYR",
  "country": "MY",
  "environment": "test",
  "merchant_reference": "payout_98765",
  "status": "processing",
  "reserved_amount": "0",
  "completed_amount": "0",
  "returned_amount": "0",
  "next_action": null,
  "failure_code": null,
  "failure_message": null,
  "recipient": {
    "type": "bank_account",
    "account_last4": "7890",
    "bank_code": "MBBEMYKL",
    "holder_name": "Alice Tan"
  },
  "created_at": "2026-05-09T12:00:00.000Z",
  "updated_at": "2026-05-09T12:00:00.000Z"
}

status is one of processing, requires_action, requires_approval, succeeded, failed, cancelled, or expired. approval_status is present only as pending while operational approval is pending. Resolve an operational approval hold through an authorised Operator. Internal execution and Provider states are not public fields.

Replay and status

merchant_reference is a unique business reference. Reusing it returns 409 duplicate_merchant_reference. Idempotency-Key is required. Reusing the same key with the same complete request replays the stored response; changing any field returns 409 idempotency_conflict.

GET /payout_intents/:id is side-effect-free and never initiates Provider work. Consume webhooks or poll this projection for later truth.

Recipient bank codes

For Thailand, recipient.bank_code uses a canonical code: BAAC, BAY, BBL, CIMB, CLICX, EXIM, GHB, GSB, ISLAMIC, KBANK, KKP, KTB, LHBANK, SCB, SCBT, SME, TCR, TISCO, TTB, or UOB. Use promptpay for a PromptPay destination and put the recipient identifier in account_number. A recognised bank code does not guarantee an eligible route. Do not send Provider-local codes such as kkb. account_number and holder_name are required; holder_name_th supplies the Thai name when required by the configured route. For other countries, use the bank code agreed for the configured country and route.

Status events

Payout webhooks expose data.status with the same public values as GET. The payout_intent.returned event reports returned funds; its resource status is failed. It does not introduce a separate returned public status. Merchant approval commands are not available through this API; an operational approval hold must be resolved by an authorised Operator.

If Workflow dispatch cannot be acknowledged, creation returns 503. Retry with the same Idempotency-Key and the same body. Init reuses the stored Payout and dispatch identity; it does not create another Payout. A 503 is not proof that the Provider did not receive a request.

On this page