Lumeo Docs

Create Payout

Initiate and confirm a payout to a recipient.

Two-step flow

Creating a payout is a two-call sequence: POST /payments/initiate records the payout intent, then POST /payments/confirm triggers compliance checks and submits it for settlement. This matches how the backend's PaymentsService is implemented. There is no single combined "create" call today.

Step 1: POST /api/v1/payments/initiate

Records a payout intent for the current user. This does not move funds yet. it validates the request and stores a payment record in INITIATED status.

Authentication

Required. Bearer token in the Authorization header.

Authorization: Bearer YOUR_ACCESS_TOKEN
Idempotency-Key: a-client-generated-uuid

Request body

FieldTypeRequiredDescription
amountnumberYesPositive amount in the given currency's major unit.
currencystringYes3-letter currency code (e.g. "USD", "INR").
destinationAddressstringYes56-character recipient wallet address.
memostringNoFree-text memo, max 256 characters.
purposeCodestringNoRBI FEMA purpose code, format P#### (e.g. P0801).
invoiceRefstringNoYour invoice/reconciliation reference, max 128 characters.
counterpartyNamestringNoMax 256 characters.
counterpartyCountrystringNoISO 3166-1 alpha-2 country code.
counterpartyBankstringNoMax 256 characters.

TODO: confirm public address format

destinationAddress is validated server-side as exactly 56 characters. The docs intentionally describe it functionally as a "recipient wallet address" rather than naming the underlying settlement network. Confirm this framing matches the public SDK's terminology before publishing.

Example request

curl -X POST https://api.lumeo.co.in/api/v1/payments/initiate \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Idempotency-Key: 7d1b6e2a-7c3e-4b9a-9b1a-3e9a2b5d9c44" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 500.00,
    "currency": "USD",
    "destinationAddress": "GADQVQHX...REDACTED...56CHARS",
    "purposeCode": "P0801",
    "invoiceRef": "invoice-2026-0142"
  }'
const response = await fetch("https://api.lumeo.co.in/api/v1/payments/initiate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 500.0,
    currency: "USD",
    destinationAddress: "GADQVQHX...REDACTED...56CHARS",
    purposeCode: "P0801",
    invoiceRef: "invoice-2026-0142",
  }),
});

const payment = await response.json();
import requests
import uuid

response = requests.post(
    "https://api.lumeo.co.in/api/v1/payments/initiate",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Idempotency-Key": str(uuid.uuid4()),
        "Content-Type": "application/json",
    },
    json={
        "amount": 500.00,
        "currency": "USD",
        "destinationAddress": "GADQVQHX...REDACTED...56CHARS",
        "purposeCode": "P0801",
        "invoiceRef": "invoice-2026-0142",
    },
)

payment = response.json()

Response: 201 Created

{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "txnId": "txn-7d1b6e2a-7c3e-4b9a-9b1a-3e9a2b5d9c44",
  "userId": "user_123",
  "amount": 500.00,
  "currency": "USD",
  "status": "INITIATED",
  "type": "SEND",
  "createdAt": "2026-06-30T09:14:22.000Z",
  "updatedAt": "2026-06-30T09:14:22.000Z"
}

Step 2: POST /api/v1/payments/confirm

Confirms a previously initiated payout. This is what actually triggers compliance checks and queues the payout for execution.

Request body

FieldTypeRequiredDescription
idstringYesThe id returned from initiate.
destinationAddressstringYesMust match the value sent to initiate.
amountstringYesAmount as a string.
currencystringYesCurrency code.
memostringNoFree-text memo.
vaultIdstringYesID of the funding wallet/vault on your account.

TODO: untyped request body

The confirm endpoint currently accepts an inline object rather than a validated DTO class. Confirm with the backend team whether this is intentional before this page is treated as fully authoritative.

Response: 201 Created

Same payment object shape as initiate, with status updated:

{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "status": "COMPLIANCE_CHECK",
  "amount": 500.00,
  "currency": "USD"
}

Payment status values

1
INITIATED
after initiate
2
COMPLIANCE_CHECK
after confirm
3
PROCESSING
checks passed
4
SUBMITTED
sent to settlement
5
CONFIRMED
funds settled
The path a payout takes when nothing goes wrong. Each hop is driven by Lumeo, not by a call from you — subscribe to the webhook rather than polling for the next state.
Branches off the happy path
MANUAL_REVIEW
from COMPLIANCE_CHECK
FAILED
see failedReason
UNKNOWN
from SUBMITTED
Where a payment can leave the happy path, and which state it leaves from.

MANUAL_REVIEW fires neither payout.settled nor payout.failed. A handler that only listens for those two will show the payment as stuck forever with no error surfaced. UNKNOWN means the settlement layer never confirmed either way, so the payment is under reconciliation rather than lost.

StatusMeaning
INITIATEDPayout intent recorded, not yet confirmed.
COMPLIANCE_CHECKConfirmed; undergoing automated compliance review.
PROCESSINGCompliance passed, payout queued for execution.
SUBMITTEDSubmitted to the settlement layer, awaiting confirmation.
CONFIRMED / COMPLETEDFunds have settled. Compliance artifacts (FIRA, tax fields) generate automatically for cross-border payouts.
MANUAL_REVIEWFlagged for manual compliance review.
FAILEDPayout could not be completed. See failedReason on the payment object.
UNKNOWNStatus could not be confirmed with the settlement layer; under reconciliation.

TODO: confirm exhaustive status list

This list was assembled by reading the current service implementation, not from a single canonical enum (the status column is a free-text string). Reconcile with the backend team if a stricter status enum is introduced.

Error responses

StatusNotes
400BadRequestException — e.g. funding wallet has no usable key.
401Missing or invalid bearer token.
404ERR_WALLET_NOT_FOUND or ERR_PAYMENT_NOT_FOUND.
429Rate limited — initiate allows 10 req/min, confirm allows 5 req/min per user.
500Internal error. Safe to retry with the same Idempotency-Key.

What happens next

Once a payment reaches CONFIRMED/COMPLETED, it enters the compliance pipeline: the transaction is reconciled against the ledger, a FIRA is generated automatically for cross-border payouts, and tax fields are recalculated. Subscribe to payment status webhooks rather than polling. See Handle webhook retries.

Idempotency

Both initiate and confirm accept an Idempotency-Key header. Retrying the same call with the same key returns the original result instead of creating a duplicate payment.