Elian Book API ← Back to elianbook.com
Developer documentation

Elian Book API

A simple, read-only REST API. Mint a per-workspace key and pull your books, live, into your own dashboards, spreadsheets, and apps. Your data is never locked in.

Overview Read-only

Every endpoint is a GET under a single base URL. Responses are JSON. A key can read only the workspace it was created in, and can only read - there is no write, update, or delete surface. If you need to get data out of Elian Book and into another tool, this is the API for it.

Base URLhttps://elianbook.com/api/v1
AuthAuthorization: Bearer <your key>
FormatJSON request/response
MethodsGET only

Authentication

Create a key in the app under Settings → API access. Only a workspace owner or admin can mint one. The raw key is shown once at creation - copy it then and store it somewhere secure; Elian Book keeps only a hash and can never show it again.

Send it on every request as a bearer token:

# every request carries the key in the Authorization header
curl https://elianbook.com/api/v1/transactions \
  -H "Authorization: Bearer eb_live_your_key_here"
Keep keys secret. A key grants full read access to your workspace's financial data. Never commit one to source control or expose it in browser/client-side code. Revoke a key anytime from Settings → API access; it stops working immediately.

Pagination

List endpoints return a page of rows plus the page window, and accept limit and offset query params. Default page size is 100; the maximum is 500. Walk the pages by increasing offset until a page returns fewer rows than the limit.

GET /api/v1/transactions?limit=500&offset=1000

{
  "data": [ /* up to `limit` rows */ ],
  "limit": 500,
  "offset": 1000
}

The report and export endpoints are not paginated - they return a single object.

Errors

Errors come back as JSON with an error message and a matching HTTP status.

StatusMeaning
401Missing, malformed, unknown, or revoked key.
403Valid key, but the request is outside /api/v1 or uses a non-GET method (the API is read-only).
400A required query parameter is missing or invalid (e.g. year on the P&L report).

Quickstart

A complete example - "which projects did a given employee work on" - joined entirely client-side from the data the API serves:

const H = { Authorization: 'Bearer eb_live_your_key_here' };

// walk every page of a list endpoint
async function all(path) {
  const out = [];
  for (let offset = 0; ; offset += 500) {
    const r = await fetch(`https://elianbook.com/api/v1${path}?limit=500&offset=${offset}`, { headers: H });
    const { data } = await r.json();
    out.push(...data);
    if (data.length < 500) break;
  }
  return out;
}

const logs = await all('/time-logs');
const projectIds = new Set(logs.filter(l => l.employee_id === EMPLOYEE_ID).map(l => l.project_id));
const projects = (await all('/projects')).filter(p => projectIds.has(p.id));

Because every record carries the ids that link it to others (employee_id, project_id, client_id, invoice_id, category_id, account_id), you can reconstruct any relationship in your own code.

Endpoints

All amounts are integer cents. All ids are integers scoped to your workspace.

GET/api/v1/transactions

Your ledger. Paginated, newest first.

Fields: id, txn_date, kind (income|expense), amount_cents, tax_cents, description, payee, method, category_id, account_id, project_id, invoice_id, cleared, created_at.

GET/api/v1/invoices

Invoices. Paginated, newest first.

Fields: id, invoice_number, client_name, client_email, issue_date, due_date, total_cents, tax_pct, discount_cents, status, project_id.

GET/api/v1/clients

Your client / customer address book. Paginated, by name.

Fields: id, name, email, phone, address, created_at.

GET/api/v1/projects

Projects / jobs. Paginated, newest first.

Fields: id, name, description, status, target_price_cents, sale_price_cents, sold_date, labor_rate_cents, client_id, created_at.

GET/api/v1/employees

Employees. Paginated, by name. Login secrets are never returned.

Fields: id, full_name, ssn_last4, is_officer, active, hire_date, work_state, bill_rate_cents, cost_rate_cents, pto_hours_annual, created_at.

GET/api/v1/time-logs

Timesheet entries. Paginated, newest first.

Query: from & to (both YYYY-MM-DD) narrow by work date.

Fields: id, employee_id, project_id, work_date, hours, description, billable, rate_cents, invoice_id, pay_run_id, approved, created_at.

GET/api/v1/categories

Income/expense categories - resolves a transaction's category_id. Paginated.

Fields: id, name, kind (income|expense), tax_line, description, active, sort_order.

GET/api/v1/accounts

Bank/card accounts - resolves a transaction's account_id. Paginated.

Fields: id, name, type, active, mask (last 4), created_at.

GET/api/v1/reports/pl

Profit & loss, summed by category.

Query: from & to (a date range) take precedence; otherwise pass year (e.g. ?year=2026).

Returns: lines (per-category id, name, kind, total_cents), plus income_cents, expense_cents, and net_cents.

GET/api/v1/export

A full one-shot dump of your workspace - the same payload as the in-app JSON backup. Returns { app, version, tables }, where tables holds every org-scoped table (company, employees, payroll, transactions, invoices, filings, and more) plus reference tax rates. Not paginated.