Version 1.0.0
Droomwork RUN ENTERPRISE
Gross to net payroll calculation for Nigeria.
https://sandbox.droomwork.ioDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…RUN computes your payroll. Give it a roster and a set of pay profiles, pin a rule pack version, and you get payslips that replay to the kobo years later.
What you should know before you start
Money is whole minor units. An amount is an integer count of the currency's smallest unit plus the currency code. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. Send a fractional amount and it's refused, not rounded. Read how many minor units make one major unit from GET /v1/currencies; don't assume it's a hundred.
Every payslip line carries its own trace. The formula, the inputs, the exact value before rounding, the rounding policy applied and the rule pack version. You never get a line without one. Keep it; it's what you show when a figure is questioned.
A run moves through states, and you move it with endpoints. You can't execute a run that was never approved, and you can't approve one you prepared yourself above the configured threshold. There's no status field to set.
Two fields tell you what you're looking at. livemode says whether the record belongs to the live realm or the sandbox. mocked says whether the figures are stand-ins rather than a real calculation. They answer different questions and both appear on every record.
Getting started
Get a sandbox key from the developer portal. Create a run. Simulate it. Approve it. Execute it. The sandbox keeps state per account, so the run you create is the run you fetch back.
Every operation below has two samples: Direct HTTP, with nothing but your language's own client, and Client library, with ours. Same request, same answer; the switch above each sample picks one and remembers it. Which should I use?
/v1/payroll/runs#List payroll runs
payroll.runs.list
Your runs, newest first.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
status
string
optionalReturn only runs in this state: draft, simulated, pending_approval, approved, executing, completed, partially_settled (some payouts failed to settle), failed or cancelled. Leave it out to get runs in every state.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledperiod
string
optionalReturn only runs for this pay period, as year and month in YYYY-MM form, for example 2026-09. Leave it out to get runs for every period.
Returns
A page of runs.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Run
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
14 fields of Run
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
const result = await api.payrollRunsList({ limit: 25, status: 'draft', period: '2026-09' });// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09', {
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.RUNApi(client)
# query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
result = api.payroll_runs_list(limit=25, status='draft', period='2026-09')import os
import requests
# query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09',
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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
$result = $api->payrollRunsList(limit: 25, status: 'draft', period: '2026-09');<?php
// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09');
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.RunApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
var result = api.payrollRunsList(25, null, RunRunStatus.fromValue("draft"), "2026-09");// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09"))
.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 RUNApi(config);
// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
var result = api.PayrollRunsList(limit: 25, status: RunRunStatus.Draft, period: "2026-09");// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09");
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), period (optional)
result, _, err := client.RUNAPI.PayrollRunsList(ctx).Limit(25).Status(droomwork.RunRunStatus("draft")).Period("2026-09").Execute()// query parameters: limit (optional), starting_after (optional), status (optional), period (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs?limit=25&status=draft&period=2026-09", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payroll/runs#Create a payroll run
payroll.runs.create
Opens a run in draft. Choose the run type now; you can't change it afterwards.
The rule pack version is pinned now, not at execution. A pack published between drafting and running won't change your figures.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
period
string
requiredThe pay period the run covers, as year and month in YYYY-MM form, for example 2026-09. It decides which roster entries and which rule pack apply unless you name them.
run_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementrule_pack_version
string
optionalDefaults to the pack in force for the period.
roster_entry_ids
array of string
optionalDefaults to every payable roster entry for the period.
Returns
The run, in draft.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"period":"2026-09","run_type":"regular","rule_pack_version":"2026.08.1","roster_entry_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsCreate({
idempotencyKey: crypto.randomUUID(),
runRunCreateRequest: {"period":"2026-09","runType":"regular","rulePackVersion":"2026.08.1","rosterEntryIds":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"period": "2026-09",
"run_type": "regular",
"rule_pack_version": "2026.08.1",
"roster_entry_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.RUNApi(client)
result = api.payroll_runs_create(body={"period": "2026-09", "run_type": "regular", "rule_pack_version": "2026.08.1", "roster_entry_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"period": "2026-09", "run_type": "regular", "rule_pack_version": "2026.08.1", "roster_entry_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsCreate($idempotencyKey, json_decode('{"period":"2026-09","run_type":"regular","rule_pack_version":"2026.08.1","roster_entry_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs');
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 => '{"period":"2026-09","run_type":"regular","rule_pack_version":"2026.08.1","roster_entry_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs"))
.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("""
{
"period": "2026-09",
"run_type": "regular",
"rule_pack_version": "2026.08.1",
"roster_entry_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 RUNApi(config);
var result = api.PayrollRunsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"period": "2026-09",
"run_type": "regular",
"rule_pack_version": "2026.08.1",
"roster_entry_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.RUNAPI.PayrollRunsCreate(ctx).IdempotencyKey(key).RunRunCreateRequest(body).Execute()body := strings.NewReader(`{
"period": "2026-09",
"run_type": "regular",
"rule_pack_version": "2026.08.1",
"roster_entry_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}#Retrieve a payroll run
payroll.runs.retrieve
The run, with its current state, the rule pack it's pinned to and its totals.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Returns
The run.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsRetrieve({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RUNApi(client)
result = api.payroll_runs_retrieve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsRetrieve(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsRetrieve("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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 RUNApi(config);
var result = api.PayrollRunsRetrieve(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RUNAPI.PayrollRunsRetrieve(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}#Update a draft run
payroll.runs.update
You can change a run only while it's in draft. Once it has been simulated or approved, the change is refused with conflict.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
roster_entry_ids
array of string
optionalWhich roster entries the run pays, by their id. You can change this only while the run is in draft; afterwards the call is refused with conflict.
rule_pack_version
string
optionalThe rule pack version to pin the run to in place of the one it has, for example 2026.08.1. You can change this only while the run is in draft.
Returns
The updated run.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X PATCH "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"roster_entry_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"],"rule_pack_version":"2026.08.1"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsUpdate({
runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
idempotencyKey: crypto.randomUUID(),
runRunUpdateRequest: {"rosterEntryIds":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"],"rulePackVersion":"2026.08.1"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'PATCH',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"roster_entry_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"rule_pack_version": "2026.08.1"
}),
});
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.RUNApi(client)
result = api.payroll_runs_update(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"roster_entry_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"], "rule_pack_version": "2026.08.1"})import os
import uuid
import requests
response = requests.request(
'PATCH',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"roster_entry_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"], "rule_pack_version": "2026.08.1"},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsUpdate($idempotencyKey, json_decode('{"roster_entry_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"],"rule_pack_version":"2026.08.1"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"roster_entry_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"],"rule_pack_version":"2026.08.1"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsUpdate("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("PATCH", HttpRequest.BodyPublishers.ofString("""
{
"roster_entry_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"rule_pack_version": "2026.08.1"
}
"""))
.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 RUNApi(config);
var result = api.PayrollRunsUpdate(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"roster_entry_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"rule_pack_version": "2026.08.1"
}
""", 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.RUNAPI.PayrollRunsUpdate(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).RunRunUpdateRequest(body).Execute()body := strings.NewReader(`{
"roster_entry_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"rule_pack_version": "2026.08.1"
}`)
req, _ := http.NewRequest("PATCH", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/simulate#Simulate a run without committing anything
payroll.runs.simulate
Computes the run and returns the totals it would produce. No payslips are written and no money moves.
Name a draft rule pack version to test a rate change before the pack is activated. Simulating against a draft pack doesn't activate it.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
draft_rule_pack_version
string
optionalSimulate against a pack that has not been activated. Naming it here does not activate it.
Returns
The simulated totals. The run moves to simulated.
object
always "run_simulation"
requiredAlways run_simulation. Tells you which kind of record you are looking at, so one handler can read any response.
run_id
string
requiredThe id of the run these totals were simulated for, starting with run_enterprise_: the run_id you sent in the path of POST /v1/payroll/runs/{run_id}/simulate. The run itself moves to simulated.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
requiredEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
wrote_records
always false
requiredAlways false. A simulation writes nothing and moves nothing.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"draft_rule_pack_version":"2026.08.1"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsSimulate({
runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
idempotencyKey: crypto.randomUUID(),
runRunSimulateRequest: {"draftRulePackVersion":"2026.08.1"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"draft_rule_pack_version": "2026.08.1"
}),
});
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.RUNApi(client)
result = api.payroll_runs_simulate(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"draft_rule_pack_version": "2026.08.1"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"draft_rule_pack_version": "2026.08.1"},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsSimulate($idempotencyKey, json_decode('{"draft_rule_pack_version":"2026.08.1"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate');
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 => '{"draft_rule_pack_version":"2026.08.1"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsSimulate("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate"))
.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("""
{
"draft_rule_pack_version": "2026.08.1"
}
"""))
.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 RUNApi(config);
var result = api.PayrollRunsSimulate(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"draft_rule_pack_version": "2026.08.1"
}
""", 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.RUNAPI.PayrollRunsSimulate(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).RunRunSimulateRequest(body).Execute()body := strings.NewReader(`{
"draft_rule_pack_version": "2026.08.1"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/simulate", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "run_simulation",
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"wrote_records": false,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
}
}
/v1/payroll/runs/{run_id}/approve#Approve a run for execution
payroll.runs.approve
Refused if you prepared the run and it's above the configured maker-checker threshold. Refused while any finding of severity p0 is open.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The approved run.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsApprove({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve', {
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.RUNApi(client)
result = api.payroll_runs_approve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsApprove(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsApprove("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve"))
.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 RUNApi(config);
var result = api.PayrollRunsApprove(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve");
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.RUNAPI.PayrollRunsApprove(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/approve", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/execute#Execute an approved run
payroll.runs.execute
Computes and writes every payslip, then emits the statutory instruction set.
Every payee is checked before anything is computed. One payee without an anchored identity or without a live engagement fails the whole run, not one payslip.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
Execution accepted. Poll the run, or wait for run.completed.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsExecute({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RUNApi(client)
result = api.payroll_runs_execute(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsExecute(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsExecute("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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 RUNApi(config);
var result = api.PayrollRunsExecute(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_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.RUNAPI.PayrollRunsExecute(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/execute", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/cancel#Cancel a run before execution
payroll.runs.cancel
Ends a run that hasn't executed. Once a run has written payslips you can't cancel it; open a correction run instead.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The cancelled run.
id
string
requiredThe run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.
object
always "payroll_run"
requiredAlways payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
status
string
requiredYou move a run between states with endpoints, not by setting this field.
draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelledrun_type
string
requiredFixed at creation and immutable afterwards.
regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlementperiod
string
requiredThe pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.
payee_count
integer · minimum 0
optionalHow many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
totals
RunTotals
optionalEach figure is the sum of its payslip lines, never a percentage of an aggregate.
5 fields of RunTotals
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
prepared_by
string · nullable
optionalWho prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.
approved_by
string · nullable
optionalNever the same actor as prepared_by above the maker-checker threshold.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
executed_at
string · date-time · nullable
optionalWhen the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsCancel({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel', {
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.RUNApi(client)
result = api.payroll_runs_cancel(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsCancel(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsCancel("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel"))
.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 RUNApi(config);
var result = api.PayrollRunsCancel(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel");
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.RUNAPI.PayrollRunsCancel(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_run",
"livemode": true,
"mocked": true,
"status": "draft",
"run_type": "regular",
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z",
"payee_count": 0,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"totals": {
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
},
"prepared_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"approved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"executed_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/totals#Retrieve run totals
payroll.runs.retrieve_totals
Every total is the sum of its payslip lines, in a stated order. No total is a percentage of an aggregate.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Returns
The totals.
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
total_deductions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_contributions
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payslip_count
integer · minimum 0
optionalHow many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunsRetrieveTotals({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals', {
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.RUNApi(client)
result = api.payroll_runs_retrieve_totals(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunsRetrieveTotals(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunsRetrieveTotals("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals"))
.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 RUNApi(config);
var result = api.PayrollRunsRetrieveTotals(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals");
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.RUNAPI.PayrollRunsRetrieveTotals(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/totals", 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)
{
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"total_deductions": {
"amount": 1234567,
"currency": "NGN"
},
"employer_contributions": {
"amount": 1234567,
"currency": "NGN"
},
"payslip_count": 0
}
/v1/payroll/runs/{run_id}/findings#List anomaly findings on a run
payroll.run_findings.list
Every run is screened by Anomaly Shield before execution. A finding of severity p0 blocks approval and execution until you resolve it.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
severity
string
optionalReturn only findings of this severity: p0, which blocks approval and execution until you resolve it, or p1 or p2, which are for you to review and block nothing. Leave it out to get findings of every severity.
p0p1p2status
string
optionalReturn only findings in this state: open (not yet resolved) or resolved (cleared with a reason code at POST /v1/payroll/runs/{run_id}/findings/{finding_id}/resolve). Leave it out to get both.
openresolvedReturns
A page of findings.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of RunFinding
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of RunFinding
id
string
requiredThe finding's identifier, as it reads on each finding from GET /v1/payroll/runs/{run_id}/findings. It never changes; pass it as finding_id in the path when you retrieve or resolve the finding.
object
always "run_finding"
requiredAlways run_finding. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run this finding was raised on, starting with run_enterprise_, as returned by POST /v1/payroll/runs. A p0 finding blocks that run's approval and execution until you resolve it.
severity
string
requiredOnly p0 blocks approval and execution.
p0p1p2status
string
requiredopenresolvedcode
string
requiredA stable code naming what was found, for example net_pay_variance_above_threshold. Match on this in your own handling; detail is for people.
detail
string
requiredA sentence for a person saying what was found and why it matters, for example that a payee has no verified destination. Don't match on it; use code.
subject_id
string · nullable
optionalWho the finding is about, when it concerns one payee: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. null when it concerns the run as a whole.
resolved_by
string · nullable
optionalWho resolved the finding, as their user identifier, kept permanently with the reason code. null while it's still open.
reason_code
string · nullable
optionalThe reason code sent when the finding was resolved, for example no_payee_destination. null while it's still open.
resolved_at
string · date-time · nullable
optionalWhen the finding was resolved, as an RFC 3339 timestamp in UTC. null while it's still open.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), severity (optional), status (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), severity (optional), status (optional)
const result = await api.payrollRunFindingsList({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25, severity: 'p0', status: 'open' });// query parameters: limit (optional), severity (optional), status (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open', {
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.RUNApi(client)
# query parameters: limit (optional), severity (optional), status (optional)
result = api.payroll_run_findings_list(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit=25, severity='p0', status='open')import os
import requests
# query parameters: limit (optional), severity (optional), status (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open',
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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), severity (optional), status (optional)
$result = $api->payrollRunFindingsList(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25, severity: 'p0', status: 'open');<?php
// query parameters: limit (optional), severity (optional), status (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open');
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.RunApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), severity (optional), status (optional)
var result = api.payrollRunFindingsList("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 25, RunFindingSeverity.fromValue("p0"), RunFindingStatus.fromValue("open"));// query parameters: limit (optional), severity (optional), status (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open"))
.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 RUNApi(config);
// query parameters: limit (optional), severity (optional), status (optional)
var result = api.PayrollRunFindingsList(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", limit: 25, severity: RunFindingSeverity.P0, status: RunFindingStatus.Open);// query parameters: limit (optional), severity (optional), status (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open");
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), severity (optional), status (optional)
result, _, err := client.RUNAPI.PayrollRunFindingsList(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Limit(25).Severity(droomwork.RunFindingSeverity("p0")).Status(droomwork.RunFindingStatus("open")).Execute()// query parameters: limit (optional), severity (optional), status (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings?limit=25&severity=p0&status=open", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "run_finding",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"severity": "p0",
"status": "open",
"code": "net_pay_variance_above_threshold",
"detail": "The payee has no verified destination, so this line cannot be paid.",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"resolved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reason_code": "no_payee_destination",
"resolved_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payroll/runs/{run_id}/findings/{finding_id}#Retrieve a finding
payroll.run_findings.retrieve
One finding: its severity, and if it's resolved, the reason code and who resolved it.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
finding_id
string
requiredThe finding's identifier, from the id of a finding on GET /v1/payroll/runs/{run_id}/findings for the same run. Pair it with that run's run_id.
Returns
The finding.
id
string
requiredThe finding's identifier, as it reads on each finding from GET /v1/payroll/runs/{run_id}/findings. It never changes; pass it as finding_id in the path when you retrieve or resolve the finding.
object
always "run_finding"
requiredAlways run_finding. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run this finding was raised on, starting with run_enterprise_, as returned by POST /v1/payroll/runs. A p0 finding blocks that run's approval and execution until you resolve it.
severity
string
requiredOnly p0 blocks approval and execution.
p0p1p2status
string
requiredopenresolvedcode
string
requiredA stable code naming what was found, for example net_pay_variance_above_threshold. Match on this in your own handling; detail is for people.
detail
string
requiredA sentence for a person saying what was found and why it matters, for example that a payee has no verified destination. Don't match on it; use code.
subject_id
string · nullable
optionalWho the finding is about, when it concerns one payee: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. null when it concerns the run as a whole.
resolved_by
string · nullable
optionalWho resolved the finding, as their user identifier, kept permanently with the reason code. null while it's still open.
reason_code
string · nullable
optionalThe reason code sent when the finding was resolved, for example no_payee_destination. null while it's still open.
resolved_at
string · date-time · nullable
optionalWhen the finding was resolved, as an RFC 3339 timestamp in UTC. null while it's still open.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunFindingsRetrieve({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', findingId: '{finding_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_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.RUNApi(client)
result = api.payroll_run_findings_retrieve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', finding_id='{finding_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunFindingsRetrieve(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', finding_id: '{finding_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunFindingsRetrieve("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{finding_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_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 RUNApi(config);
var result = api.PayrollRunFindingsRetrieve(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", findingId: "{finding_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_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.RUNAPI.PayrollRunFindingsRetrieve(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{finding_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "run_finding",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"severity": "p0",
"status": "open",
"code": "net_pay_variance_above_threshold",
"detail": "The payee has no verified destination, so this line cannot be paid.",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"resolved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reason_code": "no_payee_destination",
"resolved_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/findings/{finding_id}/resolve#Resolve a finding
payroll.run_findings.resolve
Send a reason code to clear a finding. Who cleared it is recorded with it. A finding is never deleted.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
finding_id
string
requiredThe finding's identifier, from the id of a finding on GET /v1/payroll/runs/{run_id}/findings for the same run. Pair it with that run's run_id.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
reason_code
string
requiredWhy this finding is being cleared. Recorded permanently with the actor.
note
string
optionalFree text to keep with the resolution: what you checked, or why the reason code applies. Optional; reason_code is what's required.
Returns
The resolved finding.
id
string
requiredThe finding's identifier, as it reads on each finding from GET /v1/payroll/runs/{run_id}/findings. It never changes; pass it as finding_id in the path when you retrieve or resolve the finding.
object
always "run_finding"
requiredAlways run_finding. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run this finding was raised on, starting with run_enterprise_, as returned by POST /v1/payroll/runs. A p0 finding blocks that run's approval and execution until you resolve it.
severity
string
requiredOnly p0 blocks approval and execution.
p0p1p2status
string
requiredopenresolvedcode
string
requiredA stable code naming what was found, for example net_pay_variance_above_threshold. Match on this in your own handling; detail is for people.
detail
string
requiredA sentence for a person saying what was found and why it matters, for example that a payee has no verified destination. Don't match on it; use code.
subject_id
string · nullable
optionalWho the finding is about, when it concerns one payee: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. null when it concerns the run as a whole.
resolved_by
string · nullable
optionalWho resolved the finding, as their user identifier, kept permanently with the reason code. null while it's still open.
reason_code
string · nullable
optionalThe reason code sent when the finding was resolved, for example no_payee_destination. null while it's still open.
resolved_at
string · date-time · nullable
optionalWhen the finding was resolved, as an RFC 3339 timestamp in UTC. null while it's still open.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"reason_code":"no_payee_destination","note":"example"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRunFindingsResolve({
runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
findingId: '{finding_id}',
idempotencyKey: crypto.randomUUID(),
runFindingResolveRequest: {"reasonCode":"no_payee_destination","note":"example"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"reason_code": "no_payee_destination",
"note": "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.RUNApi(client)
result = api.payroll_run_findings_resolve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', finding_id='{finding_id}', body={"reason_code": "no_payee_destination", "note": "example"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"reason_code": "no_payee_destination", "note": "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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRunFindingsResolve($idempotencyKey, json_decode('{"reason_code":"no_payee_destination","note":"example"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve');
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 => '{"reason_code":"no_payee_destination","note":"example"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRunFindingsResolve("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{finding_id}", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve"))
.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("""
{
"reason_code": "no_payee_destination",
"note": "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 RUNApi(config);
var result = api.PayrollRunFindingsResolve(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", findingId: "{finding_id}", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"reason_code": "no_payee_destination",
"note": "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.RUNAPI.PayrollRunFindingsResolve(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{finding_id}").IdempotencyKey(key).RunFindingResolveRequest(body).Execute()body := strings.NewReader(`{
"reason_code": "no_payee_destination",
"note": "example"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/findings/%7Bfinding_id%7D/resolve", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "run_finding",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"severity": "p0",
"status": "open",
"code": "net_pay_variance_above_threshold",
"detail": "The payee has no verified destination, so this line cannot be paid.",
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"resolved_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reason_code": "no_payee_destination",
"resolved_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/payslips#List payslips
payroll.payslips.list
Your payslips. Filter by run or by subject.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
run_id
string
optionalReturn only payslips written by this run: pass the run's id, starting with run_enterprise_, from POST /v1/payroll/runs or GET /v1/payroll/runs. Leave it out to get payslips from every run.
subject_id
string
optionalReturn only payslips for this person: pass their subject identifier, starting with sub_, the subject_id you sent on their roster entry at POST /v1/payroll/roster_entries. Leave it out to get payslips for everyone.
Returns
A page of payslips.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Payslip
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
12 fields of Payslip
id
string
requiredThe payslip's identifier, starting with run_enterprise_payslip_. It's written when the run executes and first reaches you on GET /v1/payroll/payslips; it never changes, so pass it as payslip_id wherever a call names this payslip.
object
always "payslip"
requiredAlways payslip. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run that wrote this payslip, starting with run_enterprise_, as returned by POST /v1/payroll/runs. Pass it as run_id on GET /v1/payroll/payslips to list every payslip from the same run.
subject_id
string
optionalWho this payslip is for: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. The same value is on their pay profile.
period
string
optionalThe pay period this payslip covers, as year and month, for example 2026-09. It matches the period of the run that wrote it.
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
lines
array of PayslipLine
requiredEvery line on this payslip, each with its own calculation trace. Read kind to tell earnings from deductions, employer contributions and information lines.
7 fields of PayslipLine
id
string
requiredThe line's identifier, as it reads on each entry in lines on the payslip from GET /v1/payroll/payslips/{payslip_id}. Quote it when you ask about one figure on this payslip.
object
always "payslip_line"
requiredAlways payslip_line. Tells you which kind of record you are looking at, so one handler can read any response.
code
string
requiredWhich pay element this line is, as a code such as pension_employee. Use it to match lines across payslips and against the explanation.
kind
string
requiredearningdeductionemployer_contributioninformationamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
trace
CalculationTrace
requiredThe formula, the inputs, the intermediate values and the pack version behind one line. Every payslip line carries one, and any historical line replays to the kobo from it.
6 fields of CalculationTrace
formula
string
requiredThe calculation behind this line, written as its named inputs and operators, for example pensionable_earnings x pension_employee_rate. Each name appears in inputs.
inputs
array of object
requiredThe values the formula was evaluated with, each named as it appears in formula. An amount is Money in whole minor units and a rate is an exact fraction, so you can replay the line.
exact
Rate
requiredAn exact fraction of two whole numbers. Never a decimal, and it carries no currency.
2 fields of Rate
numerator
integer · int64
requiredThe top of the fraction, a whole number. 8 over 100 is eight percent.
denominator
integer · int64 · minimum 1
requiredThe bottom of the fraction, a whole number of 1 or more. Never zero, so every rate can be evaluated.
rounding
Rounding
required3 fields of Rounding
scale
string
requiredThe unit rounded to, always minor_unit: the kobo for NGN, so a rounded figure is always a whole number of minor units.
minor_unitmode
string
requiredHow a value between two minor units is settled: half_up takes a half up, half_down takes it down, half_even takes it to the even neighbour, ceiling always rounds up, floor always down, truncate drops the fraction. The rule pack sets it for each line; there's no default.
half_uphalf_evenhalf_downceilingfloortruncatepoint
string
requiredWhere rounding was applied: per_line rounds each line on its own, per_total rounds once on the total. The rule pack sets it for each line, and the two can differ by real money.
per_lineper_totalresult
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
rule_pack
RulePackReference
required3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
const result = await api.payrollPayslipsList({ limit: 25, runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', subjectId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_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.RUNApi(client)
# query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
result = api.payroll_payslips_list(limit=25, run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', subject_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
$result = $api->payrollPayslipsList(limit: 25, run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', subject_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
var result = api.payrollPayslipsList(25, null, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_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 RUNApi(config);
// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
var result = api.PayrollPayslipsList(limit: 25, runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", subjectId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_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), run_id (optional), subject_id (optional)
result, _, err := client.RUNAPI.PayrollPayslipsList(ctx).Limit(25).RunId("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").SubjectId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), run_id (optional), subject_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/payslips?limit=25&run_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&subject_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"lines": [
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip_line",
"code": "pension_employee",
"kind": "earning",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"mocked": true,
"trace": {
"formula": "pensionable_earnings x pension_employee_rate",
"inputs": [
{}
],
"exact": {
"numerator": 1,
"denominator": 1
},
"rounding": {
"scale": "minor_unit",
"mode": "half_up",
"point": "per_line"
},
"result": {
"amount": 1234567,
"currency": "NGN"
},
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
}
}
}
],
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"period": "2026-09",
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payroll/payslips/{payslip_id}#Retrieve a payslip
payroll.payslips.retrieve
Every line carries its calculation trace inline. There's no separate trace endpoint.
Path parameters
payslip_id
string
requiredThe payslip's identifier, from the id of a payslip on GET /v1/payroll/payslips. It starts with run_enterprise_payslip_.
Returns
The payslip, with a traced line for every element.
id
string
requiredThe payslip's identifier, starting with run_enterprise_payslip_. It's written when the run executes and first reaches you on GET /v1/payroll/payslips; it never changes, so pass it as payslip_id wherever a call names this payslip.
object
always "payslip"
requiredAlways payslip. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run that wrote this payslip, starting with run_enterprise_, as returned by POST /v1/payroll/runs. Pass it as run_id on GET /v1/payroll/payslips to list every payslip from the same run.
subject_id
string
optionalWho this payslip is for: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. The same value is on their pay profile.
period
string
optionalThe pay period this payslip covers, as year and month, for example 2026-09. It matches the period of the run that wrote it.
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
lines
array of PayslipLine
requiredEvery line on this payslip, each with its own calculation trace. Read kind to tell earnings from deductions, employer contributions and information lines.
7 fields of PayslipLine
id
string
requiredThe line's identifier, as it reads on each entry in lines on the payslip from GET /v1/payroll/payslips/{payslip_id}. Quote it when you ask about one figure on this payslip.
object
always "payslip_line"
requiredAlways payslip_line. Tells you which kind of record you are looking at, so one handler can read any response.
code
string
requiredWhich pay element this line is, as a code such as pension_employee. Use it to match lines across payslips and against the explanation.
kind
string
requiredearningdeductionemployer_contributioninformationamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
trace
CalculationTrace
requiredThe formula, the inputs, the intermediate values and the pack version behind one line. Every payslip line carries one, and any historical line replays to the kobo from it.
6 fields of CalculationTrace
formula
string
requiredThe calculation behind this line, written as its named inputs and operators, for example pensionable_earnings x pension_employee_rate. Each name appears in inputs.
inputs
array of object
requiredThe values the formula was evaluated with, each named as it appears in formula. An amount is Money in whole minor units and a rate is an exact fraction, so you can replay the line.
exact
Rate
requiredAn exact fraction of two whole numbers. Never a decimal, and it carries no currency.
2 fields of Rate
numerator
integer · int64
requiredThe top of the fraction, a whole number. 8 over 100 is eight percent.
denominator
integer · int64 · minimum 1
requiredThe bottom of the fraction, a whole number of 1 or more. Never zero, so every rate can be evaluated.
rounding
Rounding
required3 fields of Rounding
scale
string
requiredThe unit rounded to, always minor_unit: the kobo for NGN, so a rounded figure is always a whole number of minor units.
minor_unitmode
string
requiredHow a value between two minor units is settled: half_up takes a half up, half_down takes it down, half_even takes it to the even neighbour, ceiling always rounds up, floor always down, truncate drops the fraction. The rule pack sets it for each line; there's no default.
half_uphalf_evenhalf_downceilingfloortruncatepoint
string
requiredWhere rounding was applied: per_line rounds each line on its own, per_total rounds once on the total. The rule pack sets it for each line, and the two can differ by real money.
per_lineper_totalresult
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
rule_pack
RulePackReference
required3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayslipsRetrieve({ payslipId: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_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.RUNApi(client)
result = api.payroll_payslips_retrieve(payslip_id='run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayslipsRetrieve(payslip_id: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayslipsRetrieve("run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_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 RUNApi(config);
var result = api.PayrollPayslipsRetrieve(payslipId: "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_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.RUNAPI.PayrollPayslipsRetrieve(ctx, "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"lines": [
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip_line",
"code": "pension_employee",
"kind": "earning",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"mocked": true,
"trace": {
"formula": "pensionable_earnings x pension_employee_rate",
"inputs": [
{}
],
"exact": {
"numerator": 1,
"denominator": 1
},
"rounding": {
"scale": "minor_unit",
"mode": "half_up",
"point": "per_line"
},
"result": {
"amount": 1234567,
"currency": "NGN"
},
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
}
}
}
],
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"period": "2026-09",
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/payslips/{payslip_id}/replay#Replay a payslip from its stored inputs
payroll.payslips.replay
Recomputes the payslip from the inputs and the rule pack version stored on it, then compares the result with what was issued. Any historical payslip replays to the kobo.
It's a POST because it computes rather than reads.
Path parameters
payslip_id
string
requiredThe payslip's identifier, from the id of a payslip on GET /v1/payroll/payslips. It starts with run_enterprise_payslip_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The comparison.
object
always "payslip_replay"
requiredAlways payslip_replay. Tells you which kind of record you are looking at, so one handler can read any response.
payslip_id
string
requiredThe payslip this comparison is for, starting with run_enterprise_payslip_: the payslip_id you sent in the path of POST /v1/payroll/payslips/{payslip_id}/replay.
matches
boolean
requiredtrue when every recomputed line agrees with what was issued, to the kobo. When false, differences names each line that moved.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
differences
array of object
optionalEmpty when matches is true.
3 fields
code
string
requiredThe code of the payslip line that disagrees. Match it against lines on the payslip to find the line.
issued
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
recomputed
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayslipsReplay({ payslipId: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay', {
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.RUNApi(client)
result = api.payroll_payslips_replay(payslip_id='run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayslipsReplay(payslip_id: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayslipsReplay("run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay"))
.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 RUNApi(config);
var result = api.PayrollPayslipsReplay(payslipId: "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay");
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.RUNAPI.PayrollPayslipsReplay(ctx, "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/replay", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "payslip_replay",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"matches": true,
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"differences": [
{
"code": "no_payee_destination",
"issued": {
"amount": 1234567,
"currency": "NGN"
},
"recomputed": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
/v1/payroll/payslips/{payslip_id}/explanation#Retrieve a plain language explanation of a payslip
payroll.payslips.retrieve_explanation
Explains each line in English or Nigerian Pidgin. The explanation quotes the trace and does no arithmetic of its own, so it can't disagree with the payslip.
Path parameters
payslip_id
string
requiredThe payslip's identifier, from the id of a payslip on GET /v1/payroll/payslips. It starts with run_enterprise_payslip_.
Query parameters
language
string
optionalWhich language to write the explanation in: en for English or pcm for Nigerian Pidgin. Leave it out for English.
enpcmReturns
The explanation.
object
always "payslip_explanation"
requiredAlways payslip_explanation. Tells you which kind of record you are looking at, so one handler can read any response.
payslip_id
string
requiredThe payslip this explanation is for, starting with run_enterprise_payslip_: the payslip_id you sent in the path of GET /v1/payroll/payslips/{payslip_id}/explanation.
language
string · defaults to "en"
requiredEnglish or Nigerian Pidgin.
enpcmlines
array of object
requiredOne entry per payslip line, in plain words. Each quotes the line's trace and carries the same code and amount as the line it explains.
3 fields
code
string
requiredThe code of the payslip line this entry explains. Match it against lines on the payslip.
text
string
requiredQuotes the trace. Performs no arithmetic of its own.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
Other responses
Errors it can return
# query parameters: language (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: language (optional)
const result = await api.payrollPayslipsRetrieveExplanation({ payslipId: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', language: 'en' });// query parameters: language (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en', {
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.RUNApi(client)
# query parameters: language (optional)
result = api.payroll_payslips_retrieve_explanation(payslip_id='run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', language='en')import os
import requests
# query parameters: language (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en',
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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: language (optional)
$result = $api->payrollPayslipsRetrieveExplanation(payslip_id: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', language: 'en');<?php
// query parameters: language (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en');
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.RunApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: language (optional)
var result = api.payrollPayslipsRetrieveExplanation("run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", RunExplanationLanguage.fromValue("en"));// query parameters: language (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en"))
.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 RUNApi(config);
// query parameters: language (optional)
var result = api.PayrollPayslipsRetrieveExplanation(payslipId: "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", language: RunExplanationLanguage.En);// query parameters: language (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en");
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: language (optional)
result, _, err := client.RUNAPI.PayrollPayslipsRetrieveExplanation(ctx, "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Language(droomwork.RunExplanationLanguage("en")).Execute()// query parameters: language (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/explanation?language=en", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "payslip_explanation",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"language": "en",
"lines": [
{
"code": "no_payee_destination",
"text": "example",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
/v1/payroll/payslips/{payslip_id}/issue#Issue a payslip to the worker
payroll.payslips.issue
Delivers the payslip on the channels you name. The document is signed before it leaves.
Path parameters
payslip_id
string
requiredThe payslip's identifier, from the id of a payslip on GET /v1/payroll/payslips. It starts with run_enterprise_payslip_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
channels
array of DeliveryChannel
requiredWhere to deliver the payslip: any of email, whatsapp and portal. Name at least one; each channel you name gets its own delivery status in the response.
language
string · defaults to "en"
optionalEnglish or Nigerian Pidgin.
enpcmReturns
Delivery accepted.
object
always "payslip_issue"
requiredAlways payslip_issue. Tells you which kind of record you are looking at, so one handler can read any response.
payslip_id
string
requiredThe payslip being delivered, starting with run_enterprise_payslip_: the payslip_id you sent in the path of POST /v1/payroll/payslips/{payslip_id}/issue.
deliveries
array of object
requiredOne entry per channel you named, each with its own status. Accepted isn't delivered: read the status for each channel.
2 fields
channel
string
requiredemailwhatsappportalstatus
string
requiredWhere this delivery has got to: queued means it hasn't gone yet, delivered means it reached the worker on this channel, failed means it didn't. Accepted isn't delivered, so read it for each channel.
queueddeliveredfailedOther responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"channels":["email"],"language":"en"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayslipsIssue({
payslipId: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
idempotencyKey: crypto.randomUUID(),
runPayslipIssueRequest: {"channels":["email"],"language":"en"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"channels": [
"email"
],
"language": "en"
}),
});
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.RUNApi(client)
result = api.payroll_payslips_issue(payslip_id='run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"channels": ["email"], "language": "en"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"channels": ["email"], "language": "en"},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayslipsIssue($idempotencyKey, json_decode('{"channels":["email"],"language":"en"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue');
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 => '{"channels":["email"],"language":"en"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayslipsIssue("run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue"))
.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("""
{
"channels": [
"email"
],
"language": "en"
}
"""))
.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 RUNApi(config);
var result = api.PayrollPayslipsIssue(payslipId: "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"channels": [
"email"
],
"language": "en"
}
""", 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.RUNAPI.PayrollPayslipsIssue(ctx, "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).RunPayslipIssueRequest(body).Execute()body := strings.NewReader(`{
"channels": [
"email"
],
"language": "en"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/issue", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "payslip_issue",
"payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"deliveries": [
{
"channel": "email",
"status": "queued"
}
]
}
/v1/payroll/payslips/{payslip_id}/document#Retrieve the signed payslip document
payroll.payslips.retrieve_document
The signed payslip as a PDF, or the same content as JSON.
Path parameters
payslip_id
string
requiredThe payslip's identifier, from the id of a payslip on GET /v1/payroll/payslips. It starts with run_enterprise_payslip_.
Query parameters
format
string
optionalWhich form you want the signed payslip in: pdf for the document, json for the same content as a payslip record. Leave it out to get pdf.
pdfjsonReturns
The signed document.
id
string
requiredThe payslip's identifier, starting with run_enterprise_payslip_. It's written when the run executes and first reaches you on GET /v1/payroll/payslips; it never changes, so pass it as payslip_id wherever a call names this payslip.
object
always "payslip"
requiredAlways payslip. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe id of the run that wrote this payslip, starting with run_enterprise_, as returned by POST /v1/payroll/runs. Pass it as run_id on GET /v1/payroll/payslips to list every payslip from the same run.
subject_id
string
optionalWho this payslip is for: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. The same value is on their pay profile.
period
string
optionalThe pay period this payslip covers, as year and month, for example 2026-09. It matches the period of the run that wrote it.
gross
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
net
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
lines
array of PayslipLine
requiredEvery line on this payslip, each with its own calculation trace. Read kind to tell earnings from deductions, employer contributions and information lines.
7 fields of PayslipLine
id
string
requiredThe line's identifier, as it reads on each entry in lines on the payslip from GET /v1/payroll/payslips/{payslip_id}. Quote it when you ask about one figure on this payslip.
object
always "payslip_line"
requiredAlways payslip_line. Tells you which kind of record you are looking at, so one handler can read any response.
code
string
requiredWhich pay element this line is, as a code such as pension_employee. Use it to match lines across payslips and against the explanation.
kind
string
requiredearningdeductionemployer_contributioninformationamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
trace
CalculationTrace
requiredThe formula, the inputs, the intermediate values and the pack version behind one line. Every payslip line carries one, and any historical line replays to the kobo from it.
6 fields of CalculationTrace
formula
string
requiredThe calculation behind this line, written as its named inputs and operators, for example pensionable_earnings x pension_employee_rate. Each name appears in inputs.
inputs
array of object
requiredThe values the formula was evaluated with, each named as it appears in formula. An amount is Money in whole minor units and a rate is an exact fraction, so you can replay the line.
exact
Rate
requiredAn exact fraction of two whole numbers. Never a decimal, and it carries no currency.
2 fields of Rate
numerator
integer · int64
requiredThe top of the fraction, a whole number. 8 over 100 is eight percent.
denominator
integer · int64 · minimum 1
requiredThe bottom of the fraction, a whole number of 1 or more. Never zero, so every rate can be evaluated.
rounding
Rounding
required3 fields of Rounding
scale
string
requiredThe unit rounded to, always minor_unit: the kobo for NGN, so a rounded figure is always a whole number of minor units.
minor_unitmode
string
requiredHow a value between two minor units is settled: half_up takes a half up, half_down takes it down, half_even takes it to the even neighbour, ceiling always rounds up, floor always down, truncate drops the fraction. The rule pack sets it for each line; there's no default.
half_uphalf_evenhalf_downceilingfloortruncatepoint
string
requiredWhere rounding was applied: per_line rounds each line on its own, per_total rounds once on the total. The rule pack sets it for each line, and the two can differ by real money.
per_lineper_totalresult
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
rule_pack
RulePackReference
required3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
rule_pack
RulePackReference
optional3 fields of RulePackReference
pack_id
string
requiredThe family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.
version
string
requiredThe pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.
content_hash
string
requiredA digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
# query parameters: format (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: format (optional)
const result = await api.payrollPayslipsRetrieveDocument({ payslipId: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', format: 'pdf' });// query parameters: format (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf', {
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.RUNApi(client)
# query parameters: format (optional)
result = api.payroll_payslips_retrieve_document(payslip_id='run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', format='pdf')import os
import requests
# query parameters: format (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf',
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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: format (optional)
$result = $api->payrollPayslipsRetrieveDocument(payslip_id: 'run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', format: 'pdf');<?php
// query parameters: format (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: format (optional)
var result = api.payrollPayslipsRetrieveDocument("run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "pdf");// query parameters: format (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf"))
.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 RUNApi(config);
// query parameters: format (optional)
var result = api.PayrollPayslipsRetrieveDocument(payslipId: "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", format: "pdf");// query parameters: format (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf");
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: format (optional)
result, _, err := client.RUNAPI.PayrollPayslipsRetrieveDocument(ctx, "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Format("pdf").Execute()// query parameters: format (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/payslips/run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/document?format=pdf", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_payslip_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"gross": {
"amount": 1234567,
"currency": "NGN"
},
"net": {
"amount": 1234567,
"currency": "NGN"
},
"lines": [
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payslip_line",
"code": "pension_employee",
"kind": "earning",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"mocked": true,
"trace": {
"formula": "pensionable_earnings x pension_employee_rate",
"inputs": [
{}
],
"exact": {
"numerator": 1,
"denominator": 1
},
"rounding": {
"scale": "minor_unit",
"mode": "half_up",
"point": "per_line"
},
"result": {
"amount": 1234567,
"currency": "NGN"
},
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
}
}
}
],
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"period": "2026-09",
"rule_pack": {
"pack_id": "ng-payroll",
"version": "2026.08.1",
"content_hash": "sha256:9f2c1e00"
},
"created_at": "2026-09-01T09:00:00Z"
}
/v1/pay_profiles#List pay profiles
payroll.pay_profiles.list
Your pay profiles, including superseded versions, so you can explain a historical payslip.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
subject_id
string
optionalReturn only this person's pay profiles, superseded versions included: pass their subject identifier, starting with sub_, the subject_id you sent at POST /v1/pay_profiles. Leave it out to get every profile.
Returns
A page of pay profiles.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of PayProfile
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
12 fields of PayProfile
id
string
requiredThe identifier of this profile version. It starts with run_enterprise_pay_profile_, is assigned at POST /v1/pay_profiles or PATCH /v1/pay_profiles/{pay_profile_id} and never changes; pass it as pay_profile_id to name this version.
object
always "pay_profile"
requiredAlways pay_profile. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe worker this profile pays: their subject identifier, starting with sub_, as you sent it at POST /v1/pay_profiles. Every version of the profile carries the same one.
base_salary
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
pay_frequency
string
optionalHow often the worker is paid: monthly (once a month), semi_monthly (twice a month) or weekly (once a week). It's what you set at POST /v1/pay_profiles; a new version keeps it.
monthlysemi_monthlyweeklyallowances
array of object
optionalFixed allowances on top of base salary, each as a code and an amount. Empty or absent when the worker has none.
2 fields
code
string
requiredYour own code for this allowance, as you sent it on the create or supersede call. Keep it the same on every version so you can follow one allowance over time.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredThe first day this version applies, as a calendar date in YYYY-MM-DD. A payslip for an earlier period computes from the version in force then.
effective_to
string · date · nullable
optionalThe last day this version applies, as a calendar date in YYYY-MM-DD, or null while it's current. A leaver is an end date, not a deletion.
version
integer · minimum 1
requiredWhich version of the worker's profile this is, counting from 1. Superseding it creates the next; earlier versions stay listed so you can explain an old payslip.
supersedes
string · nullable
optionalThe id of the version this one replaced, or null for the first version.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), subject_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), subject_id (optional)
const result = await api.payrollPayProfilesList({ limit: 25, subjectId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), subject_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_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.RUNApi(client)
# query parameters: limit (optional), starting_after (optional), subject_id (optional)
result = api.payroll_pay_profiles_list(limit=25, subject_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), subject_id (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), subject_id (optional)
$result = $api->payrollPayProfilesList(limit: 25, subject_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), subject_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), starting_after (optional), subject_id (optional)
var result = api.payrollPayProfilesList(25, null, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), subject_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_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 RUNApi(config);
// query parameters: limit (optional), starting_after (optional), subject_id (optional)
var result = api.PayrollPayProfilesList(limit: 25, subjectId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), subject_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_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), subject_id (optional)
result, _, err := client.RUNAPI.PayrollPayProfilesList(ctx).Limit(25).SubjectId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), subject_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/pay_profiles?limit=25&subject_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "pay_profile",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"version": 1,
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
],
"effective_to": "2026-09-01",
"supersedes": "example"
}
],
"has_more": true
}
/v1/pay_profiles#Create a pay profile
payroll.pay_profiles.create
Profiles are effective dated. A mid-period salary revision is a new version, not an edit, so a payslip issued last month still computes from what was true last month.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_id
string
requiredThe worker this profile pays: their subject identifier, starting with sub_, the subject_ref you chose for them at POST /v1/identity/verifications. They need an anchored identity, or the call is refused with subject_not_anchored.
base_salary
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
pay_frequency
string
optionalHow often this person is paid: monthly (once a month), semi_monthly (twice a month) or weekly (once a week). A new version made at PATCH /v1/pay_profiles/{pay_profile_id} keeps whatever you set here.
monthlysemi_monthlyweeklyallowances
array of object
optionalThe allowances paid on top of base_salary, each as a code you choose and an amount in whole minor units. Leave it out when there are none.
2 fields
code
string
requiredYour code for this allowance, such as housing or transport. Use the same code on every version of the profile so you can tell one allowance from another over time.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredThe first date these terms apply, as a calendar date in YYYY-MM-DD. A payslip for an earlier period computes from whatever was true then, never from this profile.
Returns
The pay profile.
id
string
requiredThe identifier of this profile version. It starts with run_enterprise_pay_profile_, is assigned at POST /v1/pay_profiles or PATCH /v1/pay_profiles/{pay_profile_id} and never changes; pass it as pay_profile_id to name this version.
object
always "pay_profile"
requiredAlways pay_profile. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe worker this profile pays: their subject identifier, starting with sub_, as you sent it at POST /v1/pay_profiles. Every version of the profile carries the same one.
base_salary
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
pay_frequency
string
optionalHow often the worker is paid: monthly (once a month), semi_monthly (twice a month) or weekly (once a week). It's what you set at POST /v1/pay_profiles; a new version keeps it.
monthlysemi_monthlyweeklyallowances
array of object
optionalFixed allowances on top of base salary, each as a code and an amount. Empty or absent when the worker has none.
2 fields
code
string
requiredYour own code for this allowance, as you sent it on the create or supersede call. Keep it the same on every version so you can follow one allowance over time.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredThe first day this version applies, as a calendar date in YYYY-MM-DD. A payslip for an earlier period computes from the version in force then.
effective_to
string · date · nullable
optionalThe last day this version applies, as a calendar date in YYYY-MM-DD, or null while it's current. A leaver is an end date, not a deletion.
version
integer · minimum 1
requiredWhich version of the worker's profile this is, counting from 1. Superseding it creates the next; earlier versions stay listed so you can explain an old payslip.
supersedes
string · nullable
optionalThe id of the version this one replaced, or null for the first version.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/pay_profiles" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","base_salary":{"amount":1234567,"currency":"NGN"},"effective_from":"2026-09-01","pay_frequency":"monthly","allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayProfilesCreate({
idempotencyKey: crypto.randomUUID(),
runPayProfileCreateRequest: {"subjectId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","baseSalary":{"amount":1234567,"currency":"NGN"},"effectiveFrom":"2026-09-01","payFrequency":"monthly","allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]},
});const response = await fetch('https://sandbox.droomwork.io/v1/pay_profiles', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}),
});
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.RUNApi(client)
result = api.payroll_pay_profiles_create(body={"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "base_salary": {"amount": 1234567, "currency": "NGN"}, "effective_from": "2026-09-01", "pay_frequency": "monthly", "allowances": [{"code": "no_payee_destination", "amount": {"amount": 1234567, "currency": "NGN"}}]})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/pay_profiles',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "base_salary": {"amount": 1234567, "currency": "NGN"}, "effective_from": "2026-09-01", "pay_frequency": "monthly", "allowances": [{"code": "no_payee_destination", "amount": {"amount": 1234567, "currency": "NGN"}}]},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayProfilesCreate($idempotencyKey, json_decode('{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","base_salary":{"amount":1234567,"currency":"NGN"},"effective_from":"2026-09-01","pay_frequency":"monthly","allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/pay_profiles');
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_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","base_salary":{"amount":1234567,"currency":"NGN"},"effective_from":"2026-09-01","pay_frequency":"monthly","allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayProfilesCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/pay_profiles"))
.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_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
"""))
.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 RUNApi(config);
var result = api.PayrollPayProfilesCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/pay_profiles");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
""", 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.RUNAPI.PayrollPayProfilesCreate(ctx).IdempotencyKey(key).RunPayProfileCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/pay_profiles", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "pay_profile",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"version": 1,
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
],
"effective_to": "2026-09-01",
"supersedes": "example"
}
/v1/pay_profiles/{pay_profile_id}#Retrieve a pay profile
payroll.pay_profiles.retrieve
One version of a pay profile.
Path parameters
pay_profile_id
string
requiredThe pay profile's identifier: the id from POST /v1/pay_profiles or GET /v1/pay_profiles. It starts with run_enterprise_pay_profile_ and names one version of the profile.
Returns
The pay profile.
id
string
requiredThe identifier of this profile version. It starts with run_enterprise_pay_profile_, is assigned at POST /v1/pay_profiles or PATCH /v1/pay_profiles/{pay_profile_id} and never changes; pass it as pay_profile_id to name this version.
object
always "pay_profile"
requiredAlways pay_profile. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe worker this profile pays: their subject identifier, starting with sub_, as you sent it at POST /v1/pay_profiles. Every version of the profile carries the same one.
base_salary
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
pay_frequency
string
optionalHow often the worker is paid: monthly (once a month), semi_monthly (twice a month) or weekly (once a week). It's what you set at POST /v1/pay_profiles; a new version keeps it.
monthlysemi_monthlyweeklyallowances
array of object
optionalFixed allowances on top of base salary, each as a code and an amount. Empty or absent when the worker has none.
2 fields
code
string
requiredYour own code for this allowance, as you sent it on the create or supersede call. Keep it the same on every version so you can follow one allowance over time.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredThe first day this version applies, as a calendar date in YYYY-MM-DD. A payslip for an earlier period computes from the version in force then.
effective_to
string · date · nullable
optionalThe last day this version applies, as a calendar date in YYYY-MM-DD, or null while it's current. A leaver is an end date, not a deletion.
version
integer · minimum 1
requiredWhich version of the worker's profile this is, counting from 1. Superseding it creates the next; earlier versions stay listed so you can explain an old payslip.
supersedes
string · nullable
optionalThe id of the version this one replaced, or null for the first version.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayProfilesRetrieve({ payProfileId: 'run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_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.RUNApi(client)
result = api.payroll_pay_profiles_retrieve(pay_profile_id='run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayProfilesRetrieve(pay_profile_id: 'run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayProfilesRetrieve("run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_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 RUNApi(config);
var result = api.PayrollPayProfilesRetrieve(payProfileId: "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_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.RUNAPI.PayrollPayProfilesRetrieve(ctx, "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "pay_profile",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"version": 1,
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
],
"effective_to": "2026-09-01",
"supersedes": "example"
}
/v1/pay_profiles/{pay_profile_id}#Supersede a pay profile with a new version
payroll.pay_profiles.update
Creates a new effective dated version. There's no delete. A leaver is an end date.
Path parameters
pay_profile_id
string
requiredThe pay profile's identifier: the id from POST /v1/pay_profiles or GET /v1/pay_profiles. It starts with run_enterprise_pay_profile_ and names one version of the profile.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
base_salary
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
allowances
array of object
optionalThe allowances the new version pays on top of base_salary, each as a code and an amount in whole minor units. Send the whole list the new version should pay, not only the ones that changed.
2 fields
code
string
requiredYour code for this allowance, such as housing or transport. Keep the code the earlier version used so the allowance reads as the same one across versions.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredWhen the new version takes effect.
Returns
The new version.
id
string
requiredThe identifier of this profile version. It starts with run_enterprise_pay_profile_, is assigned at POST /v1/pay_profiles or PATCH /v1/pay_profiles/{pay_profile_id} and never changes; pass it as pay_profile_id to name this version.
object
always "pay_profile"
requiredAlways pay_profile. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe worker this profile pays: their subject identifier, starting with sub_, as you sent it at POST /v1/pay_profiles. Every version of the profile carries the same one.
base_salary
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
pay_frequency
string
optionalHow often the worker is paid: monthly (once a month), semi_monthly (twice a month) or weekly (once a week). It's what you set at POST /v1/pay_profiles; a new version keeps it.
monthlysemi_monthlyweeklyallowances
array of object
optionalFixed allowances on top of base salary, each as a code and an amount. Empty or absent when the worker has none.
2 fields
code
string
requiredYour own code for this allowance, as you sent it on the create or supersede call. Keep it the same on every version so you can follow one allowance over time.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
effective_from
string · date
requiredThe first day this version applies, as a calendar date in YYYY-MM-DD. A payslip for an earlier period computes from the version in force then.
effective_to
string · date · nullable
optionalThe last day this version applies, as a calendar date in YYYY-MM-DD, or null while it's current. A leaver is an end date, not a deletion.
version
integer · minimum 1
requiredWhich version of the worker's profile this is, counting from 1. Superseding it creates the next; earlier versions stay listed so you can explain an old payslip.
supersedes
string · nullable
optionalThe id of the version this one replaced, or null for the first version.
Other responses
Errors it can return
curl -X PATCH "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"effective_from":"2026-09-01","base_salary":{"amount":1234567,"currency":"NGN"},"allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollPayProfilesUpdate({
payProfileId: 'run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
idempotencyKey: crypto.randomUUID(),
runPayProfileUpdateRequest: {"effectiveFrom":"2026-09-01","baseSalary":{"amount":1234567,"currency":"NGN"},"allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]},
});const response = await fetch('https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'PATCH',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"effective_from": "2026-09-01",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}),
});
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.RUNApi(client)
result = api.payroll_pay_profiles_update(pay_profile_id='run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"effective_from": "2026-09-01", "base_salary": {"amount": 1234567, "currency": "NGN"}, "allowances": [{"code": "no_payee_destination", "amount": {"amount": 1234567, "currency": "NGN"}}]})import os
import uuid
import requests
response = requests.request(
'PATCH',
'https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"effective_from": "2026-09-01", "base_salary": {"amount": 1234567, "currency": "NGN"}, "allowances": [{"code": "no_payee_destination", "amount": {"amount": 1234567, "currency": "NGN"}}]},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollPayProfilesUpdate($idempotencyKey, json_decode('{"effective_from":"2026-09-01","base_salary":{"amount":1234567,"currency":"NGN"},"allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"effective_from":"2026-09-01","base_salary":{"amount":1234567,"currency":"NGN"},"allowances":[{"code":"no_payee_destination","amount":{"amount":1234567,"currency":"NGN"}}]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollPayProfilesUpdate("run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("PATCH", HttpRequest.BodyPublishers.ofString("""
{
"effective_from": "2026-09-01",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
"""))
.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 RUNApi(config);
var result = api.PayrollPayProfilesUpdate(payProfileId: "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"effective_from": "2026-09-01",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}
""", 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.RUNAPI.PayrollPayProfilesUpdate(ctx, "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).RunPayProfileUpdateRequest(body).Execute()body := strings.NewReader(`{
"effective_from": "2026-09-01",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
]
}`)
req, _ := http.NewRequest("PATCH", "https://sandbox.droomwork.io/v1/pay_profiles/run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_pay_profile_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "pay_profile",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"base_salary": {
"amount": 1234567,
"currency": "NGN"
},
"effective_from": "2026-09-01",
"version": 1,
"pay_frequency": "monthly",
"allowances": [
{
"code": "no_payee_destination",
"amount": {
"amount": 1234567,
"currency": "NGN"
}
}
],
"effective_to": "2026-09-01",
"supersedes": "example"
}
/v1/payroll/roster_entries#List roster entries
payroll.roster_entries.list
Who's on your roster, and whether each person currently satisfies the payment gate.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
payable
boolean
optionalPass true to get only the entries that currently satisfy the payment gate. Leave it out to get every entry, payable or not.
Returns
A page of roster entries.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of RosterEntry
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
11 fields of RosterEntry
id
string
requiredThe entry's identifier, from POST /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_ and never changes; pass it as roster_entry_id on this entry's calls and in roster_entry_ids at POST /v1/payroll/runs.
object
always "roster_entry"
requiredAlways roster_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe person on this entry: their subject identifier, starting with sub_, as you sent it at POST /v1/payroll/roster_entries. Their pay profile and payslips carry the same subject_id; it can't change once the entry exists.
engagement_id
string · nullable
optionalThe engagement this person is paid under: its id, starting with rail_engagement_, from POST /v1/rail/engagements. null until you attach one; the payment gate needs a live one, and blocked_by then says no_live_engagement.
pay_profile_id
string · nullable
optionalThe pay profile this person is paid from: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles. null until you attach one; without it the entry isn't payable and blocked_by carries no_pay_profile.
payable
boolean
requiredWhether this person currently satisfies the payment gate. False doesn't mean the record is wrong; something they need is missing, and blocked_by says what.
blocked_by
array of string
optionalEmpty when payable is true.
effective_from
string · date
requiredThe first date this person is on the roster, as a calendar date in YYYY-MM-DD, as you set it when you added them.
effective_to
string · date · nullable
optionalThe last date this person is on the roster, as YYYY-MM-DD, or null while they have no end date. A leaver is an end date, set at PATCH /v1/payroll/roster_entries/{roster_entry_id}; there is no delete.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), payable (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), payable (optional)
const result = await api.payrollRosterEntriesList({ limit: 25, payable: true });// query parameters: limit (optional), starting_after (optional), payable (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true', {
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.RUNApi(client)
# query parameters: limit (optional), starting_after (optional), payable (optional)
result = api.payroll_roster_entries_list(limit=25, payable=true)import os
import requests
# query parameters: limit (optional), starting_after (optional), payable (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true',
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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), payable (optional)
$result = $api->payrollRosterEntriesList(limit: 25, payable: true);<?php
// query parameters: limit (optional), starting_after (optional), payable (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), starting_after (optional), payable (optional)
var result = api.payrollRosterEntriesList(25, null, true);// query parameters: limit (optional), starting_after (optional), payable (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true"))
.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 RUNApi(config);
// query parameters: limit (optional), starting_after (optional), payable (optional)
var result = api.PayrollRosterEntriesList(limit: 25, payable: true);// query parameters: limit (optional), starting_after (optional), payable (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true");
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), payable (optional)
result, _, err := client.RUNAPI.PayrollRosterEntriesList(ctx).Limit(25).Payable(true).Execute()// query parameters: limit (optional), starting_after (optional), payable (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/roster_entries?limit=25&payable=true", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_roster_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "roster_entry",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payable": true,
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"blocked_by": [
"no_anchored_identity"
],
"effective_to": "2026-09-01"
}
],
"has_more": true
}
/v1/payroll/roster_entries#Add someone to the roster
payroll.roster_entries.create
Adds someone to the roster. Being on the roster doesn't make them payable: the gate is checked at execution, and the entry tells you whether they pass it.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_id
string
requiredThe person to add: the subject_ref (sub_…) you first sent at POST /v1/identity/consent_tokens, the same subject_id as on their pay profile. Being on the roster doesn't make them payable; read payable on the entry you get back.
engagement_id
string
optionalThe engagement this person is paid under: its id, starting with rail_engagement_, from POST /v1/rail/engagements. Optional now, but the payment gate needs a live one at execution; read blocked_by on the entry you get back.
pay_profile_id
string
optionalThe pay profile to pay this person from: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles or GET /v1/pay_profiles. Leave it out to attach one later; until then the entry reports no_pay_profile.
effective_from
string · date
requiredThe first date this person is on the roster, as a calendar date in YYYY-MM-DD. It can't be changed afterwards; an update changes the pay profile or sets an end date.
Returns
The roster entry.
id
string
requiredThe entry's identifier, from POST /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_ and never changes; pass it as roster_entry_id on this entry's calls and in roster_entry_ids at POST /v1/payroll/runs.
object
always "roster_entry"
requiredAlways roster_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe person on this entry: their subject identifier, starting with sub_, as you sent it at POST /v1/payroll/roster_entries. Their pay profile and payslips carry the same subject_id; it can't change once the entry exists.
engagement_id
string · nullable
optionalThe engagement this person is paid under: its id, starting with rail_engagement_, from POST /v1/rail/engagements. null until you attach one; the payment gate needs a live one, and blocked_by then says no_live_engagement.
pay_profile_id
string · nullable
optionalThe pay profile this person is paid from: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles. null until you attach one; without it the entry isn't payable and blocked_by carries no_pay_profile.
payable
boolean
requiredWhether this person currently satisfies the payment gate. False doesn't mean the record is wrong; something they need is missing, and blocked_by says what.
blocked_by
array of string
optionalEmpty when payable is true.
effective_from
string · date
requiredThe first date this person is on the roster, as a calendar date in YYYY-MM-DD, as you set it when you added them.
effective_to
string · date · nullable
optionalThe last date this person is on the roster, as YYYY-MM-DD, or null while they have no end date. A leaver is an end date, set at PATCH /v1/payroll/roster_entries/{roster_entry_id}; there is no delete.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/roster_entries" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_from":"2026-09-01","engagement_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","pay_profile_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRosterEntriesCreate({
idempotencyKey: crypto.randomUUID(),
runRosterEntryCreateRequest: {"subjectId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effectiveFrom":"2026-09-01","engagementId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","payProfileId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/roster_entries', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_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.RUNApi(client)
result = api.payroll_roster_entries_create(body={"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "effective_from": "2026-09-01", "engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/roster_entries',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "effective_from": "2026-09-01", "engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "pay_profile_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRosterEntriesCreate($idempotencyKey, json_decode('{"subject_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_from":"2026-09-01","engagement_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","pay_profile_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/roster_entries');
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_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_from":"2026-09-01","engagement_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","pay_profile_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRosterEntriesCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/roster_entries"))
.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_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_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 RUNApi(config);
var result = api.PayrollRosterEntriesCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/roster_entries");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_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.RUNAPI.PayrollRosterEntriesCreate(ctx).IdempotencyKey(key).RunRosterEntryCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/roster_entries", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_roster_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "roster_entry",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payable": true,
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"blocked_by": [
"no_anchored_identity"
],
"effective_to": "2026-09-01"
}
/v1/payroll/roster_entries/{roster_entry_id}#Retrieve a roster entry
payroll.roster_entries.retrieve
One roster entry, including what's blocking payment if anything is.
Path parameters
roster_entry_id
string
requiredThe roster entry's identifier: the id from POST /v1/payroll/roster_entries or GET /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_.
Returns
The roster entry, including why it is or is not payable.
id
string
requiredThe entry's identifier, from POST /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_ and never changes; pass it as roster_entry_id on this entry's calls and in roster_entry_ids at POST /v1/payroll/runs.
object
always "roster_entry"
requiredAlways roster_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe person on this entry: their subject identifier, starting with sub_, as you sent it at POST /v1/payroll/roster_entries. Their pay profile and payslips carry the same subject_id; it can't change once the entry exists.
engagement_id
string · nullable
optionalThe engagement this person is paid under: its id, starting with rail_engagement_, from POST /v1/rail/engagements. null until you attach one; the payment gate needs a live one, and blocked_by then says no_live_engagement.
pay_profile_id
string · nullable
optionalThe pay profile this person is paid from: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles. null until you attach one; without it the entry isn't payable and blocked_by carries no_pay_profile.
payable
boolean
requiredWhether this person currently satisfies the payment gate. False doesn't mean the record is wrong; something they need is missing, and blocked_by says what.
blocked_by
array of string
optionalEmpty when payable is true.
effective_from
string · date
requiredThe first date this person is on the roster, as a calendar date in YYYY-MM-DD, as you set it when you added them.
effective_to
string · date · nullable
optionalThe last date this person is on the roster, as YYYY-MM-DD, or null while they have no end date. A leaver is an end date, set at PATCH /v1/payroll/roster_entries/{roster_entry_id}; there is no delete.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRosterEntriesRetrieve({ rosterEntryId: '{roster_entry_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_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.RUNApi(client)
result = api.payroll_roster_entries_retrieve(roster_entry_id='{roster_entry_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRosterEntriesRetrieve(roster_entry_id: '{roster_entry_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRosterEntriesRetrieve("{roster_entry_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_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 RUNApi(config);
var result = api.PayrollRosterEntriesRetrieve(rosterEntryId: "{roster_entry_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_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.RUNAPI.PayrollRosterEntriesRetrieve(ctx, "{roster_entry_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_roster_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "roster_entry",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payable": true,
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"blocked_by": [
"no_anchored_identity"
],
"effective_to": "2026-09-01"
}
/v1/payroll/roster_entries/{roster_entry_id}#Update a roster entry
payroll.roster_entries.update
Change the pay profile or set an end date.
Path parameters
roster_entry_id
string
requiredThe roster entry's identifier: the id from POST /v1/payroll/roster_entries or GET /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
pay_profile_id
string
optionalThe pay profile to pay this person from now on: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles. Send it to move them onto a different profile; leave it out when you're only setting an end date.
effective_to
string · date
optionalA leaver is an end date. There is no delete.
Returns
The updated entry.
id
string
requiredThe entry's identifier, from POST /v1/payroll/roster_entries. It starts with run_enterprise_roster_entry_ and never changes; pass it as roster_entry_id on this entry's calls and in roster_entry_ids at POST /v1/payroll/runs.
object
always "roster_entry"
requiredAlways roster_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_id
string
requiredThe person on this entry: their subject identifier, starting with sub_, as you sent it at POST /v1/payroll/roster_entries. Their pay profile and payslips carry the same subject_id; it can't change once the entry exists.
engagement_id
string · nullable
optionalThe engagement this person is paid under: its id, starting with rail_engagement_, from POST /v1/rail/engagements. null until you attach one; the payment gate needs a live one, and blocked_by then says no_live_engagement.
pay_profile_id
string · nullable
optionalThe pay profile this person is paid from: its id, starting with run_enterprise_pay_profile_, from POST /v1/pay_profiles. null until you attach one; without it the entry isn't payable and blocked_by carries no_pay_profile.
payable
boolean
requiredWhether this person currently satisfies the payment gate. False doesn't mean the record is wrong; something they need is missing, and blocked_by says what.
blocked_by
array of string
optionalEmpty when payable is true.
effective_from
string · date
requiredThe first date this person is on the roster, as a calendar date in YYYY-MM-DD, as you set it when you added them.
effective_to
string · date · nullable
optionalThe last date this person is on the roster, as YYYY-MM-DD, or null while they have no end date. A leaver is an end date, set at PATCH /v1/payroll/roster_entries/{roster_entry_id}; there is no delete.
Other responses
Errors it can return
curl -X PATCH "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pay_profile_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_to":"2026-09-01"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollRosterEntriesUpdate({
rosterEntryId: '{roster_entry_id}',
idempotencyKey: crypto.randomUUID(),
runRosterEntryUpdateRequest: {"payProfileId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effectiveTo":"2026-09-01"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D', {
method: 'PATCH',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_to": "2026-09-01"
}),
});
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.RUNApi(client)
result = api.payroll_roster_entries_update(roster_entry_id='{roster_entry_id}', body={"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "effective_to": "2026-09-01"})import os
import uuid
import requests
response = requests.request(
'PATCH',
'https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "effective_to": "2026-09-01"},
)
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollRosterEntriesUpdate($idempotencyKey, json_decode('{"pay_profile_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_to":"2026-09-01"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"pay_profile_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","effective_to":"2026-09-01"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollRosterEntriesUpdate("{roster_entry_id}", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("PATCH", HttpRequest.BodyPublishers.ofString("""
{
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_to": "2026-09-01"
}
"""))
.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 RUNApi(config);
var result = api.PayrollRosterEntriesUpdate(rosterEntryId: "{roster_entry_id}", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_to": "2026-09-01"
}
""", 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.RUNAPI.PayrollRosterEntriesUpdate(ctx, "{roster_entry_id}").IdempotencyKey(key).RunRosterEntryUpdateRequest(body).Execute()body := strings.NewReader(`{
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"effective_to": "2026-09-01"
}`)
req, _ := http.NewRequest("PATCH", "https://sandbox.droomwork.io/v1/payroll/roster_entries/%7Broster_entry_id%7D", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_roster_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "roster_entry",
"livemode": true,
"mocked": true,
"subject_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"payable": true,
"effective_from": "2026-09-01",
"engagement_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"pay_profile_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"blocked_by": [
"no_anchored_identity"
],
"effective_to": "2026-09-01"
}
/v1/payroll/imports#List bulk imports
payroll.imports.list
Your previous bulk imports and their validation reports.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of imports.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Import
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
8 fields of Import
id
string
requiredThe import's identifier, from POST /v1/payroll/imports. It starts with run_enterprise_import_ and never changes; pass it as import_id at GET /v1/payroll/imports/{import_id} to read the report again.
object
always "payroll_import"
requiredAlways payroll_import. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
kind
string
requiredWhat the file holds, as you sent it as kind at POST /v1/payroll/imports: roster, the people you pay, or pay_profiles, how each of them is paid. It decides how every row is checked and what an accepted import creates.
rosterpay_profilesstatus
string
requiredvalidating while rows are checked, pending_acceptance once the report is ready for you to read, accepted once the rows are committed, rejected when they won't be. Nothing is committed before accepted.
validatingpending_acceptanceacceptedrejectedvalidation_report
ValidationReport
requiredThe result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.
4 fields of ValidationReport
entry_count
integer · minimum 0
requiredHow many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.
error_count
integer · minimum 0
requiredHow many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.
warning_count
integer · minimum 0
requiredHow many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.
entries
array of object
optionalOne item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.
4 fields
index
integer · minimum 1
requiredThe row in a file, or the line in an instruction set.
severity
string
requirederror means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.
errorwarningcode
string
requiredWhy the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.
detail
string
requiredWhat was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/imports?limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional)
const result = await api.payrollImportsList({ limit: 25 });// query parameters: limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/payroll/imports?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.RUNApi(client)
# query parameters: limit (optional), starting_after (optional)
result = api.payroll_imports_list(limit=25)import os
import requests
# query parameters: limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/imports?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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional)
$result = $api->payrollImportsList(limit: 25);<?php
// query parameters: limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/imports?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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: limit (optional), starting_after (optional)
var result = api.payrollImportsList(25, null);// query parameters: limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/imports?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 RUNApi(config);
// query parameters: limit (optional), starting_after (optional)
var result = api.PayrollImportsList(limit: 25);// query parameters: limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/imports?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), starting_after (optional)
result, _, err := client.RUNAPI.PayrollImportsList(ctx).Limit(25).Execute()// query parameters: limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/imports?limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "run_enterprise_import_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_import",
"livemode": true,
"mocked": true,
"kind": "roster",
"status": "validating",
"validation_report": {
"entry_count": 0,
"error_count": 0,
"warning_count": 0,
"entries": [
{
"index": 1,
"severity": "error",
"code": "no_payee_destination",
"detail": "The payee has no verified destination, so this line cannot be paid."
}
]
},
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/payroll/imports#Import a roster or pay profile file
payroll.imports.create
Send CSV or XLSX. You get a report naming every bad row, not just the first, so one upload tells you everything you have to fix.
Nothing is committed until the import is accepted.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The import, with its validation report.
id
string
requiredThe import's identifier, from POST /v1/payroll/imports. It starts with run_enterprise_import_ and never changes; pass it as import_id at GET /v1/payroll/imports/{import_id} to read the report again.
object
always "payroll_import"
requiredAlways payroll_import. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
kind
string
requiredWhat the file holds, as you sent it as kind at POST /v1/payroll/imports: roster, the people you pay, or pay_profiles, how each of them is paid. It decides how every row is checked and what an accepted import creates.
rosterpay_profilesstatus
string
requiredvalidating while rows are checked, pending_acceptance once the report is ready for you to read, accepted once the rows are committed, rejected when they won't be. Nothing is committed before accepted.
validatingpending_acceptanceacceptedrejectedvalidation_report
ValidationReport
requiredThe result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.
4 fields of ValidationReport
entry_count
integer · minimum 0
requiredHow many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.
error_count
integer · minimum 0
requiredHow many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.
warning_count
integer · minimum 0
requiredHow many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.
entries
array of object
optionalOne item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.
4 fields
index
integer · minimum 1
requiredThe row in a file, or the line in an instruction set.
severity
string
requirederror means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.
errorwarningcode
string
requiredWhy the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.
detail
string
requiredWhat was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/imports" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollImportsCreate({});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/imports', {
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.RUNApi(client)
result = api.payroll_imports_create()import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/imports',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollImportsCreate();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/imports');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollImportsCreate();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/imports"))
.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 RUNApi(config);
var result = api.PayrollImportsCreate();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/imports");
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.RUNAPI.PayrollImportsCreate(ctx).Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/imports", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_import_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_import",
"livemode": true,
"mocked": true,
"kind": "roster",
"status": "validating",
"validation_report": {
"entry_count": 0,
"error_count": 0,
"warning_count": 0,
"entries": [
{
"index": 1,
"severity": "error",
"code": "no_payee_destination",
"detail": "The payee has no verified destination, so this line cannot be paid."
}
]
},
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/imports/{import_id}#Retrieve an import and its validation report
payroll.imports.retrieve
The import, with the report naming every row that failed and why.
Path parameters
import_id
string
requiredThe import's identifier: the id from POST /v1/payroll/imports or GET /v1/payroll/imports. It starts with run_enterprise_import_.
Returns
The import.
id
string
requiredThe import's identifier, from POST /v1/payroll/imports. It starts with run_enterprise_import_ and never changes; pass it as import_id at GET /v1/payroll/imports/{import_id} to read the report again.
object
always "payroll_import"
requiredAlways payroll_import. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
kind
string
requiredWhat the file holds, as you sent it as kind at POST /v1/payroll/imports: roster, the people you pay, or pay_profiles, how each of them is paid. It decides how every row is checked and what an accepted import creates.
rosterpay_profilesstatus
string
requiredvalidating while rows are checked, pending_acceptance once the report is ready for you to read, accepted once the rows are committed, rejected when they won't be. Nothing is committed before accepted.
validatingpending_acceptanceacceptedrejectedvalidation_report
ValidationReport
requiredThe result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.
4 fields of ValidationReport
entry_count
integer · minimum 0
requiredHow many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.
error_count
integer · minimum 0
requiredHow many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.
warning_count
integer · minimum 0
requiredHow many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.
entries
array of object
optionalOne item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.
4 fields
index
integer · minimum 1
requiredThe row in a file, or the line in an instruction set.
severity
string
requirederror means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.
errorwarningcode
string
requiredWhy the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.
detail
string
requiredWhat was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollImportsRetrieve({ importId: '{import_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_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.RUNApi(client)
result = api.payroll_imports_retrieve(import_id='{import_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollImportsRetrieve(import_id: '{import_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollImportsRetrieve("{import_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_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 RUNApi(config);
var result = api.PayrollImportsRetrieve(importId: "{import_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_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.RUNAPI.PayrollImportsRetrieve(ctx, "{import_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/imports/%7Bimport_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_import_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "payroll_import",
"livemode": true,
"mocked": true,
"kind": "roster",
"status": "validating",
"validation_report": {
"entry_count": 0,
"error_count": 0,
"warning_count": 0,
"entries": [
{
"index": 1,
"severity": "error",
"code": "no_payee_destination",
"detail": "The payee has no verified destination, so this line cannot be paid."
}
]
},
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/instruction_set#Retrieve the statutory instruction set for a run
payroll.instruction_sets.retrieve
What you owe to whom, grouped the way each authority needs it. PAYE by the State Internal Revenue Service of the employee's residence, pension by administrator and retirement savings account number, and totals for NHF, NSITF, ITF and NHIA, each with a reconciliation reference.
Hand it to REMIT. Its totals reconcile to run totals to the kobo.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Returns
The instruction set.
id
string
requiredThe instruction set's identifier, which never changes: read it at GET /v1/payroll/runs/{run_id}/instruction_set when Droomwork ran the payroll, and send the set on unchanged to POST /v1/remittance/instruction_sets. Quote it when you ask about the set.
object
always "instruction_set"
requiredAlways instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe payroll run this set is owed for, by its id from POST /v1/payroll/runs, which starts with run_enterprise_. The set's totals reconcile to that run's totals to the kobo.
period
string
optionalThe pay period the amounts are owed for, as year and month, for example 2026-09.
paye
array of object
requiredGrouped by the State IRS of the employee's residence, not the employer's location.
5 fields
authority_id
string
requiredThe State Internal Revenue Service this PAYE is owed to, by its id from GET /v1/remittance/authorities, which starts with remit_authority_rail_obligation_. Use it wherever a call names the authority.
jurisdiction
string
requiredThe State Internal Revenue Service this line is grouped under, named in full, for example Rivers State Internal Revenue Service. Read it to label the line; authority_id names the authority in a call.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
payee_count
integer
optionalHow many employees this line's PAYE covers. Optional; when it is present, check it against your own headcount for that state.
reconciliation_reference
string
requiredThe reference that identifies this line when you reconcile it, for example paye-2026-09-rivers. Keep it with your own records so you can trace the line later.
pension
array of object
requiredGrouped by administrator and retirement savings account number.
6 fields
administrator_id
string
requiredThe Pension Fund Administrator these contributions are owed to, by its id from GET /v1/remittance/authorities with kind=pension_administrator, which starts with remit_authority_rail_obligation_. Use it wherever a call names the administrator.
administrator_name
string
optionalThe administrator's name in full, so you can label the line without a second call. Optional; administrator_id is what names the administrator in a call.
employee_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
employer_amount
Money
optional2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
amount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
reconciliation_reference
string
requiredThe reference that identifies this line when you reconcile it. Keep it with your own records so you can trace the line later.
levies
array of object
requiredNHF, NSITF, ITF and NHIA totals, each with a reconciliation reference.
3 fields
code
string
requiredWhich levy this line is for: nhf the National Housing Fund, nsitf the Nigeria Social Insurance Trust Fund, itf the Industrial Training Fund, nhia the National Health Insurance Authority. One line per levy.
nhfnsitfitfnhiaamount
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
reconciliation_reference
string
requiredThe reference that identifies this levy line when you reconcile it. Keep it with your own records so you can trace the line later.
totals
object
requiredThe set's grand total, which its lines sum to. From a Droomwork run it reconciles to the run's totals to the kobo.
1 field
total
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollInstructionSetsRetrieve({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set', {
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.RUNApi(client)
result = api.payroll_instruction_sets_retrieve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set',
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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollInstructionSetsRetrieve(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set');
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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollInstructionSetsRetrieve("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set"))
.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 RUNApi(config);
var result = api.PayrollInstructionSetsRetrieve(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set");
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.RUNAPI.PayrollInstructionSetsRetrieve(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/instruction_set", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "instruction_set",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"paye": [
{
"authority_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"jurisdiction": "Rivers State Internal Revenue Service",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"reconciliation_reference": "paye-2026-09-rivers",
"payee_count": 1
}
],
"pension": [
{
"administrator_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"reconciliation_reference": "paye-2026-09-rivers",
"administrator_name": "Rivers State Internal Revenue Service",
"employee_amount": {
"amount": 1234567,
"currency": "NGN"
},
"employer_amount": {
"amount": 1234567,
"currency": "NGN"
}
}
],
"levies": [
{
"code": "nhf",
"amount": {
"amount": 1234567,
"currency": "NGN"
},
"reconciliation_reference": "paye-2026-09-rivers"
}
],
"totals": {
"total": {
"amount": 1234567,
"currency": "NGN"
}
},
"period": "2026-09",
"created_at": "2026-09-01T09:00:00Z"
}
/v1/payroll/runs/{run_id}/gl_export#Produce a general ledger export for a run
payroll.gl_exports.create
Produces a general ledger export in the format your accounting system reads.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
format
string
requiredThe shape your accounting system reads: csv or json for a plain file, xero or quickbooks for a file shaped for that system to import. You get it back as format on the export.
csvjsonxeroquickbookschart_of_accounts_id
string
optionalYour own identifier for the chart of accounts in your accounting system, when it needs the journal lines coded to your account codes. It's yours, not one we issue; leave it out when the lines don't need your codes.
Returns
The export.
id
string
requiredThe export's identifier, from POST /v1/payroll/runs/{run_id}/gl_export. It starts with run_enterprise_gl_export_ and never changes; pass it as gl_export_id, with the same run_id, to fetch the export again.
object
always "gl_export"
requiredAlways gl_export. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe run this export was produced from: its id, starting with run_enterprise_, from POST /v1/payroll/runs, as you named it in the path when you produced the export. Its totals reconcile to that run's totals to the kobo.
format
string
requiredThe format you asked for as format at POST /v1/payroll/runs/{run_id}/gl_export: csv, json, xero or quickbooks. It's what the file at download_url is in.
csvjsonxeroquickbooksstatus
string
requiredgenerating while the file is being produced, ready once download_url points at it, failed if it couldn't be produced. Fetch the export again at GET /v1/payroll/runs/{run_id}/gl_export/{gl_export_id} until it leaves generating.
generatingreadyfaileddownload_url
string · uri · nullable
optionalWhere to fetch the file, as a URL, once status is ready. null before that and when the export failed.
totals
object
requiredThe export's grand totals: what it debits, what it credits, and whether the two agree. Both sides reconcile to the run's totals to the kobo.
3 fields
debits
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
credits
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
balanced
boolean
requiredDebits equal credits, and both reconcile to run totals to the kobo.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"format":"csv","chart_of_accounts_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollGlExportsCreate({
runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
idempotencyKey: crypto.randomUUID(),
runGlExportCreateRequest: {"format":"csv","chartOfAccountsId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"format": "csv",
"chart_of_accounts_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.RUNApi(client)
result = api.payroll_gl_exports_create(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"format": "csv", "chart_of_accounts_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"format": "csv", "chart_of_accounts_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollGlExportsCreate($idempotencyKey, json_decode('{"format":"csv","chart_of_accounts_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export');
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 => '{"format":"csv","chart_of_accounts_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollGlExportsCreate("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export"))
.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("""
{
"format": "csv",
"chart_of_accounts_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 RUNApi(config);
var result = api.PayrollGlExportsCreate(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"format": "csv",
"chart_of_accounts_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.RUNAPI.PayrollGlExportsCreate(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).RunGlExportCreateRequest(body).Execute()body := strings.NewReader(`{
"format": "csv",
"chart_of_accounts_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "gl_export",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"format": "csv",
"status": "generating",
"totals": {
"debits": {
"amount": 1234567,
"currency": "NGN"
},
"credits": {
"amount": 1234567,
"currency": "NGN"
},
"balanced": true
},
"download_url": "https://files.sandbox.droomwork.com/example"
}
/v1/payroll/runs/{run_id}/gl_export/{gl_export_id}#Retrieve a general ledger export
payroll.gl_exports.retrieve
Your export. It reconciles to run totals to the kobo.
Path parameters
run_id
string
requiredThe run's identifier, from the id of the response to POST /v1/payroll/runs or of a run on GET /v1/payroll/runs. It starts with run_enterprise_.
gl_export_id
string
requiredThe export's identifier: the id from the response to POST /v1/payroll/runs/{run_id}/gl_export. It starts with run_enterprise_gl_export_; pair it with the same run_id you produced the export under.
Returns
The export.
id
string
requiredThe export's identifier, from POST /v1/payroll/runs/{run_id}/gl_export. It starts with run_enterprise_gl_export_ and never changes; pass it as gl_export_id, with the same run_id, to fetch the export again.
object
always "gl_export"
requiredAlways gl_export. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
run_id
string
requiredThe run this export was produced from: its id, starting with run_enterprise_, from POST /v1/payroll/runs, as you named it in the path when you produced the export. Its totals reconcile to that run's totals to the kobo.
format
string
requiredThe format you asked for as format at POST /v1/payroll/runs/{run_id}/gl_export: csv, json, xero or quickbooks. It's what the file at download_url is in.
csvjsonxeroquickbooksstatus
string
requiredgenerating while the file is being produced, ready once download_url points at it, failed if it couldn't be produced. Fetch the export again at GET /v1/payroll/runs/{run_id}/gl_export/{gl_export_id} until it leaves generating.
generatingreadyfaileddownload_url
string · uri · nullable
optionalWhere to fetch the file, as a URL, once status is ready. null before that and when the export failed.
totals
object
requiredThe export's grand totals: what it debits, what it credits, and whether the two agree. Both sides reconcile to the run's totals to the kobo.
3 fields
debits
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
credits
Money
required2 fields of Money
amount
integer · int64
requiredA whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.
currency
string
requiredISO 4217 code.
balanced
boolean
requiredDebits equal credits, and both reconcile to run totals to the kobo.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollGlExportsRetrieve({ runId: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', glExportId: '{gl_export_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_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.RUNApi(client)
result = api.payroll_gl_exports_retrieve(run_id='run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', gl_export_id='{gl_export_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollGlExportsRetrieve(run_id: 'run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', gl_export_id: '{gl_export_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollGlExportsRetrieve("run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{gl_export_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_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 RUNApi(config);
var result = api.PayrollGlExportsRetrieve(runId: "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", glExportId: "{gl_export_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_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.RUNAPI.PayrollGlExportsRetrieve(ctx, "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{gl_export_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/runs/run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/gl_export/%7Bgl_export_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "gl_export",
"livemode": true,
"mocked": true,
"run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"format": "csv",
"status": "generating",
"totals": {
"debits": {
"amount": 1234567,
"currency": "NGN"
},
"credits": {
"amount": 1234567,
"currency": "NGN"
},
"balanced": true
},
"download_url": "https://files.sandbox.droomwork.com/example"
}
/v1/payroll/readiness#What RUN needs, what you have, and what is missing
payroll.readiness.retrieve
RUN needs an anchored identity and a live engagement for every payee. You can satisfy that through ANCHOR and RAIL, or by supplying the facts yourself under an attestation; what was relied on is recorded on the fact.
Call this before your first run, so you don't discover a missing prerequisite at execution.
Returns
The readiness report.
object
always "readiness_report"
requiredAlways readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.
module
string
requiredThe module whose readiness endpoint you called, such as GET /v1/payroll/readiness: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance) or route (payouts).
anchorproofrailflowmatchrunremitroutemode
string
requiredintegrated means a required fact comes from the Droomwork module that owns it; standalone means you supply it yourself under an attestation. The checks are the same in both, and what each fact is worth is recorded on its row as held.
integratedstandaloneready
boolean
requiredtrue when every row is satisfied and the module has what it needs from you. false when a required fact is missing or held at too low an assurance; rows says which.
rows
array of ReadinessRow
requiredOne row per fact the module requires: who owns it, the assurance it needs, what you hold and whether that satisfies it. The rows that are not satisfied are what to bring.
8 fields of ReadinessRow
fact
string
requiredNamed for what it is, not for who supplies it.
owner
string
requiredThe sibling module that owns this fact when running integrated.
contract_row
string
optionalrequired
string
requiredHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedheld
one of
requiredAssuranceorsource
string · nullable
optionalintegratedstandalonenullsatisfied
boolean
requiredmissing_because
string · nullable
optionalnot_suppliedassurance_too_lownullalways_enforced
array of string
optionalGates that hold in every mode and cannot be attested away. Consent before any source is queried, bank account validation, the tax identifiers returns are filed under, and duplicate detection within the organisation's own population.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/readiness" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollReadinessRetrieve({});const response = await fetch('https://sandbox.droomwork.io/v1/payroll/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.RUNApi(client)
result = api.payroll_readiness_retrieve()import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollReadinessRetrieve();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollReadinessRetrieve();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/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 RUNApi(config);
var result = api.PayrollReadinessRetrieve();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/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.RUNAPI.PayrollReadinessRetrieve(ctx).Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/readiness", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "readiness_report",
"module": "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"
]
}
/v1/payroll/events#List events
payroll.events.list
The append only record of everything RUN 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, because the sequence counts per organisation and per stream. Without stream, you get events across streams in the order they were recorded, paged with starting_after.
Query parameters
stream
string
optionalThe id of the record whose history you want: a run's (run_enterprise_…, from POST /v1/payroll/runs) or a payslip's (run_enterprise_payslip_…, from GET /v1/payroll/payslips). Leave it out to page every stream with starting_after.
after
integer
optionalOnly events whose sequence is above this number in the stream you named, so pass the last sequence you handled; 0, or leaving it out, reads from the start. Needs stream beside it, because a sequence counts within one stream.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of events.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of StoredEvent
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of StoredEvent
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe stream the event belongs to: the id of the record it's about, a run's (run_enterprise_…) or a payslip's. Pass it as stream at GET /v1/payroll/events to replay it; sequence counts per stream and means nothing without this.
data
object
requiredThe record the event is about, in the shape its type names: a run, a finding, a payslip or an instruction set. The same body a webhook delivery carries.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/events?stream=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(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.payrollEventsList({ stream: 'run_enterprise_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/payroll/events?stream=run_enterprise_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.RUNApi(client)
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result = api.payroll_events_list(stream='run_enterprise_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/payroll/events?stream=run_enterprise_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\RUNApi(new GuzzleHttp\Client(), $config);
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$result = $api->payrollEventsList(stream: 'run_enterprise_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/payroll/events?stream=run_enterprise_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.payrollEventsList("run_enterprise_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/payroll/events?stream=run_enterprise_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 RUNApi(config);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.PayrollEventsList(stream: "run_enterprise_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/payroll/events?stream=run_enterprise_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.RUNAPI.PayrollEventsList(ctx).Stream("run_enterprise_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/payroll/events?stream=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": true,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
],
"has_more": true
}
/v1/payroll/events/{event_id}#Retrieve an event
payroll.events.retrieve
One event. An identifier belonging to another organisation comes back as not found, not refused.
Path parameters
event_id
string
requiredThe event's identifier: the id of an event you listed at GET /v1/payroll/events or received on a webhook delivery. It starts with evt_.
Returns
The event.
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe stream the event belongs to: the id of the record it's about, a run's (run_enterprise_…) or a payslip's. Pass it as stream at GET /v1/payroll/events to replay it; sequence counts per stream and means nothing without this.
data
object
requiredThe record the event is about, in the shape its type names: a run, a finding, a payslip or an instruction set. The same body a webhook delivery carries.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/events/%7Bevent_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollEventsRetrieve({ eventId: '{event_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/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.RUNApi(client)
result = api.payroll_events_retrieve(event_id='{event_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollEventsRetrieve(event_id: '{event_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollEventsRetrieve("{event_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/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 RUNApi(config);
var result = api.PayrollEventsRetrieve(eventId: "{event_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/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.RUNAPI.PayrollEventsRetrieve(ctx, "{event_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/events/%7Bevent_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": true,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
/v1/payroll/audit_entries#List audit entries
payroll.audit_entries.list
Who did what, newest first. One row per attempt, not per success: refusals are recorded too. A read that succeeded is not recorded.
Query parameters
action
string
optionalOnly attempts at one operation, matched to action on each entry: the method and route pattern, such as POST /v1/payroll/runs. Leave it out to get attempts at every operation.
actor_id
string
optionalOnly attempts by one actor, matched to actor_id on each entry: an API key's id (api_key_…, from POST /v1/api_keys), an OAuth client_id, a signed-in person's id (usr_…) or a staff member's. Leave it out to get every actor.
resource
string
optionalOnly attempts on one kind of record, matched to resource on each entry: the collection segment of the route, such as runs or payslips. Pair it with resource_id to narrow to one record; leave it out to get every kind.
resource_id
string
optionalOnly attempts on one record, matched to resource_id on each entry: the record's id as it appeared in the route, such as a run's (run_enterprise_…, from POST /v1/payroll/runs). Pair it with resource; leave it out for every record.
outcome
string
optionalOnly attempts that ended one way, as outcome reads on each entry: succeeded (a status below 400), refused (a 4xx, the request was turned down) or failed (a 5xx, it went wrong on our side). Leave it out to get all three.
succeededrefusedfailedrecorded_after
string
optionalOnly entries whose at is after this moment, as an RFC 3339 timestamp in UTC; exclusive, so an entry at exactly this instant is left out. Leave it out to reach back to the oldest entry.
recorded_before
string
optionalOnly entries whose at is before this moment, as an RFC 3339 timestamp in UTC; exclusive, so an entry at exactly this instant is left out. Pair it with recorded_after to bound a window, or leave it out to run up to the newest entry.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of audit entries.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of AuditEntry
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of AuditEntry
id
string
requiredThe entry's identifier, as GET /v1/payroll/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/payroll/audit_entries/{audit_entry_id}, or as starting_after to page past it.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredWhether the route this attempt was aimed at is mocked. It describes the route, not the answer: a refused attempt and a replayed idempotent request are both recorded as mocked when the route is. It is never derived from livemode.
at
string · date-time
requiredWhen the attempt was recorded, as an RFC 3339 timestamp in UTC. The list orders entries by it, newest first, and recorded_after and recorded_before bound it.
request_id
string
requiredThe request that made this attempt. It starts with req_ and is the same request_id an error body carries, so a refusal you were shown can be matched to its entry here.
actor_type
string
requiredWhat kind of credential acted: 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 Droomwork acted for you.
actor_id
string
requiredWho acted, by actor_type: for client your API key's id (key_…, from GET /v1/api_keys) or OAuth client_id; for user the person's id (usr_…); for staff theirs; for service a name. Pass it as actor_id on the list to follow one.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection name in the route, such as runs or payslips. null when the route named none.
resource_id
string · nullable
optionalThe id of the record the route named, such as a run's (run_enterprise_…, from POST /v1/payroll/runs) or a payslip's (run_enterprise_payslip_…, from GET /v1/payroll/payslips). null when the route named none, as on a create or a list.
outcome
string
requiredHow the attempt ended: succeeded for a status below 400, refused for a 4xx, failed for a 5xx. Pass refused as outcome on GET /v1/payroll/audit_entries to see every attempt that was turned away.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. outcome is read from it, so the two never disagree.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(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.payrollAuditEntriesList({ action: 'POST /v1/payroll/runs', actorId: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'runs', resourceId: 'run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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.RUNApi(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.payroll_audit_entries_list(action='POST /v1/payroll/runs', actor_id='usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource='runs', resource_id='run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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\RUNApi(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->payrollAuditEntriesList(action: 'POST /v1/payroll/runs', actor_id: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'runs', resource_id: 'run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(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.payrollAuditEntriesList("POST /v1/payroll/runs", "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "runs", "run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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 RUNApi(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.PayrollAuditEntriesList(action: "POST /v1/payroll/runs", actorId: "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", resource: "runs", resourceId: "run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_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.RUNAPI.PayrollAuditEntriesList(ctx).Action("POST /v1/payroll/runs").ActorId("usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Resource("runs").ResourceId("run_enterprise_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/payroll/audit_entries?action=POST%20%2Fv1%2Fpayroll%2Fruns&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=runs&resource_id=run_enterprise_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": 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
}
/v1/payroll/audit_entries/{audit_entry_id}#Retrieve an audit entry
payroll.audit_entries.retrieve
One entry. An identifier belonging to another organisation comes back as not found, not refused.
Path parameters
audit_entry_id
string
requiredThe entry's identifier, from the id of an entry you listed at GET /v1/payroll/audit_entries. It starts with audit_entry_.
Returns
The audit entry.
id
string
requiredThe entry's identifier, as GET /v1/payroll/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/payroll/audit_entries/{audit_entry_id}, or as starting_after to page past it.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredWhether the route this attempt was aimed at is mocked. It describes the route, not the answer: a refused attempt and a replayed idempotent request are both recorded as mocked when the route is. It is never derived from livemode.
at
string · date-time
requiredWhen the attempt was recorded, as an RFC 3339 timestamp in UTC. The list orders entries by it, newest first, and recorded_after and recorded_before bound it.
request_id
string
requiredThe request that made this attempt. It starts with req_ and is the same request_id an error body carries, so a refusal you were shown can be matched to its entry here.
actor_type
string
requiredWhat kind of credential acted: 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 Droomwork acted for you.
actor_id
string
requiredWho acted, by actor_type: for client your API key's id (key_…, from GET /v1/api_keys) or OAuth client_id; for user the person's id (usr_…); for staff theirs; for service a name. Pass it as actor_id on the list to follow one.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection name in the route, such as runs or payslips. null when the route named none.
resource_id
string · nullable
optionalThe id of the record the route named, such as a run's (run_enterprise_…, from POST /v1/payroll/runs) or a payslip's (run_enterprise_payslip_…, from GET /v1/payroll/payslips). null when the route named none, as on a create or a list.
outcome
string
requiredHow the attempt ended: succeeded for a status below 400, refused for a 4xx, failed for a 5xx. Pass refused as outcome on GET /v1/payroll/audit_entries to see every attempt that was turned away.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. outcome is read from it, so the two never disagree.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/payroll/audit_entries/%7Baudit_entry_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, RUNApi } from '@droomwork/sdk';
const api = new RUNApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.payrollAuditEntriesRetrieve({ auditEntryId: '{audit_entry_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/payroll/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.RUNApi(client)
result = api.payroll_audit_entries_retrieve(audit_entry_id='{audit_entry_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/payroll/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\RUNApi(new GuzzleHttp\Client(), $config);
$result = $api->payrollAuditEntriesRetrieve(audit_entry_id: '{audit_entry_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/payroll/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.RunApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
RunApi api = new RunApi(client);
var result = api.payrollAuditEntriesRetrieve("{audit_entry_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/payroll/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 RUNApi(config);
var result = api.PayrollAuditEntriesRetrieve(auditEntryId: "{audit_entry_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/payroll/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.RUNAPI.PayrollAuditEntriesRetrieve(ctx, "{audit_entry_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/payroll/audit_entries/%7Baudit_entry_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": 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"
}