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-uuidRequest body
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Positive amount in the given currency's major unit. |
currency | string | Yes | 3-letter currency code (e.g. "USD", "INR"). |
destinationAddress | string | Yes | 56-character recipient wallet address. |
memo | string | No | Free-text memo, max 256 characters. |
purposeCode | string | No | RBI FEMA purpose code, format P#### (e.g. P0801). |
invoiceRef | string | No | Your invoice/reconciliation reference, max 128 characters. |
counterpartyName | string | No | Max 256 characters. |
counterpartyCountry | string | No | ISO 3166-1 alpha-2 country code. |
counterpartyBank | string | No | Max 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
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The id returned from initiate. |
destinationAddress | string | Yes | Must match the value sent to initiate. |
amount | string | Yes | Amount as a string. |
currency | string | Yes | Currency code. |
memo | string | No | Free-text memo. |
vaultId | string | Yes | ID 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
| Status | Meaning |
|---|---|
INITIATED | Payout intent recorded, not yet confirmed. |
COMPLIANCE_CHECK | Confirmed; undergoing automated compliance review. |
PROCESSING | Compliance passed, payout queued for execution. |
SUBMITTED | Submitted to the settlement layer, awaiting confirmation. |
CONFIRMED / COMPLETED | Funds have settled. Compliance artifacts (FIRA, tax fields) generate automatically for cross-border payouts. |
MANUAL_REVIEW | Flagged for manual compliance review. |
FAILED | Payout could not be completed. See failedReason on the payment object. |
UNKNOWN | Status 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
| Status | Notes |
|---|---|
400 | BadRequestException — e.g. funding wallet has no usable key. |
401 | Missing or invalid bearer token. |
404 | ERR_WALLET_NOT_FOUND or ERR_PAYMENT_NOT_FOUND. |
429 | Rate limited — initiate allows 10 req/min, confirm allows 5 req/min per user. |
500 | Internal 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.