DDroomwork Developers

Version 1.0.0

Droomwork ROUTE MULTI-RAIL

Disbursement across Nigerian payment rails, with failover and proof.

Base URLhttps://sandbox.droomwork.io
AuthenticationDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…
Events it sends8 webhooks →
Signed in: requests you run on this page use a key made for your account. Your keys →

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?

GET/v1/payouts/instructions#

List payout instructions

payouts.instructions.list

Your instructions, newest first.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Return 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_pendingplannedexecutingsettledpartially_settledfailed
run_id string optional

Return 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

A page of instructions.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of PayoutInstruction required

The records on this page, in the order the list promises. Empty when nothing matched.

15 fields of PayoutInstruction
id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "payout_instruction",
      "livemode": true,
      "mocked": true,
      "status": "received",
      "source": "run",
      "total": {
        "amount": 1234567,
        "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
}
POST/v1/payouts/instructions#

Submit a signed payout instruction

payouts.instructions.create

We check the signature, the derived idempotency key and the integrity of every line before accepting. A tampered or duplicate instruction is quarantined, not executed, and it stays in quarantine: a tampered instruction is evidence.

Amounts arrive final. Nothing here computes one.

Headers

Idempotency-Key string required

A 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 required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string optional

The 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 optional

The 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 required

The signature from payroll or remittance. We verify it before accepting anything.

lines array of object required

One 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 required

Who 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

payslip_line_id string optional

The 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 optional

Each part settles independently as its own sub-leg.

2 fields
destination_ref string required

Which 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

Returns

The instruction, received and awaiting a funding check.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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":1234567,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":1234567,"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":1234567,"currency":"NGN"},"payslipLineId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destinationRef":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":1234567,"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": 1234567,
          "currency": "NGN"
        },
        "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
        "split": [
          {
            "destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
            "amount": {
              "amount": 1234567,
              "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": 1234567, "currency": "NGN"}, "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "split": [{"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 1234567, "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": 1234567, "currency": "NGN"}, "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "split": [{"destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "amount": {"amount": 1234567, "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":1234567,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":1234567,"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":1234567,"currency":"NGN"},"payslip_line_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","split":[{"destination_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","amount":{"amount":1234567,"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": 1234567,
                "currency": "NGN"
              },
              "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
              "split": [
                {
                  "destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
                  "amount": {
                    "amount": 1234567,
                    "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": 1234567,
            "currency": "NGN"
          },
          "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "split": [
            {
              "destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
              "amount": {
                "amount": 1234567,
                "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": 1234567,
        "currency": "NGN"
      },
      "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "split": [
        {
          "destination_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "amount": {
            "amount": 1234567,
            "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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
GET/v1/payouts/instructions/{instruction_id}#

Retrieve a payout instruction

payouts.instructions.retrieve

The instruction, with its resolved destinations and current state.

Path parameters

instruction_id string required

The 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 instruction.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
GET/v1/payouts/instructions/{instruction_id}/route_plan#

Retrieve the route plan for an instruction

payouts.instructions.retrieve_route_plan

The plan names a primary rail and at least one fallback. It's computed and stored before execution, never decided at runtime, so you can reconstruct a failover afterwards.

Path parameters

instruction_id string required

The 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 stored plan.

id string required

The 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" required

Always route_plan. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

The 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 required

The 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 optional

Why this order, kept so you can audit the plan.

max_attempts integer · minimum 1 optional

How 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/v1/payouts/instructions/{instruction_id}/funding_check#

Retrieve the funding check for an instruction

payouts.funding_checks.retrieve

Whether your account holds enough to cover the instruction in full.

Path parameters

instruction_id string required

The 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.

object always "funding_check" required

Always funding_check. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

true 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

available_amount Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

shortfall Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

checked_at string · date-time optional

When 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "object": "funding_check",
  "instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "sufficient": true,
  "required_amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "available_amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "shortfall": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "checked_at": "2026-09-01T09:00:00Z"
}
POST/v1/payouts/instructions/{instruction_id}/funding_check#

Confirm funding before any rail is attempted

payouts.funding_checks.create

Run this before any rail is attempted. If the account can't cover the instruction in full, you're told and nothing executes. No part of the file is paid: a partly paid payroll is worse than one that didn't start.

Path parameters

instruction_id string required

The 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 required

A 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 funding check result.

object always "funding_check" required

Always funding_check. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

true 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

available_amount Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

shortfall Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

checked_at string · date-time optional

When 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "object": "funding_check",
  "instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "sufficient": true,
  "required_amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "available_amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "shortfall": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "checked_at": "2026-09-01T09:00:00Z"
}
GET/v1/payouts/route_plans#

List stored route plans

payouts.route_plans.list

Every route plan we've stored. A plan is computed and stored before execution, so the plan behind any payout is here when you need it in an audit.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Return 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

A page of route plans.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of RoutePlan required

The records on this page, in the order the list promises. Empty when nothing matched.

8 fields of RoutePlan
id string required

The 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" required

Always route_plan. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

The 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 required

The 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 optional

Why this order, kept so you can audit the plan.

max_attempts integer · minimum 1 optional

How 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
POST/v1/payouts/route_plans#

Create a route plan

payouts.route_plans.create

A primary rail and the fallbacks to try when it won't carry the payment. You normally get one by planning an instruction. Create one directly to record a routing decision you made outside Droomwork against a payout.

Headers

Idempotency-Key string required

A 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 optional

The rail to try first.

fallbacks array of string optional

Rails to try in order when the primary will not carry the payment.

Returns

The route plan.

id string required

The 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" required

Always route_plan. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

The 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 required

The 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 optional

Why this order, kept so you can audit the plan.

max_attempts integer · minimum 1 optional

How 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/v1/payouts/route_plans/{route_plan_id}#

Retrieve a route plan

payouts.route_plans.retrieve

The plan as it was stored, with the reasoning that produced it.

Path parameters

route_plan_id string required

The 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

The route plan.

id string required

The 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" required

Always route_plan. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The 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 required

The 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 required

The 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 optional

Why this order, kept so you can audit the plan.

max_attempts integer · minimum 1 optional

How 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/v1/payouts/rails#

List payment rails and their current health

payouts.rails.list

Instant transfer, direct bank integrations, wallets and mobile money, behind one interface. A degraded rail never delays a payout that doesn't need it.

Query parameters

limit integer optional

How 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 optional

Return 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.

healthydegradedunavailable

Returns

A page of rails.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Rail required

The records on this page, in the order the list promises. Empty when nothing matched.

7 fields of Rail
id string required

The 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" required

Always rail. Tells you which kind of record you are looking at, so one handler can read any response.

name string required

The rail's name as people know it, such as NIP instant transfer. For display; name the rail by id in a call.

kind string required

What 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_money
health string required
healthydegradedunavailable
capabilities object optional

What 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 optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

supports_name_enquiry boolean optional

true 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 optional

How soon money sent on this rail lands, in words such as near instant, same day or next business day.

credentials_held boolean optional

Whether 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "rail",
      "name": "NIP instant transfer",
      "kind": "instant_transfer",
      "health": "healthy",
      "capabilities": {
        "max_amount": {
          "amount": 1234567,
          "currency": "NGN"
        },
        "supports_name_enquiry": true,
        "settlement_window": "near instant"
      },
      "credentials_held": true
    }
  ],
  "has_more": true
}
GET/v1/payouts/rails/{rail_id}#

Retrieve a rail

payouts.rails.retrieve

The rail with its capabilities, limits and current health.

Path parameters

rail_id string required

The 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 rail.

id string required

The 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" required

Always rail. Tells you which kind of record you are looking at, so one handler can read any response.

name string required

The rail's name as people know it, such as NIP instant transfer. For display; name the rail by id in a call.

kind string required

What 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_money
health string required
healthydegradedunavailable
capabilities object optional

What 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 optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

supports_name_enquiry boolean optional

true 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 optional

How soon money sent on this rail lands, in words such as near instant, same day or next business day.

credentials_held boolean optional

Whether 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "rail",
  "name": "NIP instant transfer",
  "kind": "instant_transfer",
  "health": "healthy",
  "capabilities": {
    "max_amount": {
      "amount": 1234567,
      "currency": "NGN"
    },
    "supports_name_enquiry": true,
    "settlement_window": "near instant"
  },
  "credentials_held": true
}
GET/v1/payouts/payouts#

List individual payouts

payouts.payouts.list

One payout per payee, or per sub-leg where an instruction splits across destinations. Each settles on its own.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Return 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 optional

Return 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.

pendingattemptingunknownsettledfailed

Returns

A page of payouts.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Payout required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of Payout
id string required

The 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" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Who 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

status string required

unknown 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.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved 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 required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The 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 required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The 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 optional

true 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 optional

How 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 optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "payout",
      "livemode": true,
      "mocked": true,
      "instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "amount": {
        "amount": 1234567,
        "currency": "NGN"
      },
      "status": "pending",
      "destination": {
        "bank_code": "no_payee_destination",
        "account_number_masked": "******4821",
        "account_name": "Rivers State Internal Revenue Service",
        "bank_name": "Rivers State Internal Revenue Service",
        "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
}
GET/v1/payouts/payouts/{payout_id}#

Retrieve a payout

payouts.payouts.retrieve

The payout with every attempt made against it. A payout in unknown has timed out and we're asking the provider what happened.

Path parameters

payout_id string required

The 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 payout.

id string required

The 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" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Who 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

status string required

unknown 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.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved 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 required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The 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 required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The 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 optional

true 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 optional

How 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 optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout",
  "livemode": true,
  "mocked": true,
  "instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "status": "pending",
  "destination": {
    "bank_code": "no_payee_destination",
    "account_number_masked": "******4821",
    "account_name": "Rivers State Internal Revenue Service",
    "bank_name": "Rivers State Internal Revenue Service",
    "validated": true
  },
  "attempt_count": 0,
  "unknown_since": "2026-09-01T09:00:00Z",
  "settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/payouts/payouts/{payout_id}/resolve#

Ask the provider what happened to an unknown payout

payouts.payouts.resolve

Ask the provider now rather than waiting for the scheduled poll. The answer comes from the provider. Nothing is assumed.

Path parameters

payout_id string required

The 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 required

A 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, resolved or still unknown if the provider has not said.

id string required

The 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" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Who 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

status string required

unknown 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.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved 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 required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The 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 required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The 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 optional

true 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 optional

How 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 optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout",
  "livemode": true,
  "mocked": true,
  "instruction_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "status": "pending",
  "destination": {
    "bank_code": "no_payee_destination",
    "account_number_masked": "******4821",
    "account_name": "Rivers State Internal Revenue Service",
    "bank_name": "Rivers State Internal Revenue Service",
    "validated": true
  },
  "attempt_count": 0,
  "unknown_since": "2026-09-01T09:00:00Z",
  "settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/payouts/payouts/{payout_id}/attempts#

List the attempts made against a payout

payouts.attempts.list

Every attempt, with its rail, its outcome and its timing. Failover to the fallback rail fires within 15 seconds at the 95th percentile of a failure or timeout signal, and these timings are your evidence of it.

Path parameters

payout_id string required

The 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 optional

How 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

A page of attempts.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Attempt required

The records on this page, in the order the list promises. Empty when nothing matched.

11 fields of Attempt
id string required

The 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" required

Always payout_attempt. Tells you which kind of record you are looking at, so one handler can read any response.

payout_id string required

The 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 required

The 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 optional

1 is the primary rail. Anything higher is a failover.

outcome string required
acceptedrejectedtimed_outprovider_error
provider_reference string · nullable optional

The 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 optional

What 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 required

When 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 optional

When 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 optional

Time 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/v1/payouts/payouts/{payout_id}/attempts/{attempt_id}#

Retrieve an attempt

payouts.attempts.retrieve

One attempt, with the rail it used and what the provider said.

Path parameters

payout_id string required

The 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 required

The 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

The attempt.

id string required

The 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" required

Always payout_attempt. Tells you which kind of record you are looking at, so one handler can read any response.

payout_id string required

The 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 required

The 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 optional

1 is the primary rail. Anything higher is a failover.

outcome string required
acceptedrejectedtimed_outprovider_error
provider_reference string · nullable optional

The 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 optional

What 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 required

When 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 optional

When 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 optional

Time 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
GET/v1/payouts/account_validations#

List account validations

payouts.account_validations.list

Your validations, including any name mismatch found.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Restricts 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_foundunresolved

Returns

A page of validations.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of AccountValidation required

The records on this page, in the order the list promises. Empty when nothing matched.

9 fields of AccountValidation
id string required

The 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" required

Always account_validation. Tells you which kind of record you are looking at, so one handler can read any response.

subject_id string required

Who 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 required
validname_mismatchaccount_not_foundunresolved
expected_name string · nullable optional

The 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 optional

What the bank holds. Compared with the expected name, and neither is a full account number.

similarity number · nullable optional

How close the two names are, from 0 to 1. A judgement aid, not a decision.

source string · nullable optional

Named when the outcome is unresolved, because silence is never a pass.

checked_at string · date-time optional

When 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "account_validation",
      "subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "outcome": "valid",
      "expected_name": "Rivers State Internal Revenue Service",
      "bank_name_returned": "example",
      "similarity": 1,
      "source": "example",
      "checked_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true
}
POST/v1/payouts/account_validations#

Validate destination accounts ahead of payday

payouts.account_validations.create

Send a batch: this is a sweep across your roster, not a check at payment time. Run it days before payday so a name mismatch is an errand rather than an incident.

This check can't be waived by attestation. It's the payment provider's requirement, and it applies whatever you've accepted.

Headers

Idempotency-Key string required

A 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 required

A sweep across a roster. Run it days before payday.

Returns

Validation accepted. Results arrive per subject.

object always "account_validation_batch" required

Always account_validation_batch. Tells you which kind of record you are looking at, so one handler can read any response.

id string required

The 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 required

How many people this sweep covers: the number of subject_ids you sent. Expect one validation per subject.

status string required

running 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.

runningcompleted
results_url string · uri optional

Where 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

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "object": "account_validation_batch",
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "subject_count": 1,
  "status": "running",
  "results_url": "https://files.sandbox.droomwork.com/example"
}
GET/v1/payouts/account_validations/{account_validation_id}#

Retrieve an account validation

payouts.account_validations.retrieve

The outcome. A name mismatch shows what the bank holds against what you expected, without echoing the full account number.

Path parameters

account_validation_id string required

The 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 validation.

id string required

The 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" required

Always account_validation. Tells you which kind of record you are looking at, so one handler can read any response.

subject_id string required

Who 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 required
validname_mismatchaccount_not_foundunresolved
expected_name string · nullable optional

The 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 optional

What the bank holds. Compared with the expected name, and neither is a full account number.

similarity number · nullable optional

How close the two names are, from 0 to 1. A judgement aid, not a decision.

source string · nullable optional

Named when the outcome is unresolved, because silence is never a pass.

checked_at string · date-time optional

When 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "account_validation",
  "subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "outcome": "valid",
  "expected_name": "Rivers State Internal Revenue Service",
  "bank_name_returned": "example",
  "similarity": 1,
  "source": "example",
  "checked_at": "2026-09-01T09:00:00Z"
}
GET/v1/payouts/settlements#

List settlements

payouts.settlements.list

Every settlement, paid or failed, maps back to the run, payslip and line it came from, including where only part of an instruction settled.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Restricts 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 optional

Restricts the page to one outcome: paid (the money arrived and was independently validated) or failed (it won't). Leave it out to get both.

paidfailed

Returns

A page of settlements.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Settlement required

The records on this page, in the order the list promises. Empty when nothing matched.

12 fields of Settlement
id string required

The 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" required

Always settlement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 required
paidfailed
amount Money required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

provider_reference string optional

The 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 required

Amount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.

origin object optional

The trail from a bank notification back to a payslip line.

4 fields
run_id string · nullable optional

The 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 optional

The 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 optional

The 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 optional

The 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 optional

When 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "settlement",
      "livemode": true,
      "mocked": true,
      "payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "outcome": "paid",
      "amount": {
        "amount": 1234567,
        "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
}
POST/v1/payouts/settlements#

Record a settlement

payouts.settlements.create

Confirmation from a rail that money arrived. Settlements usually reach us as webhooks from the rail. Record one directly when it was confirmed another way, a statement or a phone call, so it reaches reconciliation.

Headers

Idempotency-Key string required

A 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 optional

The 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.

id string required

The 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" required

Always settlement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 required
paidfailed
amount Money required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

provider_reference string optional

The 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 required

Amount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.

origin object optional

The trail from a bank notification back to a payslip line.

4 fields
run_id string · nullable optional

The 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 optional

The 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 optional

The 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 optional

The 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 optional

When the money arrived, as an RFC 3339 timestamp in UTC. null when there is no such moment, as on a failed settlement.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "settlement",
  "livemode": true,
  "mocked": true,
  "payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "outcome": "paid",
  "amount": {
    "amount": 1234567,
    "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"
}
GET/v1/payouts/settlements/{settlement_id}#

Retrieve a settlement

payouts.settlements.retrieve

A settlement is marked paid only after the amount, the destination and the reference are validated against the originating instruction. A rail reporting success isn't enough on its own.

Path parameters

settlement_id string required

The 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 settlement.

id string required

The 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" required

Always settlement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 required
paidfailed
amount Money required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

provider_reference string optional

The 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 required

Amount, destination and reference were checked against the originating instruction. paid is never set on the rail's word alone.

origin object optional

The trail from a bank notification back to a payslip line.

4 fields
run_id string · nullable optional

The 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 optional

The 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 optional

The 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 optional

The 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 optional

When the money arrived, as an RFC 3339 timestamp in UTC. null when there is no such moment, as on a failed settlement.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "settlement",
  "livemode": true,
  "mocked": true,
  "payout_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "outcome": "paid",
  "amount": {
    "amount": 1234567,
    "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"
}
GET/v1/payouts/settlements/{settlement_id}/receipt#

Retrieve the receipt for a settlement

payouts.receipts.retrieve

Immutable and hash chained, with links to the payroll and remittance trace references behind it. You get one for a failure as well as a success, because a failed payment is also something you have to prove.

Path parameters

settlement_id string required

The 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.

id string required

The 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" required

Always payout_receipt. Tells you which kind of record you are looking at, so one handler can read any response.

settlement_id string required

The 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 required
paidfailed
amount Money required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

hash string required

The 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 required

The 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 optional

The payroll and remittance traces this receipt is linked to.

issued_at string · date-time optional

When the receipt was issued and chained, as an RFC 3339 timestamp in UTC. From this moment it never changes.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_receipt",
  "settlement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "outcome": "paid",
  "amount": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "hash": "sha256:9f2c1e0043a1b8",
  "previous_hash": "sha256:9f2c1e0043a1b8",
  "trace_references": [
    "paye-2026-09-rivers"
  ],
  "issued_at": "2026-09-01T09:00:00Z"
}
GET/v1/payouts/reconciliations#

List daily reconciliations

payouts.reconciliations.list

Each day's provider statement checked against our record of what was paid.

Query parameters

limit integer optional

How 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 optional

Restricts 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 reconciliations.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Reconciliation required

The records on this page, in the order the list promises. Empty when nothing matched.

9 fields of Reconciliation
id string required

The 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" required

Always reconciliation. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

true 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 optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

ledger_total Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

break_count integer · minimum 0 required

How 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), on (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)" \
  -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: 'Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)' });
// query parameters: limit (optional), on (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)', {
  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='Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)')
import os

import requests

# query parameters: limit (optional), on (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)',
    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: 'Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)');
<?php
// query parameters: limit (optional), on (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)');
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, "Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)");
// 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=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)"))
    .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: "Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)");
// 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=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)");
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("Tue Sep 01 2026 01:00:00 GMT+0100 (West Africa Time)").Execute()
// query parameters: limit (optional), on (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payouts/reconciliations?limit=25&on=Tue%20Sep%2001%202026%2001%3A00%3A00%20GMT%2B0100%20(West%20Africa%20Time)", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "reconciliation",
      "livemode": true,
      "mocked": true,
      "on": "2026-09-01",
      "balanced": true,
      "break_count": 0,
      "provider_total": {
        "amount": 1234567,
        "currency": "NGN"
      },
      "ledger_total": {
        "amount": 1234567,
        "currency": "NGN"
      }
    }
  ],
  "has_more": true
}
GET/v1/payouts/reconciliations/{reconciliation_id}#

Retrieve a reconciliation

payouts.reconciliations.retrieve

The day, with every break it found.

Path parameters

reconciliation_id string required

The 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 reconciliation.

id string required

The 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" required

Always reconciliation. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

true 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 optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

ledger_total Money optional
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

break_count integer · minimum 0 required

How 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_settlement_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "reconciliation",
  "livemode": true,
  "mocked": true,
  "on": "2026-09-01",
  "balanced": true,
  "break_count": 0,
  "provider_total": {
    "amount": 1234567,
    "currency": "NGN"
  },
  "ledger_total": {
    "amount": 1234567,
    "currency": "NGN"
  }
}
GET/v1/payouts/reconciliations/{reconciliation_id}/exceptions#

List reconciliation exceptions

payouts.exceptions.list

A break is raised as an exception for you to answer, never resolved automatically. A difference between the provider and our records is a question, not something to paper over.

Path parameters

reconciliation_id string required

The 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 optional

How 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

A page of exceptions.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of ReconciliationException required

The records on this page, in the order the list promises. Empty when nothing matched.

9 fields of ReconciliationException
id string required

The 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" required

Always reconciliation_exception. Tells you which kind of record you are looking at, so one handler can read any response.

reconciliation_id string optional

The 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 required

Which 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_provider
detail string required

What the break is, in words, written for a person to read. Match on code, not on this.

amount Money required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

status string required

open 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.

openresolved
resolved_by string · nullable optional

Who 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 optional

The 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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": 1234567,
        "currency": "NGN"
      },
      "status": "open",
      "reconciliation_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "resolved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "reason_code": "no_payee_destination"
    }
  ],
  "has_more": true
}
GET/v1/payouts/readiness#

What ROUTE needs, what you have, and what is missing

payouts.readiness.retrieve

What ROUTE needs from you, what you hold, and what's missing. Unlike the other modules, this also reports the state of your rail credentials and partner bank onboarding.

ROUTE isn't sold on its own the way the other modules are. It moves money, so live access follows the partner bank's own onboarding rules, not anything you can settle here.

Returns

The readiness report.

object always "readiness_report" required

Always readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.

module string required

The 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).

anchorproofrailflowmatchrunremitroute
mode string required

integrated 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.

integratedstandalone
ready boolean required

true 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 required

One 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 required

Named for what it is, not for who supplies it.

owner string required

The sibling module that owns this fact when running integrated.

contract_row string optional
required string required

How 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.

attestedverified
held one of required
Assuranceor
source string · nullable optional
integratedstandalonenull
satisfied boolean required
missing_because string · nullable optional
not_suppliedassurance_too_lownull
always_enforced array of string optional

Gates 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 optional

The 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 optional

Where 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_progresscomplete

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "object": "readiness_report",
  "module": "anchor",
  "mode": "integrated",
  "ready": true,
  "rows": [
    {
      "fact": "anchored_subject",
      "owner": "anchor",
      "required": "attested",
      "held": "attested",
      "satisfied": true,
      "contract_row": "6.1.13",
      "source": "integrated",
      "missing_because": "not_supplied"
    }
  ],
  "always_enforced": [
    "example"
  ],
  "rails_without_credentials": [
    "example"
  ],
  "partner_bank_onboarding": "not_started"
}
POST/v1/payouts/instructions/{instruction_id}/execute#

Execute a planned instruction

payouts.instructions.execute

Sends every payout on the plan. Refused unless the instruction has been planned: an instruction with no route plan has nowhere to send anything.

Path parameters

instruction_id string required

The 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 required

A 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 its new state.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
POST/v1/payouts/instructions/{instruction_id}/fail#

Mark an instruction failed

payouts.instructions.fail

For an instruction that can't proceed at all, as distinct from one whose payouts failed individually. Nothing is retried from here.

Path parameters

instruction_id string required

The 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 required

A 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 its new state.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
POST/v1/payouts/instructions/{instruction_id}/mark_unknown#

Mark an instruction outcome unknown

payouts.instructions.mark_unknown

A timeout is not a failure and is never assumed to be one. An instruction in this state is resolved by asking the rail what happened, which is the resolve action. Never by guessing.

Path parameters

instruction_id string required

The 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 required

A 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 its new state.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
POST/v1/payouts/instructions/{instruction_id}/plan#

Plan the route for an instruction

payouts.instructions.plan

Chooses a rail per payout and produces the route plan. Refused unless funding is confirmed: a plan against money that isn't there can't run.

Path parameters

instruction_id string required

The 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 required

A 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 its new state.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
POST/v1/payouts/instructions/{instruction_id}/resolve#

Resolve an instruction of unknown outcome

payouts.instructions.resolve

Asks the rail what happened and records the answer. This is the only way out of the unknown state. A timeout is settled by evidence, never by assumption.

Path parameters

instruction_id string required

The 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 required

A 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 its new state.

id string required

The 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" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The 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 optional

The 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 required
2 fields of Money
amount integer · int64 required

A 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 required

ISO 4217 code.

line_count integer · minimum 1 required

How 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 optional

Derived 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 optional

true 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 optional

Why 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_mismatchnull
route_plan_id string · nullable optional

The 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 optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "route_multi_rail_payout_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "payout_instruction",
  "livemode": true,
  "mocked": true,
  "status": "received",
  "source": "run",
  "total": {
    "amount": 1234567,
    "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"
}
GET/v1/payouts/events#

List events

payouts.events.list

The append only record of what ROUTE is the authority for, oldest first. Every event here is one the webhook catalogue declares, so the code that handles a delivery handles a replay too.

Two ways to read it. Pass stream with after to replay one stream from the sequence you last handled. That's exact and needs no cursor: the sequence counts per organisation and per stream, so it's the only ordering that means anything. Without stream, you get events across streams in the order they were recorded, paged with starting_after.

Query parameters

stream string optional

The 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 optional

The 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 optional

How 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 optional

The 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.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of StoredEvent required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of StoredEvent
id string required

The 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 required

What 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 required

The 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 required

The 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 required

Per 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 required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The 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 required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether 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 required

Which 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.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The 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 required

The 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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": true,
      "mocked": true,
      "source": "anchor",
      "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "event",
      "stream": "example",
      "data": {}
    }
  ],
  "has_more": true
}
GET/v1/payouts/events/{event_id}#

Retrieve an event

payouts.events.retrieve

One event. An identifier from another organisation comes back not found rather than refused, because a refusal would confirm it exists.

Path parameters

event_id string required

The 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 event.

id string required

The 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 required

What 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 required

The 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 required

The 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 required

Per 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 required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The 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 required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether 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 required

Which 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.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The 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 required

The 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "type": "run.payslip.calculated",
  "schema_version": 1,
  "org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "sequence": 0,
  "occurred_at": "2026-09-01T09:00:00Z",
  "livemode": true,
  "mocked": true,
  "source": "anchor",
  "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "event",
  "stream": "example",
  "data": {}
}
GET/v1/payouts/audit_entries#

List audit entries

payouts.audit_entries.list

Who did what, newest first. One row per attempt rather than per success: the refusals are the half you'll want, because repeated forbidden answers on one credential is what an attack looks like from the inside.

A read that succeeded isn't recorded. A log holding every list call is mostly noise.

Query parameters

action string optional

Restricts 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 optional

Only 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 optional

Only 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 optional

Only 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 optional

Restricts 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.

succeededrefusedfailed
recorded_after string optional

Only 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 optional

Only 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 optional

How 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 optional

The 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 audit entries.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of AuditEntry required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of AuditEntry
id string required

The 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" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

Whether 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 required

When 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 required

The 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 required

What 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 required

Who 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 required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The 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 optional

The 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 required

How 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.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. Read it with outcome to tell one refusal from another.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "audit_entry",
      "livemode": true,
      "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
}
GET/v1/payouts/audit_entries/{audit_entry_id}#

Retrieve an audit entry

payouts.audit_entries.retrieve

One entry. An identifier from another organisation comes back not found rather than refused, because a refusal would confirm it exists.

Path parameters

audit_entry_id string required

The entry's identifier, from the id of an entry you listed at GET /v1/payouts/audit_entries. It starts with audit_entry_.

Returns

The audit entry.

id string required

The 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" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

Whether 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 required

When 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 required

The 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 required

What 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 required

Who 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 required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The 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 optional

The 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 required

How 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.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. Read it with outcome to tell one refusal from another.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "audit_entry",
  "livemode": true,
  "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"
}