Version 1.0.0
Droomwork ROUTE MULTI-RAIL
Disbursement across Nigerian payment rails, with failover and proof.
https://sandbox.droomwork.ioDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…ROUTE pays your people. Hand it instructions computed elsewhere and it chooses a rail, keeps trying, and tells you what happened.
What you should know before you start
You never send an amount here. Instructions arrive signed from payroll or remittance. You can't post an arbitrary payment, and there's no endpoint that would let you.
unknown is a real state and you have to handle it. A payout that times out doesn't become failed. It becomes unknown, and it resolves by asking the provider what happened, never by assuming. Treat unknown as failure and someone gets paid twice.
Idempotency keys are derived, not invented. The key for a payment comes from the instruction itself, so the same payment always produces the same key and your retry after a timeout is safe.
The destination comes from the payee, not from you. An instruction names a person. Where the money goes is resolved from their identity and engagement records, so a compromised integration can't redirect a salary.
Validate accounts days before payday, not during the run. A name mismatch found at 6am on payday is a problem. Found the week before, it's an errand.
Getting started
Get a sandbox key from the developer portal. The rail simulator covers timeout, double fire, unknown state and degradation under month end load, so you can exercise the paths that matter before you meet them in production.
Every operation below has two samples: Direct HTTP, with nothing but your language's own client, and Client library, with ours. Same request, same answer; the switch above each sample picks one and remembers it. Which should I use?
/v1/payouts/instructions#See every instruction you have submitted
payouts.instructions.list
This is every payout instruction your organisation has submitted, each with the state it reached. So a payday screen in your product can carry a row for every one and show which are still moving, without opening each in turn.
Send nothing and they come back, each carrying its own id, which starts route_multi_rail_payout_, along with its status and its created_at.
A row's status is the instruction's own. It says how far that payday got, and never whether one particular person was paid.
Read one instruction in full at GET /v1/payouts/instructions/{instruction_id}, passing a row's id as instruction_id.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
status
string
optionalReturn only instructions in one state: received, quarantined (held after a failed check), funding_pending, planned, executing, settled, partially_settled or failed. Leave it out to get instructions in every state.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedrun_id
string
optionalReturn only the instructions from one payroll run: the run's id from POST /v1/payroll/runs or GET /v1/payroll/runs, starting with run_enterprise_. Leave it out to get instructions from every run and remittance.
Returns
Your instructions, each with the state it reached and the identifier later calls take.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of PayoutInstruction
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
15 fields of PayoutInstruction
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
const result = await api.payoutsInstructionsList({ limit: 25, status: 'received', runId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
result = api.payouts_instructions_list(limit=25, status='received', run_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
$result = $api->payoutsInstructionsList(limit: 25, status: 'received', run_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
var result = api.payoutsInstructionsList(25, null, RouteInstructionStatus.fromValue("received"), "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
var result = api.PayoutsInstructionsList(limit: 25, status: RouteInstructionStatus.Received, runId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
result, _, err := client.ROUTEAPI.PayoutsInstructionsList(ctx).Limit(25).Status(droomwork.RouteInstructionStatus("received")).RunId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), status (optional), run_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/instructions?limit=25&status=received&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "received",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payouts/instructions#Submit a signed payout instruction
payouts.instructions.create
A payout instruction is one payday as a single record: who is being paid, and what each of them is due. So your product can run a whole payday from one identifier, from the route it takes to the state it ends in.
Send source, the signature that came with it, and one line per payee, each naming the payee as subject_id and the signed amount that line arrived with, in whole minor units with its currency. You get the instruction back under its own id, starting route_multi_rail_payout_.
Nothing in ROUTE has to exist before this call. Submitting pays nobody: the instruction sits in received until you plan it and then execute it, both calls of your own.
Pass the id as instruction_id to POST /v1/payouts/instructions/{instruction_id}/plan, the only transition received accepts. Execute is the only transition planned accepts.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
source
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string
optionalThe payroll run these payouts came from: the run's id from POST /v1/payroll/runs or GET /v1/payroll/runs, starting with run_enterprise_. Send it when source is run; every settlement then maps back to that run and its payslip lines.
remittance_id
string
optionalThe remittance these payouts came from: its id from POST /v1/remittance/remittances or GET /v1/remittance/remittances, starting with remit_authority_rail_remittance_. Send it when source is remit, so every settlement maps back to it.
signature
string
requiredThe signature from payroll or remittance. We verify it before accepting anything.
lines
array of object
requiredOne entry per payee, at least one: who to pay, the signed amount, the payslip line it came from, and an optional split across destinations. Every line is checked before the instruction is accepted.
4 fields
subject_id
string
requiredWho to pay: their subject identifier, starting with sub_, the subject_id on their payslip at GET /v1/payroll/payslips and roster entry at POST /v1/payroll/roster_entries. Where the money goes comes from their records, not from you.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_line_id
string
optionalThe payslip line this amount came from: the id of an entry in lines on the payee's payslip at GET /v1/payroll/payslips/{payslip_id}. Send it so the settlement can be traced back to the payslip and line it paid.
split
array of object
optionalEach part settles independently as its own sub-leg.
2 fields
destination_ref
string
requiredWhich of the payee's destinations this part goes to: a reference to one held on their identity and engagement records, the ones a payout's destination resolves from. Never an account number; where money goes comes from the payee, not from you.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
Returns
The instruction as stored, in received, with the id every later call in this module takes.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"source":"run","signature":"example","lines":[{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"}}]}],"run_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","remittance_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsCreate({
idempotencyKey: crypto.randomUUID(),
routePayoutInstructionCreateRequest: {"source":"run","signature":"example","lines":[{"subjectId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"},"payslipLineId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destinationRef":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"}}]}],"runId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","remittanceId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"source": "run",
"signature": "example",
"lines": [
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"split": [
{
"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
}
}
]
}
],
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_create(body={"source": "run", "signature": "example", "lines": [{"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 7500000, "currency": "NGN"}, "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "split": [{"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 7500000, "currency": "NGN"}}]}], "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"source": "run", "signature": "example", "lines": [{"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 7500000, "currency": "NGN"}, "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "split": [{"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 7500000, "currency": "NGN"}}]}], "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsCreate($idempotencyKey, json_decode('{"source":"run","signature":"example","lines":[{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"}}]}],"run_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","remittance_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"source":"run","signature":"example","lines":[{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":7500000,"currency":"NGN"}}]}],"run_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","remittance_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"source": "run",
"signature": "example",
"lines": [
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"split": [
{
"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
}
}
]
}
],
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"source": "run",
"signature": "example",
"lines": [
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"split": [
{
"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
}
}
]
}
],
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsCreate(ctx).IdempotencyKey(key).RoutePayoutInstructionCreateRequest(body).Execute()body := strings.NewReader(`{
"source": "run",
"signature": "example",
"lines": [
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"split": [
{
"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
}
}
]
}
],
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "received",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}#Check where one instruction has got to
payouts.instructions.retrieve
This reads one instruction back as it stands now, which is how far its payday has got. So a product holding an identifier it stored at submission can show somebody the current answer, without keeping a copy of the states itself.
Send the instruction's id in the path. You get the record back, with status for the state it is in and created_at for when you submitted it.
An instruction's id and a payout's id carry the same route_multi_rail_payout_ prefix, so the value alone will not tell you which you are holding.
See them all at GET /v1/payouts/instructions, and the route this one was given, once it is planned, at GET /v1/payouts/instructions/{instruction_id}/route_plan.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Returns
One instruction as it stands, with the state it has reached and when it arrived.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsRetrieve({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_retrieve(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsRetrieve(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsRetrieve("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsRetrieve(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsRetrieve(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "received",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/route_plan#See which rails an instruction will try
payouts.instructions.retrieve_route_plan
A route plan is the order of rails one instruction will be tried on: the one to try first, and the ones after it. So when a payment lands on a fallback, your product can show the order that was fixed before anything was sent.
Send the instruction's id in the path. You get primary_rail_id, the rail to try first, and fallback_rail_ids, the rails to try next in the order they will be tried.
The order is fixed when the instruction is planned and never decided while the payment is running. So a failover you read about afterwards can be checked against the order it was meant to follow.
Plan the instruction first, at POST /v1/payouts/instructions/{instruction_id}/plan. Read the rails themselves at GET /v1/payouts/rails, where each carries its kind and its health.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Returns
The order of rails this instruction will be tried on, the primary first.
id
string
requiredThe plan's identifier, starting with route_multi_rail_route_plan_; it never changes. It is the route_plan_id on a planned instruction, and you pass it as route_plan_id at GET /v1/payouts/route_plans/{route_plan_id}.
object
always "route_plan"
requiredAlways route_plan. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this plan routes: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. Pass it as instruction_id at GET /v1/payouts/route_plans to find the plan behind an instruction.
primary_rail_id
string
requiredThe rail to try first: the id of a rail from GET /v1/payouts/rails, starting with route_multi_rail_payout_. Fixed when the plan is made, never decided at run time, so a failover away from it can be reconstructed from the attempts.
fallback_rail_ids
array of string
requiredThe rails to try next, in order, when the primary won't carry the payment, each by its id; there is always at least one. Failover to the next fires within 15 seconds at the 95th percentile.
reasoning
array of string
optionalWhy this order, kept so you can audit the plan.
max_attempts
integer · minimum 1
optionalHow many attempts the plan allows across the primary rail and its fallbacks, at least 1. Once they are used up nothing more is tried; each one is on the payout's attempts list.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsRetrieveRoutePlan({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_retrieve_route_plan(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsRetrieveRoutePlan(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsRetrieveRoutePlan("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsRetrieveRoutePlan(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsRetrieveRoutePlan(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/route_plan", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "route_plan",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"primary_rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"fallback_rail_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"reasoning": [
"example"
],
"max_attempts": 1,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/funding_check#Whether the account can cover an instruction
payouts.funding_checks.retrieve
A funding check is one answer to one question: does your account hold everything an instruction would pay out. This is the read of that answer, so a screen in your product can show it without attempting anything on a rail.
You send the instruction's identifier in the path, starting with route_multi_rail_payout_, and get back what the instruction would pay out and what your account held, each in whole minor units with its currency.
Read sufficient for the verdict. A funding check coming back at all is not a yes: the record is there either way.
When the account is short, fund it and ask again at POST /v1/payouts/instructions/{instruction_id}/funding_check.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Returns
The funding check, naming what the instruction needs, what your account held and whether that covers it.
object
always "funding_check"
requiredAlways funding_check. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this check covers: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. It is the instruction_id you passed in the path, returned so the record stands on its own.
sufficient
boolean
requiredtrue when your account covers required_amount in full, false when it doesn't. On false nothing executes: no part of the instruction is paid, and shortfall says what's missing.
required_amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
available_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
shortfall
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
checked_at
string · date-time
optionalWhen the check ran, as an RFC 3339 timestamp in UTC. A balance moves, so check again if time has passed before you plan.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsFundingChecksRetrieve({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_funding_checks_retrieve(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsFundingChecksRetrieve(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsFundingChecksRetrieve("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsFundingChecksRetrieve(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsFundingChecksRetrieve(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "funding_check",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sufficient": true,
"required_amount": {
"amount": 41250000,
"currency": "NGN"
},
"available_amount": {
"amount": 30000000,
"currency": "NGN"
},
"shortfall": {
"amount": 11250000,
"currency": "NGN"
},
"checked_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/funding_check#Confirm funding before any rail is attempted
payouts.funding_checks.create
This asks whether your account covers everything one instruction would pay out. Ask it before you plan the route, while the instruction is the only thing that exists: that is where a funding gate of your own can sit.
Send no request body. The instruction is named in the path by the id that POST /v1/payouts/instructions returned, starting with route_multi_rail_payout_.
A funding check answers for the moment it ran. A balance moves, so ask again if time has passed before you plan.
What comes next is POST /v1/payouts/instructions/{instruction_id}/plan, which chooses the rails; the read beside this one is GET /v1/payouts/instructions/{instruction_id}/funding_check.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The check, with the figure behind it and not only a yes or no.
object
always "funding_check"
requiredAlways funding_check. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this check covers: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. It is the instruction_id you passed in the path, returned so the record stands on its own.
sufficient
boolean
requiredtrue when your account covers required_amount in full, false when it doesn't. On false nothing executes: no part of the instruction is paid, and shortfall says what's missing.
required_amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
available_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
shortfall
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
checked_at
string · date-time
optionalWhen the check ran, as an RFC 3339 timestamp in UTC. A balance moves, so check again if time has passed before you plan.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsFundingChecksCreate({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_funding_checks_create(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsFundingChecksCreate(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsFundingChecksCreate("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsFundingChecksCreate(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsFundingChecksCreate(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/funding_check", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "funding_check",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sufficient": true,
"required_amount": {
"amount": 41250000,
"currency": "NGN"
},
"available_amount": {
"amount": 30000000,
"currency": "NGN"
},
"shortfall": {
"amount": 11250000,
"currency": "NGN"
},
"checked_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/route_plans#See which rails each instruction was given
payouts.route_plans.list
This hands you every route plan your organisation holds. A plan is the rail order chosen before anything is attempted, so one screen in your product can show what was decided for every payment, and each row is the record a later question is answered from.
You get each plan with the instruction it belongs to, the rail named first, the rails to try after it in order, and how many attempts the plan allows across all of them.
Carry a row's id as route_plan_id to GET /v1/payouts/route_plans/{route_plan_id} to read one on its own. To go the other way, from an instruction to the plan carrying it, read GET /v1/payouts/instructions/{instruction_id}/route_plan.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
instruction_id
string
optionalReturn only the plans made for one instruction: pass its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions or GET /v1/payouts/instructions. Leave it out to get every stored plan.
Returns
The route plans your organisation holds, each with the rail order it names.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of RoutePlan
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
8 fields of RoutePlan
id
string
requiredThe plan's identifier, starting with route_multi_rail_route_plan_; it never changes. It is the route_plan_id on a planned instruction, and you pass it as route_plan_id at GET /v1/payouts/route_plans/{route_plan_id}.
object
always "route_plan"
requiredAlways route_plan. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this plan routes: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. Pass it as instruction_id at GET /v1/payouts/route_plans to find the plan behind an instruction.
primary_rail_id
string
requiredThe rail to try first: the id of a rail from GET /v1/payouts/rails, starting with route_multi_rail_payout_. Fixed when the plan is made, never decided at run time, so a failover away from it can be reconstructed from the attempts.
fallback_rail_ids
array of string
requiredThe rails to try next, in order, when the primary won't carry the payment, each by its id; there is always at least one. Failover to the next fires within 15 seconds at the 95th percentile.
reasoning
array of string
optionalWhy this order, kept so you can audit the plan.
max_attempts
integer · minimum 1
optionalHow many attempts the plan allows across the primary rail and its fallbacks, at least 1. Once they are used up nothing more is tried; each one is on the payout's attempts list.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), instruction_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
const result = await api.payoutsRoutePlansList({ limit: 25, instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), starting_after (optional), instruction_id (optional)
result = api.payouts_route_plans_list(limit=25, instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), instruction_id (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), instruction_id (optional)
$result = $api->payoutsRoutePlansList(limit: 25, instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
var result = api.payoutsRoutePlansList(25, null, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
var result = api.PayoutsRoutePlansList(limit: 25, instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
result, _, err := client.ROUTEAPI.PayoutsRoutePlansList(ctx).Limit(25).InstructionId("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), instruction_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/route_plans?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "route_plan",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"primary_rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"fallback_rail_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"reasoning": [
"example"
],
"max_attempts": 1,
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payouts/route_plans#Record a routing decision taken outside Droomwork
payouts.route_plans.create
This stores a routing decision you took yourself as a plan of its own. So a payment routed outside Droomwork can still be named in a call and read back later.
You get the plan back with an id starting route_multi_rail_route_plan_ to keep, the rail named first, the rails to try after it in order, and how many attempts the plan allows across all of them.
Nothing has to exist first.
The request names the rails primary and fallbacks, and they come back as primary_rail_id and fallback_rail_ids. Read the response's names, not the ones you sent.
Keep the id: it is what GET /v1/payouts/route_plans/{route_plan_id} reads back.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
primary
string
optionalThe rail to try first.
fallbacks
array of string
optionalRails to try in order when the primary will not carry the payment.
Returns
The stored plan, with the identifier to quote when you read it back.
id
string
requiredThe plan's identifier, starting with route_multi_rail_route_plan_; it never changes. It is the route_plan_id on a planned instruction, and you pass it as route_plan_id at GET /v1/payouts/route_plans/{route_plan_id}.
object
always "route_plan"
requiredAlways route_plan. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this plan routes: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. Pass it as instruction_id at GET /v1/payouts/route_plans to find the plan behind an instruction.
primary_rail_id
string
requiredThe rail to try first: the id of a rail from GET /v1/payouts/rails, starting with route_multi_rail_payout_. Fixed when the plan is made, never decided at run time, so a failover away from it can be reconstructed from the attempts.
fallback_rail_ids
array of string
requiredThe rails to try next, in order, when the primary won't carry the payment, each by its id; there is always at least one. Failover to the next fires within 15 seconds at the 95th percentile.
reasoning
array of string
optionalWhy this order, kept so you can audit the plan.
max_attempts
integer · minimum 1
optionalHow many attempts the plan allows across the primary rail and its fallbacks, at least 1. Once they are used up nothing more is tried; each one is on the payout's attempts list.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/route_plans" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"primary":"example","fallbacks":["example"]}'import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsRoutePlansCreate({});const response = await fetch('https://sandbox.droomwork.io/v1/payouts/route_plans', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"primary": "example",
"fallbacks": [
"example"
]
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_route_plans_create()import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/route_plans',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"primary": "example", "fallbacks": ["example"]},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsRoutePlansCreate();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/route_plans');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"primary":"example","fallbacks":["example"]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsRoutePlansCreate();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/route_plans"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"primary": "example",
"fallbacks": [
"example"
]
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsRoutePlansCreate();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/route_plans");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"primary": "example",
"fallbacks": [
"example"
]
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsRoutePlansCreate(ctx).Execute()body := strings.NewReader(`{
"primary": "example",
"fallbacks": [
"example"
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/route_plans", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "route_plan",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"primary_rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"fallback_rail_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"reasoning": [
"example"
],
"max_attempts": 1,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/route_plans/{route_plan_id}#Check the rail order behind one payment
payouts.route_plans.retrieve
This reads one route plan back as it was stored, the rail order chosen before anything is attempted. So when somebody asks which rail a payment was meant to take, the answer comes from a record written before the attempt and not from anybody's memory of that morning.
Send the plan's id in the path. You get the rail named first, the rails to try after it in order, how many attempts the plan allows across all of them, the instruction it belongs to and when it was written.
A plan is the decision and never the outcome. What each rail answered is kept on the payout at GET /v1/payouts/payouts/{payout_id}/attempts. A route_plan_id nothing holds answers not_found.
Path parameters
route_plan_id
string
requiredThe route plan's identifier, starting with route_multi_rail_route_plan_: the id of a plan you created at POST /v1/payouts/route_plans or listed at GET /v1/payouts/route_plans, or the route_plan_id on an instruction you planned.
Returns
One plan as it was stored, with the rails it named and the attempt cap.
id
string
requiredThe plan's identifier, starting with route_multi_rail_route_plan_; it never changes. It is the route_plan_id on a planned instruction, and you pass it as route_plan_id at GET /v1/payouts/route_plans/{route_plan_id}.
object
always "route_plan"
requiredAlways route_plan. Tells you which kind of record you are looking at, so one handler can read any response.
instruction_id
string
requiredThe instruction this plan routes: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. Pass it as instruction_id at GET /v1/payouts/route_plans to find the plan behind an instruction.
primary_rail_id
string
requiredThe rail to try first: the id of a rail from GET /v1/payouts/rails, starting with route_multi_rail_payout_. Fixed when the plan is made, never decided at run time, so a failover away from it can be reconstructed from the attempts.
fallback_rail_ids
array of string
requiredThe rails to try next, in order, when the primary won't carry the payment, each by its id; there is always at least one. Failover to the next fires within 15 seconds at the 95th percentile.
reasoning
array of string
optionalWhy this order, kept so you can audit the plan.
max_attempts
integer · minimum 1
optionalHow many attempts the plan allows across the primary rail and its fallbacks, at least 1. Once they are used up nothing more is tried; each one is on the payout's attempts list.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsRoutePlansRetrieve({ routePlanId: 'route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_route_plans_retrieve(route_plan_id='route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsRoutePlansRetrieve(route_plan_id: 'route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsRoutePlansRetrieve("route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsRoutePlansRetrieve(routePlanId: "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsRoutePlansRetrieve(ctx, "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/route_plans/route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_route_plan_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "route_plan",
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"primary_rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"fallback_rail_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"reasoning": [
"example"
],
"max_attempts": 1,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/rails#See which rails can carry your payouts
payouts.rails.list
A rail is one way money leaves Droomwork for a payee's account, and rails differ in what they will carry and how soon they settle. This is where that set comes from, so your product can show which rails are usable and what each one carries without a rail table written into your own code.
Ask without naming a rail and they come back together, each with its id, its kind, the capabilities that say what it will carry, and the health it reports.
Store the id and not the name. The id never changes and it is what you quote in a call; name is there to put in front of a person.
When you need one rail's record on its own later, GET /v1/payouts/rails/{rail_id} takes the id you stored.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
health
string
optionalReturn only rails in one state: healthy, degraded (up, but not at full service) or unavailable (not carrying payouts right now). Leave it out to get every rail.
healthydegradedunavailableReturns
The rails, each with its kind, what it will carry and the health it reports.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Rail
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
7 fields of Rail
id
string
requiredThe rail's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/rails, and it is the primary_rail_id and fallback_rail_ids on a route plan and the rail_id on an attempt.
object
always "rail"
requiredAlways rail. Tells you which kind of record you are looking at, so one handler can read any response.
name
string
requiredThe rail's name as people know it, such as NIP instant transfer. For display; name the rail by id in a call.
kind
string
requiredWhat sort of rail it is: instant_transfer (such as NIP), direct_bank (a direct bank integration), wallet or mobile_money. All four sit behind the same interface.
instant_transferdirect_bankwalletmobile_moneyhealth
string
requiredhealthydegradedunavailablecapabilities
object
optionalWhat the rail can do: the largest amount it will carry, whether it can confirm an account name before paying, and how soon it settles.
3 fields
max_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
supports_name_enquiry
boolean
optionaltrue when the rail can return the name on an account before any money moves, which is what a name mismatch check needs. false when it can't.
settlement_window
string
optionalHow soon money sent on this rail lands, in words such as near instant, same day or next business day.
credentials_held
boolean
optionalWhether your credentials for this rail are in place: true when your payouts can go through it, false when it can't carry your money yet. GET /v1/payouts/readiness names the rails still without them.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), health (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), health (optional)
const result = await api.payoutsRailsList({ limit: 25, health: 'healthy' });// query parameters: limit (optional), health (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), health (optional)
result = api.payouts_rails_list(limit=25, health='healthy')import os
import requests
# query parameters: limit (optional), health (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), health (optional)
$result = $api->payoutsRailsList(limit: 25, health: 'healthy');<?php
// query parameters: limit (optional), health (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), health (optional)
var result = api.payoutsRailsList(25, RouteRailHealth.fromValue("healthy"));// query parameters: limit (optional), health (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), health (optional)
var result = api.PayoutsRailsList(limit: 25, health: RouteRailHealth.Healthy);// query parameters: limit (optional), health (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), health (optional)
result, _, err := client.ROUTEAPI.PayoutsRailsList(ctx).Limit(25).Health(droomwork.RouteRailHealth("healthy")).Execute()// query parameters: limit (optional), health (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/rails?limit=25&health=healthy", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "rail",
"name": "NIP instant transfer",
"kind": "instant_transfer",
"health": "healthy",
"capabilities": {
"max_amount": {
"amount": 100000000,
"currency": "NGN"
},
"supports_name_enquiry": true,
"settlement_window": "near instant"
},
"credentials_held": true
}
],
"has_more": true
}
/v1/payouts/rails/{rail_id}#Check what one rail will carry
payouts.rails.retrieve
A rail is one way money leaves Droomwork for a payee's account. This reads one of them on its own, so a product that already stored a rail's identifier can ask what that rail will carry without pulling the whole set.
Put the rail's identifier in the path and that one record comes back: its kind, the capabilities that say what it will carry and how soon it settles, the health it reports, and whether your credentials for it are in place.
capabilities.max_amount is the largest single payout the rail will carry, in whole minor units with its currency. credentials_held is a separate question from health: a rail can report itself healthy and still be one your money cannot go through yet.
Use an identifier that came back from GET /v1/payouts/rails; a rail_id nothing holds answers not_found.
Path parameters
rail_id
string
requiredThe rail's identifier, starting with route_multi_rail_payout_: the id of a rail listed at GET /v1/payouts/rails, the primary_rail_id on a route plan, or the rail_id on an attempt at GET /v1/payouts/payouts/{payout_id}/attempts.
Returns
The one rail, with its capabilities, the health it reports and whether your credentials are in place.
id
string
requiredThe rail's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/rails, and it is the primary_rail_id and fallback_rail_ids on a route plan and the rail_id on an attempt.
object
always "rail"
requiredAlways rail. Tells you which kind of record you are looking at, so one handler can read any response.
name
string
requiredThe rail's name as people know it, such as NIP instant transfer. For display; name the rail by id in a call.
kind
string
requiredWhat sort of rail it is: instant_transfer (such as NIP), direct_bank (a direct bank integration), wallet or mobile_money. All four sit behind the same interface.
instant_transferdirect_bankwalletmobile_moneyhealth
string
requiredhealthydegradedunavailablecapabilities
object
optionalWhat the rail can do: the largest amount it will carry, whether it can confirm an account name before paying, and how soon it settles.
3 fields
max_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
supports_name_enquiry
boolean
optionaltrue when the rail can return the name on an account before any money moves, which is what a name mismatch check needs. false when it can't.
settlement_window
string
optionalHow soon money sent on this rail lands, in words such as near instant, same day or next business day.
credentials_held
boolean
optionalWhether your credentials for this rail are in place: true when your payouts can go through it, false when it can't carry your money yet. GET /v1/payouts/readiness names the rails still without them.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsRailsRetrieve({ railId: '{rail_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_rails_retrieve(rail_id='{rail_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsRailsRetrieve(rail_id: '{rail_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsRailsRetrieve("{rail_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsRailsRetrieve(railId: "{rail_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsRailsRetrieve(ctx, "{rail_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/rails/%7Brail_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "rail",
"name": "NIP instant transfer",
"kind": "instant_transfer",
"health": "healthy",
"capabilities": {
"max_amount": {
"amount": 100000000,
"currency": "NGN"
},
"supports_name_enquiry": true,
"settlement_window": "near instant"
},
"credentials_held": true
}
/v1/payouts/payouts#Track each payee's payment separately
payouts.payouts.list
ROUTE turns one instruction into the payments it actually makes: one for each payee, or one for each sub-leg where a payee's line splits across destinations. This list is where you see them, and each one settles on its own, so a payee whose payment fails tells you nothing about the instruction's other payouts.
You get your organisation's payouts, each carrying the payee's subject_id, the amount in whole minor units with its currency, the account it was sent to with its number masked, and where that one payment has got to.
Take an id from the list, beginning route_multi_rail_payout_, to GET /v1/payouts/payouts/{payout_id} for one payment on its own, or pass it as payout_id to POST /v1/payouts/payouts/{payout_id}/resolve when a payment's outcome is in doubt.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
instruction_id
string
optionalReturn only the payouts of one instruction: pass its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions or GET /v1/payouts/instructions. Leave it out to get payouts from every instruction.
status
string
optionalReturn only payouts in one state: pending (no attempt yet), attempting (a rail is being tried), unknown (timed out, awaiting the provider's answer), settled or failed. Leave it out to get every state.
pendingattemptingunknownsettledfailedReturns
Your organisation's payouts, each with its payee, its amount and where that one payment has got to.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Payout
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of Payout
id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.
object
always "payout"
requiredAlways payout. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
instruction_id
string
requiredThe instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.
subject_id
string
requiredWho is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
status
string
requiredunknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.
pendingattemptingunknownsettledfaileddestination
Destination
optionalResolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.
5 fields of Destination
bank_code
string
requiredThe code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.
bank_name
string
optionalThe payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.
account_number_masked
string
requiredThe payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.
account_name
string
requiredThe name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.
validated
boolean
optionaltrue once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.
attempt_count
integer · minimum 0
optionalHow many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.
unknown_since
string · date-time · nullable
optionalSet while the provider is being polled. Cleared on resolution.
settlement_id
string · nullable
optionalThe settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
const result = await api.payoutsPayoutsList({ limit: 25, instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status: 'pending' });// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
result = api.payouts_payouts_list(limit=25, instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status='pending')import os
import requests
# query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
$result = $api->payoutsPayoutsList(limit: 25, instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status: 'pending');<?php
// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
var result = api.payoutsPayoutsList(25, null, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", RoutePayoutStatus.fromValue("pending"));// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
var result = api.PayoutsPayoutsList(limit: 25, instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", status: RoutePayoutStatus.Pending);// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
result, _, err := client.ROUTEAPI.PayoutsPayoutsList(ctx).Limit(25).InstructionId("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Status(droomwork.RoutePayoutStatus("pending")).Execute()// query parameters: limit (optional), starting_after (optional), instruction_id (optional), status (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/payouts?limit=25&instruction_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=pending", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout",
"livemode": false,
"mocked": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"status": "pending",
"destination": {
"bank_code": "example",
"account_number_masked": "******4821",
"account_name": "example",
"bank_name": "example",
"validated": true
},
"attempt_count": 0,
"unknown_since": "2026-09-01T09:00:00Z",
"settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payouts/payouts/{payout_id}#Check where one payee's money stands
payouts.payouts.retrieve
When a payee says the money never arrived, this is the record that answers for them. A payout is the one payment made for one payee out of an instruction, so you can answer that one question without reading the whole instruction.
Pass the payout_id in the path and you get that payout: its status, the amount in whole minor units with its currency, and the account it was sent to.
You only ever get account_number_masked, the last four digits of the account. Match a payout to your own records by subject_id and the amount, and do not read destination.validated as the bank disagreeing: it is false for an account nobody checked too.
What was tried is a separate call. GET /v1/payouts/payouts/{payout_id}/attempts lists every rail attempt made for this payout and what came back from each. A payout_id nothing holds answers not_found.
Path parameters
payout_id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_: the id of a payout from GET /v1/payouts/payouts, or the payout_id on a settlement or an attempt.
Returns
The one payment made for one payee, with its state and the account it was sent to.
id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.
object
always "payout"
requiredAlways payout. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
instruction_id
string
requiredThe instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.
subject_id
string
requiredWho is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
status
string
requiredunknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.
pendingattemptingunknownsettledfaileddestination
Destination
optionalResolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.
5 fields of Destination
bank_code
string
requiredThe code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.
bank_name
string
optionalThe payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.
account_number_masked
string
requiredThe payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.
account_name
string
requiredThe name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.
validated
boolean
optionaltrue once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.
attempt_count
integer · minimum 0
optionalHow many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.
unknown_since
string · date-time · nullable
optionalSet while the provider is being polled. Cleared on resolution.
settlement_id
string · nullable
optionalThe settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsPayoutsRetrieve({ payoutId: '{payout_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_payouts_retrieve(payout_id='{payout_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsPayoutsRetrieve(payout_id: '{payout_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsPayoutsRetrieve("{payout_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsPayoutsRetrieve(payoutId: "{payout_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsPayoutsRetrieve(ctx, "{payout_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout",
"livemode": false,
"mocked": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"status": "pending",
"destination": {
"bank_code": "example",
"account_number_masked": "******4821",
"account_name": "example",
"bank_name": "example",
"validated": true
},
"attempt_count": 0,
"unknown_since": "2026-09-01T09:00:00Z",
"settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/payouts/{payout_id}/resolve#Close one payout whose outcome was in doubt
payouts.payouts.resolve
One payout can end up with its outcome in doubt while everything else in the instruction is done. This call works on that one payout, by its own identifier, so you act on one payee's position and leave the instruction's other payouts alone.
Send no request body. The payout_id in the path is the whole request, and it must name a payout you already hold from GET /v1/payouts/payouts. The payout comes back with its state, so you read it here and not on a second call.
A payout_id nothing holds answers not_found.
Path parameters
payout_id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_: the id of a payout from GET /v1/payouts/payouts, or the payout_id on a settlement or an attempt.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The payout as it stands after the call, with its state, its payee and its amount.
id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.
object
always "payout"
requiredAlways payout. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
instruction_id
string
requiredThe instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.
subject_id
string
requiredWho is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
status
string
requiredunknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.
pendingattemptingunknownsettledfaileddestination
Destination
optionalResolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.
5 fields of Destination
bank_code
string
requiredThe code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.
bank_name
string
optionalThe payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.
account_number_masked
string
requiredThe payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.
account_name
string
requiredThe name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.
validated
boolean
optionaltrue once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.
attempt_count
integer · minimum 0
optionalHow many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.
unknown_since
string · date-time · nullable
optionalSet while the provider is being polled. Cleared on resolution.
settlement_id
string · nullable
optionalThe settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsPayoutsResolve({ payoutId: '{payout_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_payouts_resolve(payout_id='{payout_id}')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsPayoutsResolve(payout_id: '{payout_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsPayoutsResolve("{payout_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsPayoutsResolve(payoutId: "{payout_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsPayoutsResolve(ctx, "{payout_id}").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/resolve", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout",
"livemode": false,
"mocked": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"status": "settled",
"destination": {
"bank_code": "example",
"account_number_masked": "******4821",
"account_name": "example",
"bank_name": "example",
"validated": true
},
"attempt_count": 0,
"unknown_since": "2026-09-01T09:00:00Z",
"settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/payouts/{payout_id}/attempts#Trace how a payment was tried
payouts.attempts.list
An attempt is one try at paying one payout through one rail. These are the tries made for one payout and what each came back with, so your own view can say which rails handled a payment and how many times it was tried.
You name the payout and get back its attempts, each with the rail it went through, its outcome, and when it started and ended.
Read sequence before you take anything from the order: 1 is the try at the primary rail, and anything higher is a try at a fallback. completed_at is null while a try is still open, so handle the null before you time one.
An attempt's id is what you send as attempt_id to GET /v1/payouts/payouts/{payout_id}/attempts/{attempt_id}. A payout_id nothing holds answers not_found.
Path parameters
payout_id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_: the id of a payout from GET /v1/payouts/payouts, or the payout_id on a settlement or an attempt.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
Returns
The tries made for this payout, each with its rail, its outcome and its timings.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Attempt
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
11 fields of Attempt
id
string
requiredThe attempt's identifier. It never changes; pass it as attempt_id to GET /v1/payouts/payouts/{payout_id}/attempts/{attempt_id} to retrieve the attempt on its own.
object
always "payout_attempt"
requiredAlways payout_attempt. Tells you which kind of record you are looking at, so one handler can read any response.
payout_id
string
requiredThe payout this attempt was made for: the id of a payout from GET /v1/payouts/payouts, which starts with route_multi_rail_payout_. It is the payout_id you passed in the path to list or retrieve its attempts.
rail_id
string
requiredThe rail this attempt went through: the id of a rail from GET /v1/payouts/rails, which starts with route_multi_rail_payout_. Read sequence to see whether it was the primary rail or a failover.
sequence
integer · minimum 1
optional1 is the primary rail. Anything higher is a failover.
outcome
string
requiredacceptedrejectedtimed_outprovider_errorprovider_reference
string · nullable
optionalThe reference the provider gave for this attempt, as it appears on their side; quote it when you take a question to them. null when they gave none, such as after a timeout.
provider_message
string · nullable
optionalWhat the provider said about the attempt, in their own words, such as the reason for a rejection. null when they said nothing.
started_at
string · date-time
requiredWhen the attempt was sent to the rail, as an RFC 3339 timestamp in UTC. The gap from the previous attempt's failure or timeout to this moment is failover_latency_ms.
completed_at
string · date-time · nullable
optionalWhen the attempt ended, with the rail's answer or a timeout, as an RFC 3339 timestamp in UTC. null while it is still open.
failover_latency_ms
integer · nullable
optionalTime from the failure or timeout signal on the previous attempt to this one starting. The 95th percentile of this is what the 15 second failover requirement is measured against.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional)
const result = await api.payoutsAttemptsList({ payoutId: '{payout_id}', limit: 25 });// query parameters: limit (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional)
result = api.payouts_attempts_list(payout_id='{payout_id}', limit=25)import os
import requests
# query parameters: limit (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional)
$result = $api->payoutsAttemptsList(payout_id: '{payout_id}', limit: 25);<?php
// query parameters: limit (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional)
var result = api.payoutsAttemptsList("{payout_id}", 25);// query parameters: limit (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional)
var result = api.PayoutsAttemptsList(payoutId: "{payout_id}", limit: 25);// query parameters: limit (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional)
result, _, err := client.ROUTEAPI.PayoutsAttemptsList(ctx, "{payout_id}").Limit(25).Execute()// query parameters: limit (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts?limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_attempt",
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "accepted",
"started_at": "2026-09-01T09:00:00Z",
"sequence": 1,
"provider_reference": "paye-2026-09-rivers",
"provider_message": "example",
"completed_at": "2026-09-01T09:00:00Z",
"failover_latency_ms": 1
}
],
"has_more": true
}
/v1/payouts/payouts/{payout_id}/attempts/{attempt_id}#What one rail came back with
payouts.attempts.retrieve
An attempt is one try at paying one payout through one rail. This hands you one of them whole, so a question about a single try can be taken to the provider on their own reference for it, where they gave one.
You name the payout the try was made for and the attempt itself, and get back that one attempt: its rail, its outcome, its timings, and what the provider said about it in their own words.
provider_reference and provider_message are each null where the provider gave none, so anything you build on them has to read correctly with neither.
This is the whole of one try. Where it sat among the others is on the payout's full list at GET /v1/payouts/payouts/{payout_id}/attempts.
Path parameters
payout_id
string
requiredThe payout's identifier, starting with route_multi_rail_payout_: the id of a payout from GET /v1/payouts/payouts, or the payout_id on a settlement or an attempt.
attempt_id
string
requiredThe attempt's identifier, from the id of an attempt you listed at GET /v1/payouts/payouts/{payout_id}/attempts. Pair it with the payout_id of the payout it was made for, as payout_id reads on the attempt.
Returns
One try at one rail, with the provider's own reference and message where they gave them.
id
string
requiredThe attempt's identifier. It never changes; pass it as attempt_id to GET /v1/payouts/payouts/{payout_id}/attempts/{attempt_id} to retrieve the attempt on its own.
object
always "payout_attempt"
requiredAlways payout_attempt. Tells you which kind of record you are looking at, so one handler can read any response.
payout_id
string
requiredThe payout this attempt was made for: the id of a payout from GET /v1/payouts/payouts, which starts with route_multi_rail_payout_. It is the payout_id you passed in the path to list or retrieve its attempts.
rail_id
string
requiredThe rail this attempt went through: the id of a rail from GET /v1/payouts/rails, which starts with route_multi_rail_payout_. Read sequence to see whether it was the primary rail or a failover.
sequence
integer · minimum 1
optional1 is the primary rail. Anything higher is a failover.
outcome
string
requiredacceptedrejectedtimed_outprovider_errorprovider_reference
string · nullable
optionalThe reference the provider gave for this attempt, as it appears on their side; quote it when you take a question to them. null when they gave none, such as after a timeout.
provider_message
string · nullable
optionalWhat the provider said about the attempt, in their own words, such as the reason for a rejection. null when they said nothing.
started_at
string · date-time
requiredWhen the attempt was sent to the rail, as an RFC 3339 timestamp in UTC. The gap from the previous attempt's failure or timeout to this moment is failover_latency_ms.
completed_at
string · date-time · nullable
optionalWhen the attempt ended, with the rail's answer or a timeout, as an RFC 3339 timestamp in UTC. null while it is still open.
failover_latency_ms
integer · nullable
optionalTime from the failure or timeout signal on the previous attempt to this one starting. The 95th percentile of this is what the 15 second failover requirement is measured against.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsAttemptsRetrieve({ payoutId: '{payout_id}', attemptId: '{attempt_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_attempts_retrieve(payout_id='{payout_id}', attempt_id='{attempt_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsAttemptsRetrieve(payout_id: '{payout_id}', attempt_id: '{attempt_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsAttemptsRetrieve("{payout_id}", "{attempt_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsAttemptsRetrieve(payoutId: "{payout_id}", attemptId: "{attempt_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsAttemptsRetrieve(ctx, "{payout_id}", "{attempt_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/payouts/%7Bpayout_id%7D/attempts/%7Battempt_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_attempt",
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"rail_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "accepted",
"started_at": "2026-09-01T09:00:00Z",
"sequence": 1,
"provider_reference": "paye-2026-09-rivers",
"provider_message": "example",
"completed_at": "2026-09-01T09:00:00Z",
"failover_latency_ms": 1
}
/v1/payouts/account_validations#What the banks found across your roster
payouts.account_validations.list
Your organisation's validations collect here, so one page answers how a whole roster stands instead of one payee at a time. Nothing in a validation names a payment or a run, so a check is readable whether or not anything is being paid.
You get back the validations your organisation holds, one row for each check that has been run. A validation is one check of one payee's account against the name the bank holds for it.
Read one in full at GET /v1/payouts/account_validations/{account_validation_id}, using the id on its row.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
outcome
string
optionalRestricts the page to one outcome: valid (the name matches), name_mismatch (it doesn't), account_not_found or unresolved (no answer, never a pass). Pass name_mismatch to find the errands before payday; leave it out to get all four.
validname_mismatchaccount_not_foundunresolvedReturns
The validations your organisation holds, with what the bank answered on each.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of AccountValidation
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
9 fields of AccountValidation
id
string
requiredThe validation's identifier. It starts with route_multi_rail_payout_ and never changes; pass it as account_validation_id to GET /v1/payouts/account_validations/{account_validation_id} to retrieve this record on its own.
object
always "account_validation"
requiredAlways account_validation. Tells you which kind of record you are looking at, so one handler can read any response.
subject_id
string
requiredWho the check was for: one of the subject_ids you sent to POST /v1/payouts/account_validations, the sub_ reference that names a payee in lines[].subject_id at POST /v1/payouts/instructions.
outcome
string
requiredvalidname_mismatchaccount_not_foundunresolvedexpected_name
string · nullable
optionalThe name you expected on the account. Compare it with bank_name_returned when the outcome is name_mismatch; null when there was no name to compare.
bank_name_returned
string · nullable
optionalWhat the bank holds. Compared with the expected name, and neither is a full account number.
similarity
number · nullable
optionalHow close the two names are, from 0 to 1. A judgement aid, not a decision.
source
string · nullable
optionalNamed when the outcome is unresolved, because silence is never a pass.
checked_at
string · date-time
optionalWhen the bank was asked, as an RFC 3339 timestamp in UTC. Read it to see how old a result is before you rely on it on payday.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), outcome (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), outcome (optional)
const result = await api.payoutsAccountValidationsList({ limit: 25, outcome: 'valid' });// query parameters: limit (optional), starting_after (optional), outcome (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), starting_after (optional), outcome (optional)
result = api.payouts_account_validations_list(limit=25, outcome='valid')import os
import requests
# query parameters: limit (optional), starting_after (optional), outcome (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), outcome (optional)
$result = $api->payoutsAccountValidationsList(limit: 25, outcome: 'valid');<?php
// query parameters: limit (optional), starting_after (optional), outcome (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), starting_after (optional), outcome (optional)
var result = api.payoutsAccountValidationsList(25, null, RouteValidationOutcome.fromValue("valid"));// query parameters: limit (optional), starting_after (optional), outcome (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), starting_after (optional), outcome (optional)
var result = api.PayoutsAccountValidationsList(limit: 25, outcome: RouteValidationOutcome.Valid);// query parameters: limit (optional), starting_after (optional), outcome (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), outcome (optional)
result, _, err := client.ROUTEAPI.PayoutsAccountValidationsList(ctx).Limit(25).Outcome(droomwork.RouteValidationOutcome("valid")).Execute()// query parameters: limit (optional), starting_after (optional), outcome (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/account_validations?limit=25&outcome=valid", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "account_validation",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "valid",
"expected_name": "example",
"bank_name_returned": "example",
"similarity": 1,
"source": "example",
"checked_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payouts/account_validations#Validate destination accounts ahead of payday
payouts.account_validations.create
This asks the bank holding a payee's account what name that account is in. The answer is the bank's own, which is the one thing your records cannot supply, and each check is kept as a record you can read back later.
Send the sub_ references of the payees you want checked, as subject_ids, at least one. No account number goes up and none comes back. No instruction, run or payment has to exist first.
This check belongs to the payment provider. Deciding internally that a name is close enough does not answer it, whatever your own policy allows.
Your validations are read back at GET /v1/payouts/account_validations, and one on its own at GET /v1/payouts/account_validations/{account_validation_id}.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_ids
array of string
requiredA sweep across a roster. Run it days before payday.
Returns
The sweep itself, not the answers, which arrive one per payee afterwards.
object
always "account_validation_batch"
requiredAlways account_validation_batch. Tells you which kind of record you are looking at, so one handler can read any response.
id
string
requiredThe sweep's identifier, returned by POST /v1/payouts/account_validations when you send a batch of subject_ids. It never changes; quote it when you raise a question about the sweep.
subject_count
integer
requiredHow many people this sweep covers: the number of subject_ids you sent. Expect one validation per subject.
status
string
requiredrunning while subjects are still being checked, completed once every subject has a validation you can read at GET /v1/payouts/account_validations or at results_url.
runningcompletedresults_url
string · uri
optionalWhere to fetch the sweep's results, as an absolute URL. Each subject's validation also appears at GET /v1/payouts/account_validations as it lands.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/account_validations" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}'import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsAccountValidationsCreate({
idempotencyKey: crypto.randomUUID(),
routeAccountValidationCreateRequest: {"subjectIds":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
});const response = await fetch('https://sandbox.droomwork.io/v1/payouts/account_validations', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_account_validations_create(body={"subject_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/account_validations',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsAccountValidationsCreate($idempotencyKey, json_decode('{"subject_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/account_validations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"subject_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsAccountValidationsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/account_validations"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsAccountValidationsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/account_validations");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsAccountValidationsCreate(ctx).IdempotencyKey(key).RouteAccountValidationCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/account_validations", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "account_validation_batch",
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"subject_count": 1,
"status": "running",
"results_url": "https://files.sandbox.droomwork.com/example"
}
/v1/payouts/account_validations/{account_validation_id}#One payee's account check in full
payouts.account_validations.retrieve
This hands back a single validation, for when the question is about one person and not about the roster. The identifier never changes, so your own record for that payee can point at the exact check that was run for them.
Pass the validation's id as account_validation_id and you get that one record: who it was for, as subject_id, and what the bank answered. A validation is one check of one payee's account against the name the bank holds for it.
Validations, payouts, rails and instructions all carry identifiers that start route_multi_rail_payout_, so the prefix will not tell you which of them you are holding. Keep an identifier with the record you read it from.
Path parameters
account_validation_id
string
requiredThe validation's identifier, from the id of a validation you listed at GET /v1/payouts/account_validations. It starts with route_multi_rail_payout_; a sweep produces one per subject.
Returns
The one check you named, with the payee it covers and the bank's answer.
id
string
requiredThe validation's identifier. It starts with route_multi_rail_payout_ and never changes; pass it as account_validation_id to GET /v1/payouts/account_validations/{account_validation_id} to retrieve this record on its own.
object
always "account_validation"
requiredAlways account_validation. Tells you which kind of record you are looking at, so one handler can read any response.
subject_id
string
requiredWho the check was for: one of the subject_ids you sent to POST /v1/payouts/account_validations, the sub_ reference that names a payee in lines[].subject_id at POST /v1/payouts/instructions.
outcome
string
requiredvalidname_mismatchaccount_not_foundunresolvedexpected_name
string · nullable
optionalThe name you expected on the account. Compare it with bank_name_returned when the outcome is name_mismatch; null when there was no name to compare.
bank_name_returned
string · nullable
optionalWhat the bank holds. Compared with the expected name, and neither is a full account number.
similarity
number · nullable
optionalHow close the two names are, from 0 to 1. A judgement aid, not a decision.
source
string · nullable
optionalNamed when the outcome is unresolved, because silence is never a pass.
checked_at
string · date-time
optionalWhen the bank was asked, as an RFC 3339 timestamp in UTC. Read it to see how old a result is before you rely on it on payday.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsAccountValidationsRetrieve({ accountValidationId: '{account_validation_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_account_validations_retrieve(account_validation_id='{account_validation_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsAccountValidationsRetrieve(account_validation_id: '{account_validation_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsAccountValidationsRetrieve("{account_validation_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsAccountValidationsRetrieve(accountValidationId: "{account_validation_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsAccountValidationsRetrieve(ctx, "{account_validation_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/account_validations/%7Baccount_validation_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "account_validation",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "valid",
"expected_name": "example",
"bank_name_returned": "example",
"similarity": 1,
"source": "example",
"checked_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/settlements#See every settlement and what it paid
payouts.settlements.list
A settlement is the record that one payout's money arrived, or did not: paid or failed. This reads back the settlements your organisation holds, so your product can take a bank movement and name the payroll line it paid.
Read amount for the figure a settlement is for, in whole minor units with its currency, provider_reference for the rail's own reference for the payment, and origin for the run, payslip and payslip line behind it.
amount is one payout's figure, not an instruction's total, and an instruction split across destinations has one settlement per payout. Any part of origin is null where the payment traces to nothing there, so handle the null.
Carry a settlement's id as settlement_id to GET /v1/payouts/settlements/{settlement_id} to read one in full.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
run_id
string
optionalRestricts the page to settlements from one payroll run: the run's id from POST /v1/payroll/runs or GET /v1/payroll/runs, starting run_enterprise_, as it reads in origin.run_id. Leave it out for settlements from every run and remittance.
outcome
string
optionalRestricts the page to one outcome: paid (the money arrived and was independently validated) or failed (it won't). Leave it out to get both.
paidfailedReturns
The settlements on this page, each with the figure that moved and the trail back to what it paid.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Settlement
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
12 fields of Settlement
id
string
requiredThe settlement's identifier. It starts with route_multi_rail_settlement_ and never changes; pass it as settlement_id to GET /v1/payouts/settlements/{settlement_id} or its /receipt, and read it as settlement_id on the payout it closed.
object
always "settlement"
requiredAlways settlement. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
payout_id
string
requiredThe payout this settlement closes: the id of a payout from GET /v1/payouts/payouts, which starts with route_multi_rail_payout_. When you record a settlement at POST /v1/payouts/settlements, this is the payout_id you sent.
instruction_id
string
optionalThe instruction behind this settlement's payout: the id returned by POST /v1/payouts/instructions, which starts with route_multi_rail_payout_. Read origin for the run, payslip and line it came from.
outcome
string
requiredpaidfailedamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
provider_reference
string
optionalThe rail's own reference for this payment, exactly as it reported it. One of the three things checked against the originating instruction before paid is set, and what you quote to the rail about this payment.
independently_validated
boolean
requiredAmount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.
origin
object
optionalThe trail from a bank notification back to a payslip line.
4 fields
run_id
string · nullable
optionalThe payroll run the payment came from: the run_id you sent at POST /v1/payouts/instructions, a run's id from POST /v1/payroll/runs (run_enterprise_). Pass it as run_id to GET /v1/payouts/settlements; null when it traces to no run.
payslip_id
string · nullable
optionalThe payslip within run_id the payment was for: a payslip's id from GET /v1/payroll/payslips, starting run_enterprise_payslip_, one step on the trail from a bank notification to a payslip line. null when it traces to no payslip.
payslip_line_id
string · nullable
optionalThe payslip line this settlement pays: the payslip_line_id you sent on the instruction line at POST /v1/payouts/instructions, a line's id in lines on a payslip from GET /v1/payroll/payslips. null when it traces to no payslip line.
remittance_id
string · nullable
optionalThe remittance the payment was for: the remittance_id you sent at POST /v1/payouts/instructions, a remittance's id from POST /v1/remittance/remittances (remit_authority_rail_remittance_). null when it traces to no remittance.
settled_at
string · date-time · nullable
optionalWhen the money arrived, as an RFC 3339 timestamp in UTC. null when there is no such moment, as on a failed settlement.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
const result = await api.payoutsSettlementsList({ limit: 25, runId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'paid' });// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
result = api.payouts_settlements_list(limit=25, run_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome='paid')import os
import requests
# query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
$result = $api->payoutsSettlementsList(limit: 25, run_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'paid');<?php
// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
var result = api.payoutsSettlementsList(25, null, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", RouteSettlementOutcome.fromValue("paid"));// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
var result = api.PayoutsSettlementsList(limit: 25, runId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", outcome: RouteSettlementOutcome.Paid);// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
result, _, err := client.ROUTEAPI.PayoutsSettlementsList(ctx).Limit(25).RunId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Outcome(droomwork.RouteSettlementOutcome("paid")).Execute()// query parameters: limit (optional), starting_after (optional), run_id (optional), outcome (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/settlements?limit=25&run_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=paid", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "settlement",
"livemode": false,
"mocked": true,
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "paid",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"independently_validated": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"provider_reference": "paye-2026-09-rivers",
"origin": {
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"settled_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payouts/settlements#Record a confirmation that money arrived
payouts.settlements.create
This records a settlement against a payout: confirmation that its money arrived. So a payment confirmed outside the rail's own reporting reaches the same record your other settlements are read from, with a receipt of its own.
Send the payout_id of the payout that was confirmed. You get the settlement back, with an id starting route_multi_rail_settlement_ to keep.
Take the payout_id from GET /v1/payouts/payouts.
payout_id is what ties a confirmation to the money it is about. Send the payout the statement or the call named, not the instruction behind it.
Read the settlement back at GET /v1/payouts/settlements/{settlement_id}, passing the id as settlement_id.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
payout_id
string
optionalThe payout this settles: the id of a payout from GET /v1/payouts/payouts or GET /v1/payouts/payouts/{payout_id}, which starts with route_multi_rail_payout_. Send the one the rail, statement or call confirmed.
Returns
The settlement as recorded, with the id to read it back or fetch its receipt.
id
string
requiredThe settlement's identifier. It starts with route_multi_rail_settlement_ and never changes; pass it as settlement_id to GET /v1/payouts/settlements/{settlement_id} or its /receipt, and read it as settlement_id on the payout it closed.
object
always "settlement"
requiredAlways settlement. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
payout_id
string
requiredThe payout this settlement closes: the id of a payout from GET /v1/payouts/payouts, which starts with route_multi_rail_payout_. When you record a settlement at POST /v1/payouts/settlements, this is the payout_id you sent.
instruction_id
string
optionalThe instruction behind this settlement's payout: the id returned by POST /v1/payouts/instructions, which starts with route_multi_rail_payout_. Read origin for the run, payslip and line it came from.
outcome
string
requiredpaidfailedamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
provider_reference
string
optionalThe rail's own reference for this payment, exactly as it reported it. One of the three things checked against the originating instruction before paid is set, and what you quote to the rail about this payment.
independently_validated
boolean
requiredAmount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.
origin
object
optionalThe trail from a bank notification back to a payslip line.
4 fields
run_id
string · nullable
optionalThe payroll run the payment came from: the run_id you sent at POST /v1/payouts/instructions, a run's id from POST /v1/payroll/runs (run_enterprise_). Pass it as run_id to GET /v1/payouts/settlements; null when it traces to no run.
payslip_id
string · nullable
optionalThe payslip within run_id the payment was for: a payslip's id from GET /v1/payroll/payslips, starting run_enterprise_payslip_, one step on the trail from a bank notification to a payslip line. null when it traces to no payslip.
payslip_line_id
string · nullable
optionalThe payslip line this settlement pays: the payslip_line_id you sent on the instruction line at POST /v1/payouts/instructions, a line's id in lines on a payslip from GET /v1/payroll/payslips. null when it traces to no payslip line.
remittance_id
string · nullable
optionalThe remittance the payment was for: the remittance_id you sent at POST /v1/payouts/instructions, a remittance's id from POST /v1/remittance/remittances (remit_authority_rail_remittance_). null when it traces to no remittance.
settled_at
string · date-time · nullable
optionalWhen the money arrived, as an RFC 3339 timestamp in UTC. null when there is no such moment, as on a failed settlement.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/settlements" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"payout_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsSettlementsCreate({});const response = await fetch('https://sandbox.droomwork.io/v1/payouts/settlements', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_settlements_create()import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/settlements',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsSettlementsCreate();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/settlements');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"payout_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsSettlementsCreate();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/settlements"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsSettlementsCreate();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/settlements");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsSettlementsCreate(ctx).Execute()body := strings.NewReader(`{
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/settlements", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "settlement",
"livemode": false,
"mocked": true,
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "paid",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"independently_validated": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"provider_reference": "paye-2026-09-rivers",
"origin": {
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"settled_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/settlements/{settlement_id}#Check whether one payout's money arrived
payouts.settlements.retrieve
This reads one settlement back: whether one payout's money arrived, the figure that moved, and the rail's own reference for it. So a screen in your product answers a question about one payment weeks later from the identifier it stored, and shows the rail's own reference to quote back.
Send the settlement's id in the path as settlement_id. You get outcome, the amount it is for in whole minor units with its currency, provider_reference and settled_at.
settled_at is null where there is no such moment, as on a failed settlement. Read outcome first; a missing timestamp is not a payment still on its way.
The receipt for this settlement is at GET /v1/payouts/settlements/{settlement_id}/receipt, under the same settlement_id, and there is one whether the outcome was paid or failed. A settlement_id nothing holds answers not_found.
Path parameters
settlement_id
string
requiredThe settlement's identifier, from the id of a settlement you listed at GET /v1/payouts/settlements or recorded at POST /v1/payouts/settlements, or from a payout's settlement_id. It starts with route_multi_rail_settlement_.
Returns
One settlement as it stands, with the outcome, the figure that moved and the rail reference for it.
id
string
requiredThe settlement's identifier. It starts with route_multi_rail_settlement_ and never changes; pass it as settlement_id to GET /v1/payouts/settlements/{settlement_id} or its /receipt, and read it as settlement_id on the payout it closed.
object
always "settlement"
requiredAlways settlement. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
payout_id
string
requiredThe payout this settlement closes: the id of a payout from GET /v1/payouts/payouts, which starts with route_multi_rail_payout_. When you record a settlement at POST /v1/payouts/settlements, this is the payout_id you sent.
instruction_id
string
optionalThe instruction behind this settlement's payout: the id returned by POST /v1/payouts/instructions, which starts with route_multi_rail_payout_. Read origin for the run, payslip and line it came from.
outcome
string
requiredpaidfailedamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
provider_reference
string
optionalThe rail's own reference for this payment, exactly as it reported it. One of the three things checked against the originating instruction before paid is set, and what you quote to the rail about this payment.
independently_validated
boolean
requiredAmount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.
origin
object
optionalThe trail from a bank notification back to a payslip line.
4 fields
run_id
string · nullable
optionalThe payroll run the payment came from: the run_id you sent at POST /v1/payouts/instructions, a run's id from POST /v1/payroll/runs (run_enterprise_). Pass it as run_id to GET /v1/payouts/settlements; null when it traces to no run.
payslip_id
string · nullable
optionalThe payslip within run_id the payment was for: a payslip's id from GET /v1/payroll/payslips, starting run_enterprise_payslip_, one step on the trail from a bank notification to a payslip line. null when it traces to no payslip.
payslip_line_id
string · nullable
optionalThe payslip line this settlement pays: the payslip_line_id you sent on the instruction line at POST /v1/payouts/instructions, a line's id in lines on a payslip from GET /v1/payroll/payslips. null when it traces to no payslip line.
remittance_id
string · nullable
optionalThe remittance the payment was for: the remittance_id you sent at POST /v1/payouts/instructions, a remittance's id from POST /v1/remittance/remittances (remit_authority_rail_remittance_). null when it traces to no remittance.
settled_at
string · date-time · nullable
optionalWhen the money arrived, as an RFC 3339 timestamp in UTC. null when there is no such moment, as on a failed settlement.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsSettlementsRetrieve({ settlementId: 'route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_settlements_retrieve(settlement_id='route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsSettlementsRetrieve(settlement_id: 'route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsSettlementsRetrieve("route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsSettlementsRetrieve(settlementId: "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsSettlementsRetrieve(ctx, "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "settlement",
"livemode": false,
"mocked": true,
"payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "paid",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"independently_validated": true,
"instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"provider_reference": "paye-2026-09-rivers",
"origin": {
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"settled_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/settlements/{settlement_id}/receipt#Prove one payment after the fact
payouts.receipts.retrieve
A receipt is the record that proves a single settlement, and each settlement has exactly one. Nothing updates or removes a receipt once it exists, so a copy your product keeps never goes stale.
Pass the settlement_id of a settlement you listed or recorded, and you get its receipt, naming the settlement it covers and the amount it attests in whole minor units with its currency.
No call takes a receipt onward, so one payment's trail ends here. Where you need the payout it closed or the run behind it, read the settlement itself at GET /v1/payouts/settlements/{settlement_id}.
Path parameters
settlement_id
string
requiredThe settlement's identifier, from the id of a settlement you listed at GET /v1/payouts/settlements or recorded at POST /v1/payouts/settlements, or from a payout's settlement_id. It starts with route_multi_rail_settlement_.
Returns
The receipt for that one settlement, which is what you keep and quote about that payment.
id
string
requiredThe receipt's identifier, as returned by GET /v1/payouts/settlements/{settlement_id}/receipt. It never changes, and neither does the receipt: once issued, nothing on it is updated or removed.
object
always "payout_receipt"
requiredAlways payout_receipt. Tells you which kind of record you are looking at, so one handler can read any response.
settlement_id
string
requiredThe settlement this receipt proves: the id of a settlement from GET /v1/payouts/settlements, starting route_multi_rail_settlement_, the same settlement_id you passed in the path. There is one receipt per settlement, paid or failed.
outcome
string
requiredpaidfailedamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
hash
string
requiredThe receipt's own hash, as the algorithm and the digest, such as sha256:9f2c1e0043a1b8. The next receipt carries it as previous_hash, which is what lets you check the chain.
previous_hash
string
requiredThe hash of the receipt issued just before this one. Each receipt carries the last one's hash, so you can walk the chain back and confirm nothing was changed or removed.
trace_references
array of string
optionalThe payroll and remittance traces this receipt is linked to.
issued_at
string · date-time
optionalWhen the receipt was issued and chained, as an RFC 3339 timestamp in UTC. From this moment it never changes.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsReceiptsRetrieve({ settlementId: 'route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_receipts_retrieve(settlement_id='route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsReceiptsRetrieve(settlement_id: 'route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsReceiptsRetrieve("route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsReceiptsRetrieve(settlementId: "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsReceiptsRetrieve(ctx, "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/settlements/route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/receipt", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_receipt",
"settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"outcome": "paid",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"hash": "sha256:9f2c1e0043a1b8",
"previous_hash": "sha256:9f2c1e0043a1b8",
"trace_references": [
"paye-2026-09-rivers"
],
"issued_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/reconciliations#Check which days are still in question
payouts.reconciliations.list
A reconciliation is one day's check: what the provider's statement says went out that day, set against the record of what was paid. Listing them is how a screen in your own product can show a day as agreed or still in question, with nobody comparing a statement by hand.
You send nothing, and get back a page of these days, each one carrying the day it covers, both totals in whole minor units with its currency, and whether it balanced. Keep each id, which starts route_multi_rail_settlement_.
Read one day in full at GET /v1/payouts/reconciliations/{reconciliation_id}, passing the id you kept as reconciliation_id.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
on
string
optionalRestricts the page to the reconciliation for one day, as YYYY-MM-DD, such as 2026-09-01, matching on on the record. One is produced for every day; leave it out to get every day.
Returns
A page of days, each saying whether it balanced and how many breaks it found.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Reconciliation
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
9 fields of Reconciliation
id
string
requiredThe reconciliation's identifier. It starts with route_multi_rail_settlement_ and never changes; pass it as reconciliation_id to GET /v1/payouts/reconciliations/{reconciliation_id} and to /exceptions under it to list the day's breaks.
object
always "reconciliation"
requiredAlways reconciliation. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
on
string · date
requiredThe day this reconciliation covers, as YYYY-MM-DD, such as 2026-09-01: that day's provider statement checked against what was paid. Filter the list by it with the on query.
balanced
boolean
requiredtrue when the day's provider statement and the record of what was paid agree, so break_count is 0. false means at least one break, each raised as an exception for you to answer.
provider_total
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
ledger_total
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
break_count
integer · minimum 0
requiredHow many breaks the day's check found, from 0 up: where the provider's statement and the record of what was paid disagree. 0 when balanced is true; each break is listed as an exception under /exceptions.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), on (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), on (optional)
const result = await api.payoutsReconciliationsList({ limit: 25, on: '2026-09-01' });// query parameters: limit (optional), on (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional), on (optional)
result = api.payouts_reconciliations_list(limit=25, on='2026-09-01')import os
import requests
# query parameters: limit (optional), on (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), on (optional)
$result = $api->payoutsReconciliationsList(limit: 25, on: '2026-09-01');<?php
// query parameters: limit (optional), on (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional), on (optional)
var result = api.payoutsReconciliationsList(25, "2026-09-01");// query parameters: limit (optional), on (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional), on (optional)
var result = api.PayoutsReconciliationsList(limit: 25, on: "2026-09-01");// query parameters: limit (optional), on (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), on (optional)
result, _, err := client.ROUTEAPI.PayoutsReconciliationsList(ctx).Limit(25).On("2026-09-01").Execute()// query parameters: limit (optional), on (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=2026-09-01", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "reconciliation",
"livemode": false,
"mocked": true,
"on": "2026-09-01",
"balanced": true,
"break_count": 0,
"provider_total": {
"amount": 57890000,
"currency": "NGN"
},
"ledger_total": {
"amount": 57900000,
"currency": "NGN"
}
}
],
"has_more": true
}
/v1/payouts/reconciliations/{reconciliation_id}#What one day's statement check found
payouts.reconciliations.retrieve
This reads one day's check on its own. A break is a payment where the provider's statement and the record of what was paid disagree, and the count on this record is what lets your product leave an agreed day alone and put only the days carrying breaks in front of somebody.
You pass the reconciliation_id you kept from the list, and that day comes back with both totals in whole minor units with its currency, whether it balanced, and how many breaks the check found.
The breaks themselves are not here, only how many there are. Read them at GET /v1/payouts/reconciliations/{reconciliation_id}/exceptions under the same identifier, where each one carries the amount it concerns and whether somebody has answered it.
Path parameters
reconciliation_id
string
requiredThe reconciliation's identifier, from the id of a reconciliation you listed at GET /v1/payouts/reconciliations. It starts with route_multi_rail_settlement_ and names one day's check.
Returns
The one day you asked for, with both totals and its count of breaks.
id
string
requiredThe reconciliation's identifier. It starts with route_multi_rail_settlement_ and never changes; pass it as reconciliation_id to GET /v1/payouts/reconciliations/{reconciliation_id} and to /exceptions under it to list the day's breaks.
object
always "reconciliation"
requiredAlways reconciliation. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
on
string · date
requiredThe day this reconciliation covers, as YYYY-MM-DD, such as 2026-09-01: that day's provider statement checked against what was paid. Filter the list by it with the on query.
balanced
boolean
requiredtrue when the day's provider statement and the record of what was paid agree, so break_count is 0. false means at least one break, each raised as an exception for you to answer.
provider_total
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
ledger_total
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
break_count
integer · minimum 0
requiredHow many breaks the day's check found, from 0 up: where the provider's statement and the record of what was paid disagree. 0 when balanced is true; each break is listed as an exception under /exceptions.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsReconciliationsRetrieve({ reconciliationId: '{reconciliation_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_reconciliations_retrieve(reconciliation_id='{reconciliation_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsReconciliationsRetrieve(reconciliation_id: '{reconciliation_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsReconciliationsRetrieve("{reconciliation_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsReconciliationsRetrieve(reconciliationId: "{reconciliation_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsReconciliationsRetrieve(ctx, "{reconciliation_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "reconciliation",
"livemode": false,
"mocked": true,
"on": "2026-09-01",
"balanced": true,
"break_count": 0,
"provider_total": {
"amount": 57890000,
"currency": "NGN"
},
"ledger_total": {
"amount": 57900000,
"currency": "NGN"
}
}
/v1/payouts/reconciliations/{reconciliation_id}/exceptions#Read the breaks a day's check found
payouts.exceptions.list
A reconciliation checks one day's provider statement against the record of what was paid, and every place the two disagreed is raised as a break for somebody to answer. The check is done for you, so what you work through here is the disagreements alone, not a statement to compare line by line.
Name the day's reconciliation in the path with an id from GET /v1/payouts/reconciliations, and you get its breaks, each carrying a code for the kind of break and the amount it concerns, in whole minor units with its currency.
Match on code. detail is the same break in words, written for a person to read, so show it and don't branch on it.
A break is never resolved automatically, so one nobody answers stays open. Nothing here closes one, and a reconciliation_id nothing holds answers not_found.
Path parameters
reconciliation_id
string
requiredThe reconciliation's identifier, from the id of a reconciliation you listed at GET /v1/payouts/reconciliations. It starts with route_multi_rail_settlement_ and names one day's check.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
Returns
The day's breaks, each with the kind of break, the amount it concerns and the words to read.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of ReconciliationException
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
9 fields of ReconciliationException
id
string
requiredThe exception's identifier, as id on each break listed at GET /v1/payouts/reconciliations/{reconciliation_id}/exceptions. It never changes; use it to follow one break from open to resolved.
object
always "reconciliation_exception"
requiredAlways reconciliation_exception. Tells you which kind of record you are looking at, so one handler can read any response.
reconciliation_id
string
optionalThe day's reconciliation this break belongs to: the id of a reconciliation from GET /v1/payouts/reconciliations, starting route_multi_rail_settlement_, the same reconciliation_id you listed the exceptions under.
code
string
requiredWhich kind of break: missing_in_ledger (the provider shows a payment we don't), missing_at_provider (we show one the provider doesn't), amount_mismatch (the amounts differ) or duplicate_at_provider (paid twice). detail says it in words.
missing_in_ledgermissing_at_provideramount_mismatchduplicate_at_providerdetail
string
requiredWhat the break is, in words, written for a person to read. Match on code, not on this.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
status
string
requiredopen until somebody answers the break, resolved once they have, with resolved_by and reason_code saying who and why. A break is never resolved automatically.
openresolvedresolved_by
string · nullable
optionalWho resolved the break, as the identifier of the person who did. null while the break is still open: it is never resolved automatically, so this is always somebody's decision.
reason_code
string · nullable
optionalThe reason recorded against the break, as a code you can match on, such as no_payee_destination. null while none has been recorded.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional)
const result = await api.payoutsExceptionsList({ reconciliationId: '{reconciliation_id}', limit: 25 });// query parameters: limit (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: limit (optional)
result = api.payouts_exceptions_list(reconciliation_id='{reconciliation_id}', limit=25)import os
import requests
# query parameters: limit (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional)
$result = $api->payoutsExceptionsList(reconciliation_id: '{reconciliation_id}', limit: 25);<?php
// query parameters: limit (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: limit (optional)
var result = api.payoutsExceptionsList("{reconciliation_id}", 25);// query parameters: limit (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: limit (optional)
var result = api.PayoutsExceptionsList(reconciliationId: "{reconciliation_id}", limit: 25);// query parameters: limit (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional)
result, _, err := client.ROUTEAPI.PayoutsExceptionsList(ctx, "{reconciliation_id}").Limit(25).Execute()// query parameters: limit (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/reconciliations/%7Breconciliation_id%7D/exceptions?limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "reconciliation_exception",
"code": "missing_in_ledger",
"detail": "The payee has no verified destination, so this line cannot be paid.",
"amount": {
"amount": 7500000,
"currency": "NGN"
},
"status": "open",
"reconciliation_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"resolved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reason_code": "no_payee_destination"
}
],
"has_more": true
}
/v1/payouts/readiness#What ROUTE needs before it moves money
payouts.readiness.retrieve
ROUTE depends on facts other parts of the pipeline own, and this call lists the ones it asks for. So a dependency on another part of the pipeline is something your integration can plan for from the start, and not something you discover at the first run.
Send no parameters. You get one rows entry per fact ROUTE requires, each carrying the module that owns it when you run integrated and the assurance that fact has to reach, plus always_enforced beside them.
ROUTE isn't sold on its own. It moves money, so live access follows the partner bank's own onboarding rules, and that isn't anything you can settle here.
Returns
Every fact ROUTE requires, with the module that owns it, so you can plan the dependencies.
object
always "readiness_report"
requiredAlways readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.
module
string
requiredThe module whose readiness endpoint you called, such as GET /v1/payroll/readiness: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance) or route (payouts).
anchorproofrailflowmatchrunremitroutemode
string
requiredintegrated means a required fact comes from the Droomwork module that owns it; standalone means you supply it yourself under an attestation. The checks are the same in both, and what each fact is worth is recorded on its row as held.
integratedstandaloneready
boolean
requiredtrue when every row is satisfied and the module has what it needs from you. false when a required fact is missing or held at too low an assurance; rows says which.
rows
array of ReadinessRow
requiredOne row per fact the module requires: who owns it, the assurance it needs, what you hold and whether that satisfies it. The rows that are not satisfied are what to bring.
8 fields of ReadinessRow
fact
string
requiredNamed for what it is, not for who supplies it.
owner
string
requiredThe sibling module that owns this fact when running integrated.
contract_row
string
optionalrequired
string
requiredHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedheld
one of
requiredAssuranceorsource
string · nullable
optionalintegratedstandalonenullsatisfied
boolean
requiredmissing_because
string · nullable
optionalnot_suppliedassurance_too_lownullalways_enforced
array of string
optionalGates that hold in every mode and cannot be attested away. Consent before any source is queried, bank account validation, the tax identifiers returns are filed under, and duplicate detection within the organisation's own population.
rails_without_credentials
array of string
optionalThe rails you hold no credentials for, each one a rail from GET /v1/payouts/rails whose credentials_held is false. Empty when you hold credentials for every rail.
partner_bank_onboarding
string
optionalWhere you stand in the partner bank's onboarding: not_started, in_progress (under way with the bank) or complete (the bank has finished onboarding you). Live access follows the bank's own rules, and ROUTE isn't sold on its own.
not_startedin_progresscompleteOther responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/readiness" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsReadinessRetrieve({});const response = await fetch('https://sandbox.droomwork.io/v1/payouts/readiness', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_readiness_retrieve()import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/readiness',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsReadinessRetrieve();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/readiness');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsReadinessRetrieve();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/readiness"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsReadinessRetrieve();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/readiness");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsReadinessRetrieve(ctx).Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/readiness", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "readiness_report",
"module": "route",
"mode": "integrated",
"ready": true,
"rows": [
{
"fact": "signed_payout_instruction",
"owner": "run",
"contract_row": "6.3.1",
"required": "verified",
"held": "verified",
"source": "integrated",
"satisfied": true,
"missing_because": null
},
{
"fact": "payee_destination",
"owner": "anchor",
"contract_row": "6.3.1",
"required": "verified",
"held": "verified",
"source": "integrated",
"satisfied": true,
"missing_because": null
}
],
"always_enforced": [
"consent_token",
"bank_account_validation",
"tax_identifier_validation",
"duplicate_detection"
],
"rails_without_credentials": [],
"partner_bank_onboarding": "not_started"
}
/v1/payouts/instructions/{instruction_id}/execute#Execute a planned instruction
payouts.instructions.execute
This is the call that sends the payments out. Everything before it is preparation, so your product has one exact moment to record as the moment a payday went, and a state to show for it.
Send the instruction's id in the path and no request body. You get the instruction back in executing, which is where it stays until an outcome is recorded against it.
The instruction has to be in planned, which POST /v1/payouts/instructions/{instruction_id}/plan leaves it in. One in any other state answers conflict.
Two calls take it on from here: POST /v1/payouts/instructions/{instruction_id}/resolve once the rail has answered, and POST /v1/payouts/instructions/{instruction_id}/mark_unknown while it has not. A payday that cannot be paid at all closes at POST /v1/payouts/instructions/{instruction_id}/fail.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The instruction in executing, which is the state its payments go out under.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsExecute({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_execute(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsExecute(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsExecute("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsExecute(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsExecute(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "executing",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/fail#Close an instruction that cannot be paid
payouts.instructions.fail
This closes an instruction that cannot go through at all, as a decision somebody took and recorded. So your product has a final state for a payday that stopped, and the stop is on the record as a stop.
Send the instruction's id in the path and no request body. You get the instruction back in failed, carrying the total and line_count it arrived with.
The instruction has to be in executing or unknown, the same two states resolve accepts. One in received or planned answers conflict, and no call abandons an instruction before it has executed: received accepts only planning, and planned only executing.
Nothing is retried from here and nothing leads out of failed. Where the rail has not answered yet, record that instead at POST /v1/payouts/instructions/{instruction_id}/mark_unknown, and close it at POST /v1/payouts/instructions/{instruction_id}/resolve once you know.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The instruction in failed, which is the end of it.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsFail({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_fail(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsFail(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsFail("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsFail(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsFail(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fail", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "failed",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/mark_unknown#Record that a rail has not answered
payouts.instructions.mark_unknown
This separates a payday whose sending is over from one still going out. An instruction left in executing reads as still working; moving it here says the sending finished and the answer is missing, so a screen in your product can show the two apart.
Send the instruction's id in the path and no request body. You get the instruction back in unknown, carrying the total and line_count it arrived with.
The instruction has to be in executing, which POST /v1/payouts/instructions/{instruction_id}/execute leaves it in. One in any other state answers conflict.
Two calls lead out of unknown: POST /v1/payouts/instructions/{instruction_id}/resolve once you hold the rail's answer, and POST /v1/payouts/instructions/{instruction_id}/fail where the money did not move.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The instruction in unknown, which says its outcome is not established yet.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsMarkUnknown({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_mark_unknown(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsMarkUnknown(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsMarkUnknown("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsMarkUnknown(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsMarkUnknown(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unknown", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "unknown",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/plan#Plan the route for an instruction
payouts.instructions.plan
Planning is where an instruction is given its rails: which one to try first, and which to fall back to. So your product can see the route a payday will take before any money is sent, and keep that record for afterwards.
Send the instruction's id in the path and no request body. You get the instruction back in planned, and not the route plan itself.
The instruction has to be in received, which is where POST /v1/payouts/instructions leaves it. One in any later state answers conflict.
Planning sends nothing. Read the route it chose at GET /v1/payouts/instructions/{instruction_id}/route_plan, then send the payments at POST /v1/payouts/instructions/{instruction_id}/execute, the only transition planned accepts.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The instruction in planned, with its rails chosen and nothing sent yet.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsPlan({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_plan(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsPlan(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsPlan("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsPlan(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsPlan(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/plan", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "planned",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/instructions/{instruction_id}/resolve#Settle an instruction once you know the outcome
payouts.instructions.resolve
This is the call that closes a payday as paid. It is the only one that reaches settled, so a period your product shows as complete is one somebody recorded as complete.
Send the instruction's id in the path and no request body. You get the instruction back in settled, carrying the total and line_count it arrived with.
The instruction has to be in executing or unknown. One still in received or planned answers conflict.
Resolve on the answer you hold, never on the time that has passed. Where there is no answer yet, POST /v1/payouts/instructions/{instruction_id}/mark_unknown is the state for it.
Nothing leads out of settled. Where the money did not move, POST /v1/payouts/instructions/{instruction_id}/fail is the close instead.
Path parameters
instruction_id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_: the id returned by POST /v1/payouts/instructions when you submitted it, or of one listed at GET /v1/payouts/instructions.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The instruction in settled, with the outcome recorded against it.
id
string
requiredThe instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.
object
always "payout_instruction"
requiredAlways payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredunknown is a real state and not a failure. An instruction whose rail did not answer is resolved by asking what happened, never by assuming the money did not move. Treating unknown as failed is how someone gets paid twice.
receivedquarantinedfunding_pendingplannedexecutingunknownsettledpartially_settledfailedsource
string
requiredWhich of these signed the amounts. You never supply an amount yourself.
runremitrun_id
string · nullable
optionalThe payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.
remittance_id
string · nullable
optionalThe remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
line_count
integer · minimum 1
requiredHow many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.
idempotency_key
string
optionalDerived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.
signature_verified
boolean
optionaltrue when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.
quarantine_reason
string · nullable
optionalWhy the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.
signature_invalidduplicate_instructionintegrity_mismatchnullroute_plan_id
string · nullable
optionalThe plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsInstructionsResolve({ instructionId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_instructions_resolve(instruction_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsInstructionsResolve(instruction_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsInstructionsResolve("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsInstructionsResolve(instructionId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsInstructionsResolve(ctx, "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payouts/instructions/route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/resolve", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payout_instruction",
"livemode": false,
"mocked": true,
"status": "settled",
"source": "run",
"total": {
"amount": 57900000,
"currency": "NGN"
},
"line_count": 1,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"remittance_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"idempotency_key": "example",
"signature_verified": true,
"quarantine_reason": "signature_invalid",
"route_plan_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payouts/events#Replay what happened to one payout instruction
payouts.events.list
ROUTE's own append only record of the payout facts it is the authority for, oldest first. Reading it back is how a consumer that was offline, or one you are writing today against money that moved last month, works through the same facts a webhook delivery carries, in the order they happened.
Pass stream with after and you get that stream's events after the sequence you name, where 0 reads from the start. Leave stream out and you get every stream together, in the order the events were recorded.
A sequence counts inside one stream and never across streams, so store one number per stream and send it with the stream it belongs to. after on its own has nothing to count from.
Read one event in full at GET /v1/payouts/events/{event_id}.
Query parameters
stream
string
optionalThe id of the instruction or payout to replay, from POST /v1/payouts/instructions or GET /v1/payouts/payouts (route_multi_rail_payout_…), with after beside it. Leave it out to read every stream, paged with starting_after.
after
integer
optionalThe sequence of the last event you handled on that stream, as sequence reads on each event; you get the events after it, and 0 reads from the start. Needs stream beside it, because a sequence counts within one stream.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of events, oldest first; has_more says whether to ask again.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of StoredEvent
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of StoredEvent
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe id of the record this event is about, an instruction's or a payout's (route_multi_rail_payout_…); pass it as stream to GET /v1/payouts/events to replay it. sequence counts per organisation and per stream, never across them.
data
object
requiredThe record the event is about, in the shape its type names: an instruction, a funding check, a payout, a rail or a reconciliation exception. The same body a webhook delivery carries.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const result = await api.payoutsEventsList({ stream: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25 });// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result = api.payouts_events_list(stream='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after=0, limit=25)import os
import requests
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$result = $api->payoutsEventsList(stream: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25);<?php
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.payoutsEventsList("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 0, 25, null);// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.PayoutsEventsList(stream: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", after: 0, limit: 25);// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result, _, err := client.ROUTEAPI.PayoutsEventsList(ctx).Stream("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").After(0).Limit(25).Execute()// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/events?stream=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": false,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
],
"has_more": true
}
/v1/payouts/events/{event_id}#Confirm a payout fact you were sent
payouts.events.retrieve
One event out of ROUTE's record, read back in full. An event's id never changes and a redelivery carries the same one, so a handler that is not sure whether it has already acted on a payout can settle that here.
Send the identifier from a delivery you received or from GET /v1/payouts/events; it starts with evt_. You get that one event, the stream it belongs to, the sequence it sits at there, and the record the event was about.
Those last two put you back into a replay: send them to GET /v1/payouts/events as stream and after. An event_id nothing holds answers not_found.
Path parameters
event_id
string
requiredThe event's identifier, from the id of an event you listed at GET /v1/payouts/events or received on a webhook delivery. It starts with evt_.
Returns
The one event as it was recorded, with the stream and sequence to replay from.
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe id of the record this event is about, an instruction's or a payout's (route_multi_rail_payout_…); pass it as stream to GET /v1/payouts/events to replay it. sequence counts per organisation and per stream, never across them.
data
object
requiredThe record the event is about, in the shape its type names: an instruction, a funding check, a payout, a rail or a reconciliation exception. The same body a webhook delivery carries.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsEventsRetrieve({ eventId: '{event_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_events_retrieve(event_id='{event_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsEventsRetrieve(event_id: '{event_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsEventsRetrieve("{event_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsEventsRetrieve(eventId: "{event_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsEventsRetrieve(ctx, "{event_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/events/%7Bevent_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": false,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
/v1/payouts/audit_entries#Who moved money, and who was turned down
payouts.audit_entries.list
Every attempt on your payout records, who made it, and how it ended. So your product can answer who sent an instruction for execution, or who resolved a payout, without keeping a log of its own.
You get entries newest first. Narrow them to one caller with actor_id, to one record with resource_id, to the refusals alone with outcome, or to a window of time.
action is matched exactly against what the entry stored. Something close is not a match: you get an empty page and no refusal, so check the spelling before you believe the log is empty.
A read of yours that worked leaves no row here. What you find is the changes, and the attempts that were turned away.
Read one in full at GET /v1/payouts/audit_entries/{audit_entry_id}.
Query parameters
action
string
optionalRestricts the list to attempts at one route, as action reads on an entry: the method and route pattern, such as POST /v1/payouts/instructions. Leave it out to get attempts at every route.
actor_id
string
optionalOnly one actor's attempts, matched on the exact actor_id of its entries: the id of the API key (key_…), person (usr_…) or staff account that made them. Add outcome=refused to see repeated refusals; leave it out for every actor.
resource
string
optionalOnly attempts on one kind of record, as resource reads on an entry: the collection segment of the route, such as instructions, payouts or settlements. Pair it with resource_id to narrow to one record; leave it out for every kind.
resource_id
string
optionalOnly attempts on one record, as resource_id reads on an entry: the id the route named, such as an instruction's from POST /v1/payouts/instructions (route_multi_rail_payout_…). Pair it with resource; leave it out for every record.
outcome
string
optionalRestricts the list to one outcome: succeeded (carried out), refused (you were turned down with a 4xx) or failed (it went wrong on our side, a 5xx). Leave it out to get all three.
succeededrefusedfailedrecorded_after
string
optionalOnly entries recorded after this moment, as an RFC 3339 timestamp in UTC such as 2026-01-01T00:00:00Z; an entry at exactly this instant is left out. Leave it out to go back to the oldest entry.
recorded_before
string
optionalOnly entries recorded before this moment, as an RFC 3339 timestamp in UTC such as 2027-01-01T00:00:00Z; an entry at exactly this instant is left out. Pair it with recorded_after to bound a window, or leave it out to read up to the newest.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of attempts, newest first, so your audit screen opens on what just happened.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of AuditEntry
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of AuditEntry
id
string
requiredThe entry's identifier, as GET /v1/payouts/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/payouts/audit_entries/{audit_entry_id}, or as starting_after to page past it.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredWhether the route this attempt was aimed at is mocked. It describes the route, not the answer: a refused request and a replayed idempotent request both count as attempts against a mocked route, and the log says so.
at
string · date-time
requiredWhen the attempt was made, as an RFC 3339 timestamp in UTC. Filter on it with recorded_after and recorded_before when you list.
request_id
string
requiredThe identifier of the request that made the attempt. It's the same request_id an error body carries, so you can match a refusal here to the response you were given.
actor_type
string
requiredWhat kind of caller made the attempt: client for your API key or OAuth client, user for a signed-in person, staff for a Droomwork staff member on a support case, service when we acted for you.
actor_id
string
requiredWho made the attempt, as the id of what actor_type names: the API key's (key_…) or client's for client, the person's (usr_…) for user. Pass it as actor_id to GET /v1/payouts/audit_entries to follow one actor.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection in the route: instructions, payouts, settlements and so on. Null when the route names none.
resource_id
string · nullable
optionalThe id of the one record the route named, such as an instruction's (route_multi_rail_payout_…, from POST /v1/payouts/instructions). null for an attempt on a collection, such as creating an instruction.
outcome
string
requiredHow the attempt ended: succeeded means it was carried out, refused means you were turned away with a 4xx, failed means we answered with a 5xx. status carries the exact code.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. Read it with outcome to tell one refusal from another.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const result = await api.payoutsAuditEntriesList({ action: 'POST /v1/payouts/instructions', actorId: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'instructions', resourceId: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recordedAfter: '2026-01-01T00:00:00Z', recordedBefore: '2027-01-01T00:00:00Z', limit: 25 });// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result = api.payouts_audit_entries_list(action='POST /v1/payouts/instructions', actor_id='usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource='instructions', resource_id='route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome='succeeded', recorded_after='2026-01-01T00:00:00Z', recorded_before='2027-01-01T00:00:00Z', limit=25)import os
import requests
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$result = $api->payoutsAuditEntriesList(action: 'POST /v1/payouts/instructions', actor_id: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'instructions', resource_id: 'route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recorded_after: '2026-01-01T00:00:00Z', recorded_before: '2027-01-01T00:00:00Z', limit: 25);<?php
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.payoutsAuditEntriesList("POST /v1/payouts/instructions", "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "instructions", "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "succeeded", "2026-01-01T00:00:00Z", "2027-01-01T00:00:00Z", 25, null);// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.PayoutsAuditEntriesList(action: "POST /v1/payouts/instructions", actorId: "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", resource: "instructions", resourceId: "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", outcome: "succeeded", recordedAfter: "2026-01-01T00:00:00Z", recordedBefore: "2027-01-01T00:00:00Z", limit: 25);// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result, _, err := client.ROUTEAPI.PayoutsAuditEntriesList(ctx).Action("POST /v1/payouts/instructions").ActorId("usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Resource("instructions").ResourceId("route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Outcome("succeeded").RecordedAfter("2026-01-01T00:00:00Z").RecordedBefore("2027-01-01T00:00:00Z").Limit(25).Execute()// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/audit_entries?action=POST%20%2Fv1%2Fpayouts%2Finstructions&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=instructions&resource_id=route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": false,
"mocked": true,
"at": "2026-09-01T09:00:00Z",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"actor_type": "example",
"actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"action": "example",
"outcome": "succeeded",
"status": 1,
"resource": "example",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"has_more": true
}
/v1/payouts/audit_entries/{audit_entry_id}#Tie one attempt to its request
payouts.audit_entries.retrieve
An audit entry is one attempt to change something, or one that was refused, and this reads one back. It names the request that made the attempt, so a refusal your integration recorded and the row we hold for it can be matched to each other.
Put the entry's id in the path. One entry comes back: the moment it happened, who was behind it, the route they aimed at, and the status they were answered with.
An entry stops at the attempt. It does not carry what changed, and it does not carry the reason behind a refusal; for what happened to the instruction itself, read GET /v1/payouts/events.
Where resource_id is set, open that record, such as an instruction at GET /v1/payouts/instructions/{instruction_id}. An audit_entry_id nothing holds answers not_found.
Path parameters
audit_entry_id
string
requiredThe entry's identifier, from the id of an entry you listed at GET /v1/payouts/audit_entries. It starts with audit_entry_.
Returns
One attempt in full, with the request behind it and the status it was answered with.
id
string
requiredThe entry's identifier, as GET /v1/payouts/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/payouts/audit_entries/{audit_entry_id}, or as starting_after to page past it.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredWhether the route this attempt was aimed at is mocked. It describes the route, not the answer: a refused request and a replayed idempotent request both count as attempts against a mocked route, and the log says so.
at
string · date-time
requiredWhen the attempt was made, as an RFC 3339 timestamp in UTC. Filter on it with recorded_after and recorded_before when you list.
request_id
string
requiredThe identifier of the request that made the attempt. It's the same request_id an error body carries, so you can match a refusal here to the response you were given.
actor_type
string
requiredWhat kind of caller made the attempt: client for your API key or OAuth client, user for a signed-in person, staff for a Droomwork staff member on a support case, service when we acted for you.
actor_id
string
requiredWho made the attempt, as the id of what actor_type names: the API key's (key_…) or client's for client, the person's (usr_…) for user. Pass it as actor_id to GET /v1/payouts/audit_entries to follow one actor.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection in the route: instructions, payouts, settlements and so on. Null when the route names none.
resource_id
string · nullable
optionalThe id of the one record the route named, such as an instruction's (route_multi_rail_payout_…, from POST /v1/payouts/instructions). null for an attempt on a collection, such as creating an instruction.
outcome
string
requiredHow the attempt ended: succeeded means it was carried out, refused means you were turned away with a 4xx, failed means we answered with a 5xx. status carries the exact code.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. Read it with outcome to tell one refusal from another.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, ROUTEApi } from '@droomwork/sdk';
const api = new ROUTEApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payoutsAuditEntriesRetrieve({ auditEntryId: '{audit_entry_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.ROUTEApi(client)
result = api.payouts_audit_entries_retrieve(audit_entry_id='{audit_entry_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\ROUTEApi(new GuzzleHttp\Client(), $config);
$result = $api->payoutsAuditEntriesRetrieve(audit_entry_id: '{audit_entry_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RouteApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RouteApi api = new RouteApi(client);
var result = api.payoutsAuditEntriesRetrieve("{audit_entry_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new ROUTEApi(config);
var result = api.PayoutsAuditEntriesRetrieve(auditEntryId: "{audit_entry_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.ROUTEAPI.PayoutsAuditEntriesRetrieve(ctx, "{audit_entry_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/audit_entries/%7Baudit_entry_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": false,
"mocked": true,
"at": "2026-09-01T09:00:00Z",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"actor_type": "example",
"actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"action": "example",
"outcome": "succeeded",
"status": 1,
"resource": "example",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}