Ledgerdoc Ledger API Console
(01) — Ledger API · v2.4

Ledgers that reconcile themselves.

Ledgerdoc is a double-entry ledger API. Every posting is immutable, every write is idempotent, and every balance is derived from the entries rather than stored beside them — so the books cannot drift out of agreement with their own history.

Built for teams who moved money on a spreadsheet for too long. Post your first balanced entry in about four minutes, then keep the same two endpoints in production.

POST /v2/entries
# A balanced double-entry posting.
# Debits and credits must sum to zero.
curl -X POST https://api.ledgerdoc.dev/v2/entries \
  -H "Authorization: Bearer $LEDGER_KEY" \
  -H "Idempotency-Key: inv-4417-settle" \
  -d '{
    "narrative": "Invoice 4417 settled",
    "lines": [
      { "account": "cash:operating",   "amount":  48200 },
      { "account": "ar:acme-holdings", "amount": -48200 }
    ]
  }'

# 201 Created
# { "id": "ent_8Fq2wZ", "balanced": true, "posted_at": ... }
(02) — Quickstart

Three steps to a balanced book.

Copy these in order. The sandbox needs no card and no approval — keys are issued the moment you create a workspace.

01

Install

The CLI ships the same client the SDKs wrap. Node 18+ or any runtime with fetch.

npm i @ledgerdoc/client
02

Authenticate

Sandbox keys start sk_test_. They never touch live balances.

export LEDGER_KEY=sk_test_9f2a
03

Post an entry

Lines must sum to zero. An unbalanced entry is rejected, never partially written.

ledger entries create \
  --from cash:operating \
  --to ar:acme 48200
(03) — Documentation

The docs surface, exactly as it ships.

Left nav, article body, and an "On this page" rail that tracks your scroll. This is a real article from the guide, not a placeholder — twenty-one more are indexed by topic.

Docs/Core concepts/Idempotent postings

Idempotent postings

How Ledgerdoc guarantees that a retried write posts exactly once, even when the network disagrees about whether the first attempt arrived.

Every write endpoint accepts an Idempotency-Key header. The key is an arbitrary string you choose, scoped to your workspace, and retained for 24 hours. If two requests arrive carrying the same key, the second returns the original response — it does not create a second entry.

Why retries are unavoidable

A client that times out cannot tell the difference between a request that never arrived and a response that was lost on the way back. Retrying is the only safe behaviour, so the ledger has to make retrying safe.

Rule

Generate the key from something stable in your own domain — an invoice ID, a payout batch, a settlement reference. Never generate it per attempt, or every retry becomes a new posting.

Replay behaviour

The response to a replayed request carries a Replayed: true header so you can distinguish it in logs. The body is byte-identical to the first response, including the original posted_at timestamp.

Replay — second attempt
# Same Idempotency-Key, sent twice.
HTTP/1.1 201 Created
Replayed: true
Content-Type: application/json

{
  "id": "ent_8Fq2wZ",
  "balanced": true,
  "posted_at": "2026-07-14T09:12:44Z"
}

Key conflicts

Reusing a key with a different payload is an error, not a silent overwrite. The ledger returns 409 idempotency_conflict and writes nothing.

Outcomes for a repeated Idempotency-Key within the 24-hour window.
ConditionStatusResult
Same key, same payload201Original response replayed, no new entry
Same key, changed payload409Rejected as idempotency_conflict
Same key, after 24h201Treated as new — key has expired
No key supplied201Posted, but retries are unsafe

Scope and retention

Keys are scoped per workspace and per environment. A sandbox key and a live key never collide. Retention is 24 hours from first use; after that the key is forgotten and a request carrying it is treated as new.

  • Keys are case-sensitive and capped at 255 characters.
  • Replays do not count against your write rate limit.
  • Reads are naturally idempotent and ignore the header.
(04) — API reference

Eight endpoints. That is the whole surface.

Every route is versioned under /v2. Scopes are checked per key; a read key cannot post.

MethodPath DescriptionScope
POST/v2/entriesPost a balanced double-entry transactionentries:write
GET/v2/entries/:idRetrieve a single posting with its linesentries:read
GET/v2/entriesList postings, filtered by account or windowentries:read
POST/v2/accountsCreate an account in the chartaccounts:write
GET/v2/accounts/:id/balanceDerive a balance at an optional timestampaccounts:read
PATCH/v2/accounts/:idRename or re-tag an account, never its historyaccounts:write
POST/v2/reconciliationsOpen a reconciliation window against a statementrecon:write
DELETE/v2/webhooks/:idRemove a webhook subscriptionhooks:write

Postings are immutable. There is deliberately no PATCH or DELETE on /v2/entries — corrections are made by posting a reversing entry.

Full reference: reconciliation windows →

99.99%
Uptime, trailing 90 days
41ms
p50 write latency
12
Official SDKs
2.1B
Entries posted to date
(05) — SDKs

Install one line, keep the same shapes.

Every SDK is generated from the same schema, so field names and error codes match the reference exactly. Six shown; twelve published.

JavaScriptv2.4.1
npm i @ledgerdoc/client
Pythonv2.4.0
pip install ledgerdoc
Gov2.3.9
go get ledgerdoc.dev/go
Rubyv2.4.0
gem install ledgerdoc
Rustv2.2.7
cargo add ledgerdoc
Elixirv2.1.4
mix deps.get ledgerdoc
(06) — Changelog

Every change, dated and tagged.

Breaking changes ship only on a major version and are announced 90 days ahead. Everything else lands continuously.

14 Jul 2026v2.4.1

Replay headers on idempotent writes

AddedFixed
  • Replayed responses now carry a Replayed: true header for log correlation.
  • Fixed a case where a replay returned a fresh posted_at instead of the original.
  • Idempotency keys are no longer counted against the write rate limit on replay.
28 Jun 2026v2.4.0

Point-in-time balance derivation

Added
  • GET /v2/accounts/:id/balance accepts an at timestamp to derive historical balances.
  • Reconciliation windows can now be opened against a statement date range.
  • Added Elixir SDK at v2.1.4.
09 Jun 2026v2.3.4

Sandbox parity with live regions

Fixed
  • Sandbox now enforces the same 255-character cap on idempotency keys as live.
  • Corrected a rounding difference in multi-currency line validation.
21 May 2026v2.3.0

Webhook subscriptions, scoped per event

AddedBreaking
  • Webhooks are now subscribed per event type rather than per workspace.
  • Breaking: the legacy /v2/hooks alias is removed. Migrate to /v2/webhooks.
  • Delivery retries follow an exponential schedule over six hours.
30 Apr 2026v2.2.7

Rust SDK and faster list pagination

AddedFixed
  • Published the Rust SDK at v2.2.7 with async support.
  • Cursor pagination on GET /v2/entries is roughly 4× faster on large accounts.
02 Apr 2026v2.2.0

Immutable entries enforced at the storage layer

Breaking
  • Breaking: PATCH /v2/entries/:id is removed. Post a reversing entry instead.
  • Every posting now carries an append-only audit hash chained to the previous entry.

Six most recent shown. All fourteen releases, filterable by version →

(07) — Status

Where it runs, and how fast.

All systems operational Checked 60s ago
eu-dublin-1p50 38msOperational
eu-frankfurt-1p50 41msOperational
us-virginia-1p50 44msOperational
ap-sydney-1p50 52msOperational
(08) — Community & support

Ask people who have read the source.

Forum

Developer forum

Public, searchable and answered by the engineers who wrote the endpoints. Median first reply is under four hours on weekdays.

forum.ledgerdoc.dev · 3,400 threads
Live

Office hours

Open call every Thursday at 16:00 UTC. Bring a failing request and we will read the trace with you. No agenda, no slides.

Thursdays 16:00 UTC · 45 minutes
Enterprise

Support tiers

Standard is included. Priority adds a 1-hour response target and a named engineer; Platform adds a shared incident channel and design review.

Priority $900/mo · Platform $2,400/mo
(09) — FAQ

Questions from the forum.

What are the rate limits?

600 writes and 3,000 reads per minute per workspace on the default plan, burstable to double for 30 seconds. Replayed idempotent requests are not counted. Every response carries X-RateLimit-Remaining; a 429 includes Retry-After in seconds.

Is the sandbox a separate environment?

Yes. Sandbox keys begin sk_test_ and operate on isolated data with the same validation rules and the same latency profile. Sandbox data is reset every 30 days and is never billed.

How does versioning work?

The version is in the path. A major version is supported for 24 months after its successor ships, and breaking changes are announced 90 days ahead on the changelog and by email to workspace owners. Minor releases are additive and never remove a field.

Where is data stored, and can I choose?

Workspaces are pinned to one region at creation — Dublin, Frankfurt, Virginia or Sydney — and entries never leave it. Backups stay in-region. Region cannot be changed after creation; create a new workspace and replay your entries instead.

Can I export everything?

Yes, at any time and without asking us. GET /v2/entries with a cursor walks the full history as newline-delimited JSON, and the CLI wraps it as ledger export --all. There is no proprietary format and no export fee.

What happens if an entry is wrong?

Nothing is edited. Post a reversing entry that references the original, then post the correction. The audit chain stays intact and the balance derives correctly from the three entries together — which is what an auditor expects to see.

(10) — Get started

Ledgers that reconcile themselves.

Create a workspace, take a sandbox key, and post a balanced entry before your coffee goes cold. No card, no sales call.

Perfect for: platform teams replacing a spreadsheet ledger with something auditable