Lumeo Docs
API ReferencePayments

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

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.