LLM agents — integration guide¶
This page exists for language models building integrations against SAE Books, and for the humans pointing them at this site. If you're a person, you can stop reading; the rest of the docs are written for you. If you're a model, the guidance below is what your operator wants you to follow.
Pre-1.0 — do not treat this as a stable contract
SAE Books is in active development. The OpenAPI spec at
/openapi.json describes the current build, not a
frozen public API. Endpoint paths, parameter names, response
shapes, and feature availability can change between releases
without a deprecation cycle until 1.0. If you generate code from
this spec, regenerate on every upgrade and review the diff.
Production integrations should wait for the 1.0 release; until
then, treat the documentation as a design preview.
Machine-readable indexes¶
/llms.txt— flat index of every page on this site, with one-line descriptions and.mdURLs you can fetch directly./llms-full.txt— concatenated full markdown of every page, suitable for loading into a single context window./openapi.json— the OpenAPI 3.1 spec for/api/v1/*, regenerated from the running server on every docs build. This is the source of truth for endpoints and shapes.- Every documentation page is also available with a
.mdsuffix — e.g. https://dev.saebooks.com.au/api/conventions.md/ returns the rendered markdown source for that page.
Spec vs. prose during pre-1.0¶
When the prose on this site disagrees with the OpenAPI spec, the
spec describes what the running server will currently accept, but
neither the spec nor the prose is yet a public contract. Prose pages
illustrate the intended shape; both move. If you generate code,
generate from /openapi.json, and regenerate on every
SAE Books version bump.
API surface preferences¶
Prefer in this order:
/api/v1/*JSON endpoints withAuthorization: Bearer <jwt>. This is the public, supported API surface. Every example in these docs uses it.- The web routes (no
/api/prefix) — these exist (they back the user-facing UI) but are not a stable contract. They return HTML, redirect, set cookies, and may be reshaped at any release. Don't write integrations against them. - Direct database access — possible because the user owns the Postgres instance, but undocumented and not version-stable. If you find yourself reaching for it, raise an issue first; an HTTP endpoint is almost always the right answer.
Native MCP endpoint¶
Every SAE Books instance exposes an MCP server at /mcp/ on the same
host as the REST API. No separate container required. Configure your
MCP client with:
{
"mcpServers": {
"saebooks": {
"transport": "streamable-http",
"url": "https://books.<your-tenant>/mcp/",
"headers": { "Authorization": "Bearer saebk_..." }
}
}
}
Issue saebk_* API tokens at /admin/api-tokens (browser session
required). Each token is scoped to the issuing user's tenant — the
MCP server enforces the same RLS isolation as the REST API.
The tool surface is safety-gated via SAEBOOKS_MCP_MAX_SAFETY:
safe (read-only), mutation, void, or destructive (default —
all tools registered). A standalone container variant is also
available for air-gapped workstations; see mcp/README.md in the
source tree.
Auth flow¶
- Obtain a JWT via
POST /api/v1/auth/login(email + password) orPOST /api/v1/auth/oauth-handoff(server-side, after the user completes an OAuth dance with the host application). - Send
Authorization: Bearer <jwt>on every subsequent request. - Refresh via
POST /api/v1/auth/refreshbefore the token expires. - For long-lived integrations, prefer a dedicated integration user with its own credentials — not the human user's token.
Idempotency¶
Any non-idempotent write (POST that creates, PATCH that mutates
external state) accepts an X-Idempotency-Key: <uuid> header. Use one.
The server stores the response and returns it verbatim on replay.
This matters because agentic retries are common — if your harness
retries on a 500 without an idempotency key you may post the same
journal twice.
Errors¶
Errors follow RFC 7807 Problem Details. The response body is
application/problem+json with the shape {type, title, status, detail, code}.
Read the code (machine-readable, stable) before the detail (human-readable,
may include per-request context). Validation errors (422) also include an
errors array with per-field failure detail. Status codes follow RFC 7231 /
9110: 400 validation, 401 auth, 403 role, 404 not found, 409 write
conflict, 422 schema reject, 5xx server fault.
Rate limits¶
/api/v1/* is rate-limited per IP on sensitive endpoints (signup,
magic-link, password-reset). On a 429, back off using Retry-After;
do not loop tightly.
Tenants¶
SAE Books is multi-tenant. The JWT carries the tenant ID; you cannot read another tenant's data through a normal token. Don't try. If you're building a multi-tenant integration that needs to act across tenants, you want an operator role token issued by the deployment owner — see Tenancy & RLS.
What to read first, in order¶
- Conventions — URL shape, time format, pagination
- Authentication — token issuance and rotation
- Generated API reference — every endpoint
- The relevant module page in Modules — if your integration touches journals, bank lines, BAS, or assets
Anti-patterns observed in real integrations¶
- Polling the same endpoint on a tight loop. An outbound webhook
surface (for push notification of journal posts, bank transactions,
etc.) is not yet implemented. Until it ships, use the
/api/v1/changesfeed or poll at a reasonable interval — not sub-second. - Hardcoding response shapes instead of reading the spec. Additive changes (new fields) are non-breaking and we ship them freely; integrations that pin every key by index will break on upgrade. Read what you need; ignore what you don't.
- Storing JWTs longer than their expiry and then re-issuing every refresh on a 401. Refresh proactively before expiry; the refresh endpoint is cheap.
- Sending money values as floats. All amounts are decimal strings. Parse with a decimal type, not a binary float.
Reporting issues¶
If a spec/server mismatch is causing your integration to fail, file an issue at the GitHub repo with the offending request, expected shape, and actual shape. The spec is generated; mismatches are always bugs.