DDroomwork Developers

Version 1.0.0

Droomwork LEDGER

The double entry books behind everything that moves money.

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

Every movement of your money is recorded as a posting: two or more lines that sum to zero. This is where you read them.

What you should know before you start

You can only read here, and that won't change. There's no endpoint that writes a posting and no ledger:write scope to hold. A posting exists because a module caused it.

A posting is its lines. The lines sum to zero, and they come back with the posting, not behind a second call. Debits and credits are both positive numbers; the direction carries the sign.

Balances are computed, never stored. An account's balance is worked out from its lines at the moment you ask.

A correction is another posting. Nothing here is ever edited or deleted. A posting that reverses another names it in reverses_id.

Your books start empty, and stay empty until your chart of accounts exists. A posting's line can only name an account that is already there, and no endpoint here creates one. Until the chart is in place, GET /v1/ledger/accounts answers with an empty list and there is nothing yet to read at the other two.

Money is whole minor units. An amount is an integer count of the currency's smallest unit plus the currency code. For NGN that's kobo. Read how many minor units make one major unit from GET /v1/currencies; don't assume it's a hundred.

Getting started

You need a credential carrying ledger:read. Sandbox keys from the developer portal carry it. Read the chart of accounts first: every posting line names an account by its code, and balances are grouped by code.

Every operation below has two samples: Direct HTTP, with nothing but your language's own client, and Client library, with ours. Same request, same answer; the switch above each sample picks one and remembers it. Which should I use?

GET/v1/ledger/accounts#

See what each account holds

ledger.accounts.list

This answers what every account of yours stands at right now, so your product can show a trial balance of its own without adding anything up or holding a figure that falls behind.

Send nothing; this takes no parameters. Your whole chart comes back in code order, with every account in it, whether or not anything has reached one yet.

This is the one list here that does not page. There is no cursor to follow and no page size to raise, so what comes back is your whole chart and you can total it as it stands.

Returns

Every account you hold, each with the balance it stands at.

object always "list" required

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

data array of LedgerAccount required

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

9 fields of LedgerAccount
id string required

The account's identifier, starting with ledger_account_; it never changes. Each posting line names this account by it in account_id at GET /v1/ledger/postings.

object always "ledger_account" required

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

livemode boolean required

False for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.

mocked boolean required

True when this response came from a mock, false when the engine computed the numbers. It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.

code string required

The name a posting line refers to the account by, unique within your organisation. Lines name a code rather than an identifier, so you can read a chart without a lookup.

kind string required

What the account is for. There are four kinds and no more.

assetliabilityexpenseequity
currency string required

ISO 4217 code. An account holds one currency.

balance object required

Computed from the account's lines when you ask, never stored. Debits positive, credits negative. Zero for an account with no lines yet.

2 fields
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

created_at string · date-time required

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

has_more boolean required

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

Other responses

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

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/ledger/accounts" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, LEDGERApi } from '@droomwork/sdk';

const api = new LEDGERApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.ledgerAccountsList({});
const response = await fetch('https://sandbox.droomwork.io/v1/ledger/accounts', {
  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.LEDGERApi(client)

result = api.ledger_accounts_list()
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/ledger/accounts',
    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\LEDGERApi(new GuzzleHttp\Client(), $config);

$result = $api->ledgerAccountsList();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/ledger/accounts');
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.LedgerApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
LedgerApi api = new LedgerApi(client);

var result = api.ledgerAccountsList();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/ledger/accounts"))
    .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 LEDGERApi(config);

var result = api.LedgerAccountsList();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/ledger/accounts");
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.LEDGERAPI.LedgerAccountsList(ctx).Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/ledger/accounts", nil)
	req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
Response
{
  "object": "list",
  "data": [
    {
      "id": "ledger_account_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "ledger_account",
      "livemode": false,
      "mocked": false,
      "code": "payroll_payable",
      "kind": "asset",
      "currency": "NGN",
      "balance": {
        "amount": 41250000,
        "currency": "NGN"
      },
      "created_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true
}
GET/v1/ledger/postings#

Go through every movement, newest first

ledger.postings.list

This answers what has moved through your books, so your product can walk every movement your organisation holds and find the one behind a figure somebody is asking about.

Send nothing and they come back most recently recorded first. One posting is one movement, so a figure you are chasing sits inside one of them and never across several.

That order is when a posting was recorded, and occurred_on is the date the movement belongs to. The two part company whenever a period is posted after it closes, so page on the order and reconcile on occurred_on.

A starting_after that is not one of your postings answers with an empty page, the same answer you get on reaching the end, so send an id you read from here.

Open one in full at GET /v1/ledger/postings/{posting_id}.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

Returns

One page of movements, newest first, each with the lines behind it.

object always "list" required

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

data array of LedgerPosting required

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

9 fields of LedgerPosting
id string required

The posting's identifier, starting with ledger_posting_; it never changes. Pass it as posting_id to GET /v1/ledger/postings/{posting_id}, or as starting_after when paging GET /v1/ledger/postings.

object always "ledger_posting" required

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

livemode boolean required

False for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.

mocked boolean required

True when this response came from a mock, false when the engine computed the numbers. It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.

natural_key string required

Derived from the source document, so re-posting the same source produces no second posting. A retry after a timeout is safe because of this.

occurred_on string · date required

The date the movement belongs to, which is not always the date it was recorded. A September payroll posted in October occurred in September.

reverses_id string · nullable optional

The id of the posting this one reverses, starting with ledger_posting_; null unless this posting is a correction. Retrieve that posting at GET /v1/ledger/postings/{posting_id}.

lines array of LedgerPostingLine required

The lines that make up the posting, at least two, each naming an account, a direction and a positive amount. They sum to zero and come back with the posting rather than behind a second call.

9 fields of LedgerPostingLine
id string required

The line's identifier, starting with ledger_posting_; it never changes. Quote it when you ask about one line of a posting you read at GET /v1/ledger/postings or GET /v1/ledger/postings/{posting_id}.

object always "ledger_posting_line" required

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

account_id string required

The id of the account this line moves, starting with ledger_account_. It matches one account in your chart at GET /v1/ledger/accounts; account_code carries that account's code.

account_code string required

The account's code, so a line reads without a second call.

direction string required

Which side of the posting this line is on: debit counts as positive, credit as negative. amount is positive either way, and debits less credits across a posting's lines come to zero.

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

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

run_id string · nullable optional

The id of the payroll run this line came from, starting with run_enterprise_, as POST /v1/payroll/runs and GET /v1/payroll/runs return it; null when no run produced the movement. Retrieve it at GET /v1/payroll/runs/{run_id}.

payslip_id string · nullable optional

The id of the payslip this line came from, starting with run_enterprise_payslip_, as listed at GET /v1/payroll/payslips; null for a movement no payslip produced. Retrieve it at GET /v1/payroll/payslips/{payslip_id}.

payslip_line_id string · nullable optional

The id of the payslip line this amount traces to: an entry in lines on the payslip at GET /v1/payroll/payslips/{payslip_id}, found by payslip_id; null when no payslip line produced it.

created_at string · date-time required

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

has_more boolean required

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

Other responses

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

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/ledger/postings?limit=25" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, LEDGERApi } from '@droomwork/sdk';

const api = new LEDGERApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional)
const result = await api.ledgerPostingsList({ limit: 25 });
// query parameters: limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/ledger/postings?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.LEDGERApi(client)

# query parameters: limit (optional), starting_after (optional)
result = api.ledger_postings_list(limit=25)
import os

import requests

# query parameters: limit (optional), starting_after (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/ledger/postings?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\LEDGERApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional)
$result = $api->ledgerPostingsList(limit: 25);
<?php
// query parameters: limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/ledger/postings?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.LedgerApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
LedgerApi api = new LedgerApi(client);

// query parameters: limit (optional), starting_after (optional)
var result = api.ledgerPostingsList(25, null);
// query parameters: limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/ledger/postings?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 LEDGERApi(config);

// query parameters: limit (optional), starting_after (optional)
var result = api.LedgerPostingsList(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/ledger/postings?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.LEDGERAPI.LedgerPostingsList(ctx).Limit(25).Execute()
// query parameters: limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/ledger/postings?limit=25", nil)
	req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
Response
{
  "object": "list",
  "data": [
    {
      "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "ledger_posting",
      "livemode": false,
      "mocked": false,
      "natural_key": "payroll_september",
      "occurred_on": "2026-09-01",
      "lines": [
        {
          "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "object": "ledger_posting_line",
          "account_id": "ledger_account_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "account_code": "example",
          "direction": "debit",
          "amount": {
            "amount": 7500000,
            "currency": "NGN"
          },
          "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
        },
        {
          "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "object": "ledger_posting_line",
          "account_id": "ledger_account_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "account_code": "example",
          "direction": "debit",
          "amount": {
            "amount": 7500000,
            "currency": "NGN"
          },
          "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
        }
      ],
      "created_at": "2026-09-01T09:00:00Z",
      "reverses_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
  ],
  "has_more": true
}
GET/v1/ledger/postings/{posting_id}#

Check one movement, line by line

ledger.postings.retrieve

This answers with one movement in full, so when somebody queries a single figure you can open the posting behind it and show every account that figure moved.

Send the posting's id in the path, the one you read from GET /v1/ledger/postings or from another posting's reverses_id, which only a correction carries, naming the posting it reverses.

A posting that is not yours answers not_found, which is the same answer as one that never existed.

Path parameters

posting_id string required

The posting's id, starting with ledger_posting_, from a posting you listed at GET /v1/ledger/postings. One that isn't yours answers not_found, not forbidden.

Returns

The movement you asked for, with every line it is made of.

id string required

The posting's identifier, starting with ledger_posting_; it never changes. Pass it as posting_id to GET /v1/ledger/postings/{posting_id}, or as starting_after when paging GET /v1/ledger/postings.

object always "ledger_posting" required

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

livemode boolean required

False for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.

mocked boolean required

True when this response came from a mock, false when the engine computed the numbers. It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.

natural_key string required

Derived from the source document, so re-posting the same source produces no second posting. A retry after a timeout is safe because of this.

occurred_on string · date required

The date the movement belongs to, which is not always the date it was recorded. A September payroll posted in October occurred in September.

reverses_id string · nullable optional

The id of the posting this one reverses, starting with ledger_posting_; null unless this posting is a correction. Retrieve that posting at GET /v1/ledger/postings/{posting_id}.

lines array of LedgerPostingLine required

The lines that make up the posting, at least two, each naming an account, a direction and a positive amount. They sum to zero and come back with the posting rather than behind a second call.

9 fields of LedgerPostingLine
id string required

The line's identifier, starting with ledger_posting_; it never changes. Quote it when you ask about one line of a posting you read at GET /v1/ledger/postings or GET /v1/ledger/postings/{posting_id}.

object always "ledger_posting_line" required

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

account_id string required

The id of the account this line moves, starting with ledger_account_. It matches one account in your chart at GET /v1/ledger/accounts; account_code carries that account's code.

account_code string required

The account's code, so a line reads without a second call.

direction string required

Which side of the posting this line is on: debit counts as positive, credit as negative. amount is positive either way, and debits less credits across a posting's lines come to zero.

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

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

run_id string · nullable optional

The id of the payroll run this line came from, starting with run_enterprise_, as POST /v1/payroll/runs and GET /v1/payroll/runs return it; null when no run produced the movement. Retrieve it at GET /v1/payroll/runs/{run_id}.

payslip_id string · nullable optional

The id of the payslip this line came from, starting with run_enterprise_payslip_, as listed at GET /v1/payroll/payslips; null for a movement no payslip produced. Retrieve it at GET /v1/payroll/payslips/{payslip_id}.

payslip_line_id string · nullable optional

The id of the payslip line this amount traces to: an entry in lines on the payslip at GET /v1/payroll/payslips/{payslip_id}, found by payslip_id; null when no payslip line produced it.

created_at string · date-time required

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

Other responses

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

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, LEDGERApi } from '@droomwork/sdk';

const api = new LEDGERApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.ledgerPostingsRetrieve({ postingId: 'ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_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.LEDGERApi(client)

result = api.ledger_postings_retrieve(posting_id='ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_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\LEDGERApi(new GuzzleHttp\Client(), $config);

$result = $api->ledgerPostingsRetrieve(posting_id: 'ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_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.LedgerApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
LedgerApi api = new LedgerApi(client);

var result = api.ledgerPostingsRetrieve("ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_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 LEDGERApi(config);

var result = api.LedgerPostingsRetrieve(postingId: "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_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.LEDGERAPI.LedgerPostingsRetrieve(ctx, "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/ledger/postings/ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
	req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
Response
{
  "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "ledger_posting",
  "livemode": false,
  "mocked": false,
  "natural_key": "payroll_september",
  "occurred_on": "2026-09-01",
  "lines": [
    {
      "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "ledger_posting_line",
      "account_id": "ledger_account_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "account_code": "example",
      "direction": "debit",
      "amount": {
        "amount": 7500000,
        "currency": "NGN"
      },
      "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    },
    {
      "id": "ledger_posting_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "ledger_posting_line",
      "account_id": "ledger_account_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "account_code": "example",
      "direction": "debit",
      "amount": {
        "amount": 7500000,
        "currency": "NGN"
      },
      "run_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "payslip_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "payslip_line_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
  ],
  "created_at": "2026-09-01T09:00:00Z",
  "reverses_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}