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 URL | https://elianbook.com/api/v1 |
|---|---|
| Auth | Authorization: Bearer <your key> |
| Format | JSON request/response |
| Methods | GET 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"
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.
| Status | Meaning |
|---|---|
401 | Missing, malformed, unknown, or revoked key. |
403 | Valid key, but the request is outside /api/v1 or uses a non-GET method (the API is read-only). |
400 | A 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.
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.
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.
Your client / customer address book. Paginated, by name.
Fields: id, name, email, phone, address, created_at.
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.
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.
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.
Income/expense categories - resolves a transaction's category_id. Paginated.
Fields: id, name, kind (income|expense), tax_line, description, active, sort_order.
Bank/card accounts - resolves a transaction's account_id. Paginated.
Fields: id, name, type, active, mask (last 4), created_at.
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.
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.