Developers
API quickstart
Connect a script or an AI agent to your books through the LedgerIt API, with an API key, idempotent writes and the TypeScript SDK.
The LedgerIt API lets a script or an AI agent read your books and post to them, with the same checks a person gets in the app. This guide takes you from a new key to your first journal entry. Every operation is listed in the API reference.
Create an API key
- Open Settings and go to API keys.
- Choose Create a key.
- Give it a Name for what will use it, such as "Month-end close agent", so you know what to revoke later.
- Pick the Access: Read only reads the books and can't change anything; Read and write does what your role lets you do.
- Choose Create key, then copy it. It is shown once only, so store it in a secret manager, not in code.
Anyone in the books can create a key for themselves; a viewer is offered read-only keys. Each person can hold up to 20 keys per set of books. Admins and owners see everyone's keys and can Revoke any of them; a revoked key stops working at its next request.
How a key acts
Read onlyHeld to a viewer's access, whatever the role
Other booksNaming them answers 404
A key acts as the member who made it, in the books it was made in, and no others. It never does more than that member's role allows today, so if their role changes, so does the key. A read-only key is held to a viewer's access. Settings and your team lists the roles.
Make your first request
curl https://api.ledgerit.ai/v1/journal_entries \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"entry_date": "2026-09-30",
"description": "Accrue September AWS",
"lines": [
{
"account_id": "gla_01k6jx3m9qf8vbz2t5n4wyhd7c",
"direction": "debit", "amount": "1240.00"
},
{
"account_id": "gla_01k6jx3m9rf2c8x8w1e5bq4zna",
"direction": "credit", "amount": "1240.00"
}
]
}'{
"id": "je_01k6k8f2a1b3c5d7e9g1h3j5km",
"entry_no": 42,
"entry_date": "2026-09-30",
"period": "2026-09",
"description": "Accrue September AWS",
"entry_kind": "manual",
"status": "posted",
"amount": "1240.00"
}Posted. Debits equal credits, the key can write and the role can post.
The API lives at https://api.ledgerit.ai, and every path starts with /v1. Send the key as a bearer token:
export LEDGERIT_TOKEN="ll_…"
curl https://api.ledgerit.ai/v1/accounts \
-H "Authorization: Bearer $LEDGERIT_TOKEN"
That returns your chart of accounts with balances.
The optional X-Entity-Id header names the set of books a call acts in. With a key you can leave it out: a key only ever opens the books it was made in, and naming any other books answers 404.
Try a call from the reference
Every operation in the API reference has a Try it button under its example. Signed in, it opens the operation's parameters and an editable body; choose Send request to call the API as yourself, without a key, and read the status and the answer underneath. The call acts in the books you last opened in the app, with your role, so anything other than a read asks you to confirm first: it changes your books exactly as the app would. Signed out, the reference offers Sign in and brings you back to the same page.
Read an id
Every id starts with what it names: gla_01k6jx3m9qf8vbz2t5n4wyhd7c is a ledger account, je_… a journal entry, jel_… one of its lines, party_… a customer or vendor, txn_… a transaction, inv_… an invoice. Treat an id as an opaque string and pass it back exactly as you got it. Send one where another kind belongs, such as a transaction id as an account_id, and the call is refused with 400 invalid_parameter, naming the field and the prefix it expects, before anything is read or written.
The part after the last underscore is the record's UUID in 26 characters, in the TypeID format, so ids sort by when the record was made. The API still accepts a bare UUID wherever it takes an id, so ids kept from before keep working.
List transactions
Lists are paginated with a cursor. A page is an object with data, has_more and next_cursor. Pass limit (1 to 200, default 50) and, for the next page, cursor set to the last page's next_cursor.
curl "https://api.ledgerit.ai/v1/transactions?status=needs_review&limit=100" \
-H "Authorization: Bearer $LEDGERIT_TOKEN"
status takes needs_review, ready, posted or excluded, and can be repeated. A few endpoints, such as the chart of accounts and the reports, return one object rather than a page; none returns a bare array.
Post a journal entry
A journal entry needs a date, a description and at least two lines. Each line names an account, a direction (debit or credit) and a positive amount. Debits must equal credits, or the entry is refused. Posting needs a read-and-write key held by an accountant, admin or owner.
curl https://api.ledgerit.ai/v1/journal_entries \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"entry_date": "2026-09-30",
"description": "Accrue September AWS",
"lines": [
{"account_id": "gla_01k6jx3m9qf8vbz2t5n4wyhd7c", "direction": "debit", "amount": "1240.00"},
{"account_id": "gla_01k6jx3m9rf2c8x8w1e5bq4zna", "direction": "credit", "amount": "1240.00"}
]
}'
Use account ids from /v1/accounts: each is a string, not the account's code. A posted entry is never edited or deleted; you reverse or replace it. Journal entries explains why.
Retry safely with idempotency keys
- Idempotency-Key
- 7f3c9a2e-4b1d-4e8f-a6c2-0d9e1b5f3a71
- amount
- "1240.00"
- Nothing sent yet.
Send, then Send again with the same key. Then change the amount and send once more.
Send an Idempotency-Key header, any unique string such as a UUID, on every call that creates something. If a request times out and you send it again with the same key, you get the first answer back, marked Idempotent-Replayed: true, instead of a second entry. Keys last 24 hours. Reusing a key for a different request is refused with idempotency_key_reused.
Handle errors
Every error has the same shape:
{
"error": {
"type": "permission_error",
"code": "token_scope_insufficient",
"message": "This API key can only read. Make a key with write access in Settings to change the books.",
"param": null,
"request_id": "…"
}
}
Branch on code: it never changes once published. message is for people and follows Accept-Language, so never match on it. param names the field at fault, if any. Quote request_id when you contact support.
| Code | Meaning |
|---|---|
unauthenticated | No valid key |
token_scope_insufficient | A read-only key tried to write |
role_insufficient, permission_denied | The member's role doesn't allow it |
resource_not_found | Not in these books |
invalid_parameter | A field is malformed |
rate_limited | Too many requests; wait for Retry-After |
Rate limits
Each person can make 300 requests a minute, counted across the app and the API together. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. Past the limit you get a 429 with Retry-After.
Money
Amounts are decimal strings with two places, such as "-1234.50", never floats, and the currency is named once on the response. Keep them as strings or exact decimals.
Use the TypeScript SDK
@ledgerit/sdk is a TypeScript client with one method per operation. It is not published to npm yet. It reads the key from LEDGERIT_TOKEN, adds an idempotency key to every create, retries rate limits and safe failures, and throws typed errors carrying code. Its Money helpers add and compare amounts without floats.
import { LedgerIt, PermissionDeniedError } from "@ledgerit/sdk";
const client = new LedgerIt(); // reads LEDGERIT_TOKEN
for await (const tx of client.transactions.listAll({ status: ["needs_review"] })) {
console.log(tx.event_date, tx.description, tx.amount);
}
try {
await client.journalEntries.create({
entry_date: "2026-09-30",
description: "Accrue September AWS",
lines: [
{ account_id: "gla_01k6jx3m9qf8vbz2t5n4wyhd7c", direction: "debit", amount: "1240.00" },
{ account_id: "gla_01k6jx3m9rf2c8x8w1e5bq4zna", direction: "credit", amount: "1240.00" },
],
});
} catch (err) {
if (err instanceof PermissionDeniedError) {
console.error(err.code, err.requestId); // a read-only key, or a role that can't post
} else {
throw err;
}
}
What the API doesn't cover
The API covers the books: the company, accounts, dimensions, journal entries, drafts, opening balances, transactions, rules, counterparties, the close, reports, notifications and the audit trail archive. It leaves out, on purpose:
- People and access. Members, invitations, API keys and deleting books or an account are managed in the app.
- Files. Uploading documents and exporting reports as files happen in the app.