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

  1. Open Settings and go to API keys.
  2. Choose Create a key.
  3. Give it a Name for what will use it, such as "Month-end close agent", so you know what to revoke later.
  4. Pick the Access: Read only reads the books and can't change anything; Read and write does what your role lets you do.
  5. 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

API keyMonth-end close agentRead and write, or read only
Acts asThe member who made itTheir role today; it changes when theirs does
InAcme Inc's booksOnly the books it was made in

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 never does more than that member's role allows today.

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

Try a request
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"
      }
    ]
  }'
201Succeeded
Response
{
  "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.

Choose a call, the key's access and your role to see the request and what the API answers. The sample books are Acme Inc's.

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

POST /v1/journal_entries
41 entries in the books
Idempotency-Key
7f3c9a2e-4b1d-4e8f-a6c2-0d9e1b5f3a71
amount
"1240.00"
  1. Nothing sent yet.

Send, then Send again with the same key. Then change the amount and send once more.

One journal entry sent over a flaky network: the first answer is lost, the retry with the same key gets it back, and the same key with a different amount is refused.

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.

CodeMeaning
unauthenticatedNo valid key
token_scope_insufficientA read-only key tried to write
role_insufficient, permission_deniedThe member's role doesn't allow it
resource_not_foundNot in these books
invalid_parameterA field is malformed
rate_limitedToo 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.