Queue a mass payout batch
curl --request POST \
--url https://api.liddie.io/api/v1/mass-payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"rows": [
{}
],
"label": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://api.liddie.io/api/v1/mass-payouts"
payload = {
"rows": [{}],
"label": "<string>",
"idempotencyKey": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({rows: [{}], label: '<string>', idempotencyKey: '<string>'})
};
fetch('https://api.liddie.io/api/v1/mass-payouts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.liddie.io/api/v1/mass-payouts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'rows' => [
[
]
],
'label' => '<string>',
'idempotencyKey' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.liddie.io/api/v1/mass-payouts"
payload := strings.NewReader("{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.liddie.io/api/v1/mass-payouts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.liddie.io/api/v1/mass-payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyMass payouts
Queue a mass payout batch
Queue a Liddie mass payout to up to 500 whitelisted crypto recipients in one API request, secured with a fresh MFA factor from the dashboard user.
POST
/
api
/
v1
/
mass-payouts
Queue a mass payout batch
curl --request POST \
--url https://api.liddie.io/api/v1/mass-payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"rows": [
{}
],
"label": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://api.liddie.io/api/v1/mass-payouts"
payload = {
"rows": [{}],
"label": "<string>",
"idempotencyKey": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({rows: [{}], label: '<string>', idempotencyKey: '<string>'})
};
fetch('https://api.liddie.io/api/v1/mass-payouts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.liddie.io/api/v1/mass-payouts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'rows' => [
[
]
],
'label' => '<string>',
'idempotencyKey' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.liddie.io/api/v1/mass-payouts"
payload := strings.NewReader("{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.liddie.io/api/v1/mass-payouts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.liddie.io/api/v1/mass-payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"rows\": [\n {}\n ],\n \"label\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyQueue a batch payout — one request sends money out to up to 500 recipients at once.
Every recipient address must be on your withdrawal whitelist — Liddie checks this server-side. Egress is fee-free: you pay the network cost only, no platform markup.
In addition to the fields above, the body must include one fresh MFA factor:
This moves money out of your account. Run Validate a batch (dry-run) first to get a
canSubmit verdict, and always send an idempotencyKey so a network retry can’t queue the same batch twice.# The body must also include one fresh MFA factor
# (VERIFICATION_PROPS - see the Authentication guide).
curl -X POST https://api.liddie.io/api/v1/mass-payouts \
-H "Authorization: Bearer <dashboard-jwt>" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{ "currency": "LTC", "address": "L...", "amount": "0.05" }
],
"label": "July contractor payouts",
"idempotencyKey": "payouts-2026-07"
}'
// Dashboard-session auth: send the dashboard JWT as a Bearer token.
// The body must also include one fresh MFA factor
// (VERIFICATION_PROPS - see the Authentication guide).
const res = await fetch('https://api.liddie.io/api/v1/mass-payouts', {
method: 'POST',
headers: {
Authorization: 'Bearer <dashboard-jwt>',
'Content-Type': 'application/json',
},
body: JSON.stringify({
rows: [
{ currency: 'LTC', address: 'L...', amount: '0.05' },
],
label: 'July contractor payouts',
idempotencyKey: 'payouts-2026-07',
}),
})
const body = await res.json()
Authorization
Dashboard-only (JWT) with themass-payouts:manage team permission. API keys are not accepted — an API key gets 401 {"error":"Invalid or expired token"}.
Because this call moves money, the body must carry one fresh MFA factor (passkey / TOTP / emailed code — the VERIFICATION_PROPS fields; see the Authentication guide). Email codes come from Send mass-payout email code. Rate limit: 6/min.
Body parameters
array
required
The payout rows — up to 500 recipients per batch. Each row carries the recipient’s
currency, address, and amount as strings (an optional destinationTag string is supported for tag-based currencies like XRP), the same row shape the validator echoes back.string
Optional label for the batch. Max 100 characters.
string
Optional idempotency key (8–100 characters) so a retried submit does not queue the batch twice.
MFA fields (dashboard sessions)
MFA fields (dashboard sessions)
string
The single-use emailed code.
string
A 6-digit code from your authenticator app — an alternative to
emailCode. Backup codes are not accepted here: the field is validated against ^[0-9]{6}$ and backup codes are 16 hexadecimal characters. They work only at login (POST /2fa/validate). If you have lost your authenticator, request an emailed code instead.object
WebAuthn assertion, used together with
challengeKey.string
Accompanies
passkeyResponse.Errors
| Status | Body | Why |
|---|---|---|
| 400 | {"ok":false,"error":{"code":"VERIFICATION_FAILED","message":"Verification required: provide email code, OTP, or passkey"}} | The body lacks a valid fresh MFA factor. |
| 401 | {"ok":false,"error":{"code":"VERIFICATION_FAILED","message":"Invalid email code"}} | Wrong, already-used or invalid email code, OTP or passkey assertion. Same code, different status — do not treat this 401 as an expired session. |
| 403 | {"ok":false,"error":{"code":"VERIFICATION_FAILED","message":"TOTP is not enabled on this account"}} | A totpCode was sent by a user with no authenticator enrolled. |
| 429 | {"ok":false,"error":{"code":"VERIFICATION_FAILED","message":"Too many incorrect codes. Request a new one."}} | Five wrong email codes — the challenge is voided; request a fresh code. |
| 429 | {"ok":false,"error":{"code":"VERIFICATION_FAILED","message":"Too many failed attempts. Try again later."}} | TOTP lockout after repeated wrong codes. |
| 403 | {"ok":false,"error":{"code":"IP_NOT_WHITELISTED","message":"This request came from 198.51.100.9, which is not on your withdrawal IP whitelist. Add that IP to your whitelisted IPs, then try again."}} | Your IP whitelist is non-empty and this address is not on it. Checked before MFA, so your emailed code is NOT consumed — whitelist the address and retry with the same one. An empty IP list never produces this. |
| 401 | {"error":"Invalid or expired token"} | Called with an API key or a missing/expired dashboard JWT. |
See also
- Validate a batch (dry-run): check the rows and get a
canSubmitverdict before this call. - Get a batch: follow the queued batch and its per-recipient legs.
- Cancel a batch: stop a queued batch; pending legs are refunded.