Skip to main content

Cashout Endpoints

Endpoint reference for the cashout / settlement API. For the guided walkthrough — enabling the API, the security model, and per-step task pages — see the Cashout / Settlements section.

All cashout endpoints live under /api/v1/merchant/settlements, require the X-Api-Key header plus the signing headers below, and must originate from an allowlisted IP. The API is off by defaultenable it first.

Headers

Every cashout request carries the API key plus the two signing headers (full walkthrough: Signing Requests); the money-moving POST additionally requires Idempotency-Key.

HeaderTypeRequiredDescriptionExample
X-Api-KeystringYesGateway API key. Keep it server-side only.<your-api-key>
X-TimestampstringYesCurrent Unix time in seconds. Rejected (signature_stale) if more than 5 minutes from the server clock.1783369120
X-Signaturestring (hex)YesHMAC_SHA256(secret, X-Timestamp + "." + rawBody), hex-encoded. For GET requests the body is the empty string.a7b9…
Idempotency-KeystringConditionalRequired on POST /merchant/settlements — reuse the same key to safely retry. The same key with a different body returns 409 idempotency_conflict. Not used on GET.<uuid>
Content-TypestringConditionalapplication/json on POST /merchant/settlements.application/json

GET /merchant/settlements/balances

Your cashout-able balance per asset (matured funds only), plus a "maturing" bucket for funds still inside the settlement waiting window.

TS=$(date +%s); BODY=""
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl https://api-staging.finomesh.com/api/v1/merchant/settlements/balances \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG"

Request body: None. For GET, sign the empty body (see the signing walkthrough).

Response — 200 OK

Balances are grouped by asset under data.items[]. Each item splits the funds into a matured bucket (withdrawable now — totalAvailable*) and a maturing bucket (still inside your settlement waiting window — totalPending*):

{
"success": true,
"data": {
"items": [
{
"assetId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"symbol": "USDT",
"name": "Tether",
"type": "TRC20",
"networkId": "9b1de2c8-7a41-4c8e-b3f2-6d5e4a3b2c1d",
"networkName": "TRON",
"networkSymbol": "TRX",
"totalAvailable": "250.00",
"totalAvailableUsd": "250.00",
"payments": 12,
"totalPendingAvailable": "40.00",
"totalPendingUsd": "40.00",
"pendingPayments": 2,
"nextMaturityAt": "2026-07-07T13:45:00Z"
}
]
}
}
FieldTypeNullableDescriptionExample
data.items[]arrayNoOne entry per asset with a non-zero balance. Empty when nothing is settle-able.[ … ]
data.items[].assetIdstring (UUID)NoThe asset — pass it to POST /merchant/settlements.a1b2c3d4-…
data.items[].symbolstringNoAsset ticker.USDT
data.items[].namestringNoHuman-readable asset name.Tether
data.items[].typestring (enum)NoToken standard: NATIVE, ERC20, TRC20, or BEP20.TRC20
data.items[].networkIdstring (UUID)NoThe chain the asset settles on.9b1de2c8-…
data.items[].networkNamestringNoChain name.TRON
data.items[].networkSymbolstringNoNative-coin ticker of the chain.TRX
data.items[].totalAvailablestring (decimal)NoMatured, withdrawable now — the ceiling for a settlement of this asset. Decimal string — never a number.250.00
data.items[].totalAvailableUsdstring (decimal)NototalAvailable valued in USD. Decimal string.250.00
data.items[].paymentsintegerNoNumber of matured payments backing totalAvailable.12
data.items[].totalPendingAvailablestring (decimal)NoMaturing — settle-able once its waiting window elapses; not yet withdrawable. Decimal string.40.00
data.items[].totalPendingUsdstring (decimal)NototalPendingAvailable valued in USD. Decimal string.40.00
data.items[].pendingPaymentsintegerNoNumber of payments in the maturing bucket.2
data.items[].nextMaturityAtstring (RFC 3339)YesWhen the soonest maturing funds become withdrawable. null/omitted when nothing is maturing.2026-07-07T13:45:00Z

Each item also carries an aggregate lifetime-ledger breakdown (totalReceived, totalTxFee, totalCommission, and totalPendingReceived / totalPendingTxFee / totalPendingCommission for the maturing bucket) used by the dashboard; a merchant integration only needs totalAvailable to size a settlement.


POST /merchant/settlements/estimate-fee

Preview the network fee for a cashout before requesting it — a dry run. It returns the exact feeLocked and netAmount a real create of the same body would produce, but creates nothing: no settlement, no address-book entry, no balance is claimed. Use it to show the payer the fee, or to check a withdrawal clears its own network fee before committing.

The request body is the same as create minus label (nothing is stored). This is a read-only POST (it carries a body to sign), so it does not require an Idempotency-Key.

BODY='{"assetId":"<asset-uuid>","amount":"20","address":"T..."}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl -X POST https://api-staging.finomesh.com/api/v1/merchant/settlements/estimate-fee \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-d "$BODY"

Request body

FieldTypeRequiredDescriptionExample
assetIdstring (UUID)YesThe asset to withdraw. Its network is used to price the fee and validate the address."<asset-uuid>"
amountstring (decimal)YesAmount you intend to withdraw, in asset units. Decimal string — never a number."20"
addressstringYesDestination address. Validated for the asset's chain (EVM EIP-55 checksum, TRON/UTXO base58check) — a malformed address returns 422 validation_error before any fee is quoted, same as create. The fee is destination-aware (e.g. on TRON a first-time recipient costs more energy), so quote against the real address you'll pay."T..."

Response — 200 OK

{
"success": true,
"data": {
"address": "T...",
"assetName": "USDT",
"network": "TRON",
"amount": "20",
"feeLocked": "2.59",
"netAmount": "17.41",
"sufficient": true
}
}
FieldTypeDescription
addressstringDestination echoed back.
assetNamestringHuman-readable asset symbol, e.g. USDT.
networkstringChain the payout would settle on, e.g. TRON.
amountstring (decimal)The amount you asked to quote.
feeLockedstring (decimal)Network fee that would be reserved, in the asset's units — the same figure a real cashout locks.
netAmountstring (decimal)amount − feeLocked — what would land at the destination.
sufficientbooleanfalse when feeLocked ≥ amount (a real cashout would be rejected as below its own network fee). The numbers are still returned so you can see why.
note

The quote is a point-in-time estimate. Network fees move with chain conditions, so the fee locked when you actually create the settlement may differ slightly. A 503 fee_estimation_unavailable means the chain RPC was momentarily unreachable — retry shortly.


POST /merchant/settlements

Request a settlement of an exact amount to a destination address.

BODY='{"assetId":"<asset-uuid>","amount":"250.00","address":"T...","label":"payouts"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl -X POST https://api-staging.finomesh.com/api/v1/merchant/settlements \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-d "$BODY"

Request body

FieldTypeRequiredDefaultDescriptionExample
assetIdstring (UUID)YesThe asset to withdraw. Its network determines how the address is validated."<asset-uuid>"
amountstring (decimal)YesExact amount to withdraw, in asset units. Must be > 0 and your matured available for that asset. Decimal string — never a number."250.00"
addressstringYesDestination address. Validated for the asset's chain (EVM EIP-55 checksum, TRON/UTXO base58check). Stored in your address book on first use, tagged API; a repeat address is reused, not rejected. For EVM chains the match is case-insensitive — the same address sent checksummed or all-lowercase reuses one entry (stored lowercase). Base58 chains (TRON/BTC) are matched exactly."T..."
labelstringNorandom api-…Names the auto-created address-book entry."payouts"
metadataobjectNoFree-form one-level JSON object for your own tracking, echoed back on read and shown to the approver (key by key) when a request parks at your approval cap. Keys are not validated; total size capped at 4 KB. A requester key is shown first.{"requester":"ops-team-alice","orderId":"1234"}

Required headers

Idempotency-Key is required — reuse the same key to safely retry a request without creating a second settlement. The same key with a different body returns 409 idempotency_conflict.

Response 201

Returns the settlement in the slim shape below. It starts REQUESTED (so txHash / blockchainUrl are empty until it settles on-chain) — or PENDING_APPROVAL when the amount exceeds your approval cap, in which case it waits for a team member to approve it before dispatch:

{
"success": true,
"data": {
"id": "d4447446-45a0-4603-b04a-db88c40fc7d8",
"address": "T...",
"assetName": "USDT",
"network": "TRON",
"source": "API",
"status": "REQUESTED",
"amount": "250.00",
"feeLocked": "4.82",
"netAmount": "245.18",
"receivedAmount": "",
"txHash": "",
"blockchainUrl": ""
}
}

The settlement is dispatched on-chain after your gateway's configured delay. Poll the status endpoint or rely on the settlement webhook for the terminal result.

Approval cap

You can set a cashout approval cap — a USD ceiling — per gateway in the dashboard (next to the signing secret). When a cap is set, any API cashout whose USD value is strictly greater than the cap is created as PENDING_APPROVAL instead of REQUESTED: the funds are reserved, but the payout is not dispatched until it is approved. Approving moves it to REQUESTED and starts the normal dispatch delay.

You can approve either from the dashboard settlement list or entirely over the API — GET /merchant/settlements?status=PENDING_APPROVAL to find what is waiting, then POST /merchant/settlements/:id/approve to release it (2FA required). Approval is the only merchant action: a parked cashout you don't approve simply stays reserved until you do.

  • The cap is a manual sign-off step for large programmatic withdrawals — it does not change auth, signing, or idempotency.
  • The Idempotency-Key still returns the original settlement id on replay while the request is parked. Poll GET /merchant/settlements/:id or the list endpoint rather than re-POSTing to check whether it has been approved.
  • With no cap configured, every API cashout dispatches without approval and responses never include PENDING_APPROVAL — behavior is unchanged.

GET /merchant/settlements/:id

Status of one of your settlements.

TS=$(date +%s); BODY=""
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl https://api-staging.finomesh.com/api/v1/merchant/settlements/<settlement-id> \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG"
{
"success": true,
"data": {
"id": "d4447446-45a0-4603-b04a-db88c40fc7d8",
"address": "TRUpnEA3xuVCuLDZyFVXhCjdvTZf1gB13r",
"assetName": "USDT",
"network": "TRON",
"source": "API",
"status": "SETTLED",
"amount": "19",
"feeLocked": "4.82",
"netAmount": "14.18",
"receivedAmount": "19",
"txHash": "f0ee051f4fae2585bceed7320818e222c913c2931fecd3265cbc6d1255767850",
"blockchainUrl": "https://nile.tronscan.org/#/transaction/f0ee051f4fae2585bceed7320818e222c913c2931fecd3265cbc6d1255767850"
}
}

Statuses: REQUESTEDPROCESSINGSETTLED (terminal) or REJECTED (terminal). Cashouts above your approval cap start at PENDING_APPROVAL and move to REQUESTED once approved. REJECTED is a cancellation — an operator/support action, not something you trigger over the API. A settlement id that isn't yours returns 404.

If you set an approval cap

Turning on a cashout approval cap means POST /merchant/settlements can return a fifth status, PENDING_APPROVAL, for requests above the cap. If your integration switches on status, handle it (treat it as "pending, not yet dispatched") before enabling the cap.

Response shape

Both the create (201) and this endpoint return the same slim object under data:

FieldTypeNullableDescription
idstring (UUID)NoSettlement id — use it to poll this endpoint.
addressstringNoDestination address the payout is sent to.
assetNamestringNoHuman-readable asset symbol, e.g. USDT.
networkstringNoChain the payout settles on, e.g. TRON.
sourcestring (enum)NoAPI for cashout-API settlements.
statusstring (enum)NoPENDING_APPROVAL / REQUESTED / PROCESSING / SETTLED / REJECTED. PENDING_APPROVAL appears only when a cashout cap is set.
metadataobjectYesEcho of the metadata object you sent on create; omitted when none was supplied.
amountstring (decimal)NoGross amount requested, in the asset's units.
feeLockedstring (decimal)NoNetwork fee reserved for the payout, in the asset's units.
netAmountstring (decimal)Noamount − feeLocked — what lands at the destination.
receivedAmountstring (decimal)NoAmount actually sent on settlement (empty string until SETTLED).
txHashstringNoOn-chain transaction hash (empty string until SETTLED).
blockchainUrlstringNoReady-to-open block-explorer link for txHash (empty string until SETTLED).

GET /merchant/settlements

List your cashouts with an optional status filter. Use it to find the cashouts waiting for approval (?status=PENDING_APPROVAL) — and, because the response carries pagination.total, to count them without pulling every row (?status=PENDING_APPROVAL&limit=1).

TS=$(date +%s); BODY=""
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl "https://api-staging.finomesh.com/api/v1/merchant/settlements?status=PENDING_APPROVAL&limit=20" \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG"

Query parameters

ParamTypeDescription
statusstringOptional. One of PENDING_APPROVAL, REQUESTED, PROCESSING, SETTLED, REJECTED. An unknown value returns 422.
limitintOptional page size.
pageintOptional 1-based page number.
afterstringOptional cursor; overrides page when set.

Response — 200 OK

data.items is an array of the slim settlement shape; data.pagination.total is the total number of matches.

{
"success": true,
"data": {
"items": [
{
"id": "d4447446-45a0-4603-b04a-db88c40fc7d8",
"address": "T...",
"assetName": "USDT",
"network": "TRON",
"source": "API",
"status": "PENDING_APPROVAL",
"metadata": { "requester": "ops-team-alice", "orderId": "1234" },
"amount": "250.00",
"feeLocked": "4.82",
"netAmount": "245.18",
"receivedAmount": "",
"txHash": "",
"blockchainUrl": ""
}
],
"pagination": { "total": 1 }
}
}

POST /merchant/settlements/:id/approve

Approve a cashout that parked at your approval cap. Approving moves it to REQUESTED and starts the normal dispatch delay.

Two-factor authentication is required. Enable 2FA on your account, then send a fresh 6-digit code in the body. A merchant with no 2FA enrolled is refused with 403 totp_not_enrolled — enable 2FA, or approve from the dashboard. This keeps a large payout behind a human second factor even though the request is signed with your cashout secret.

TS=$(date +%s); BODY='{"totpCode":"123456"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
curl -X POST https://api-staging.finomesh.com/api/v1/merchant/settlements/<settlement-id>/approve \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-d "$BODY"

Request body

FieldTypeRequiredDescription
totpCodestringWhen 2FA enrolledFresh 6-digit code from your authenticator app.

Idempotency-Key is supported (and recommended) — reuse the same key to safely retry without a double-approve.

Response — 200 OK

Returns the settlement in the slim shape with status now REQUESTED. Error cases:

CodeWhen
403 totp_not_enrolled2FA is not enabled on your account — it is required to approve over the API.
401 totp_required2FA is enabled but the code is missing or wrong.
409 conflictThe cashout is no longer awaiting approval (already approved, or cancelled by an operator).
404 not_foundNo such settlement under your account.
You approve — you cannot reject

A merchant can only approve a parked cashout or leave it pending; there is no merchant reject over the API. Cancelling a parked cashout is an operator/support action. A cashout you never approve stays reserved — its funds are unavailable to settle — until it is approved or an operator cancels it.


Errors

All cashout error codes, with how to resolve each, are on the Cashout Errors page.