Developer FAQ¶
Common questions from developers integrating with or extending SAE Books.
Licensing and forking¶
Can I fork SAE Books?
Yes. The source is published under AGPLv3 at github.com/saebooks/saebooks. Fork it, run it, modify it.
AGPLv3 copyleft applies: if you distribute a modified binary, or run a modified version as a network service for others, you must publish your source changes under the same licence.
Can I run SAE Books commercially?
Yes — AGPLv3 permits commercial use. You can sell a service built on SAE Books, charge for installation, or integrate it into a paid product, provided you comply with the copyleft requirements.
What about the SAE Engineering relays (license, lodge, feeds)?
Those are private commercial services. A fork cannot use SAE Engineering's Stripe keys, ATO Machine Credential, or SISS Data Services agreement. Forkers who want bank feeds, STP/BAS lodgement, or licence-management infrastructure need to establish their own agreements with the relevant parties (ACSISS / SISS, ATO DSP program, Stripe).
Can I embed SAE Books in my product without releasing my code?
AGPLv3 requires source disclosure for network service use. If your product exposes SAE Books functionality over a network, your changes to the SAE Books source must be published. The AGPL licence does not require disclosure of your surrounding proprietary application code — only the modifications to SAE Books itself.
Architecture and integration¶
What is the recommended integration pattern?
For integrations that act on behalf of a user, use OAuth handoff — the user authenticates on the SAE Books side, your server gets a scoped token. For backend automations, create an integration user in the tenant with a long-lived rotating credential.
See Build an integration for the full walkthrough.
Is there a webhook API?
SAE Books accepts inbound webhooks from external services (e.g. Resend for email delivery events, Paperless-ngx for document ingestion). An outbound webhook surface — for notifying your endpoints when SAE Books records change — is not yet implemented; follow the project changelog for when it ships.
How do I get an OpenAPI client for my language?
The OpenAPI schema is at /openapi.json on every install. Feed it to your generator:
# Python (openapi-python-client)
pip install openapi-python-client
openapi-python-client generate --url https://app.saebooks.com.au/openapi.json
# TypeScript (openapi-typescript)
npx openapi-typescript https://app.saebooks.com.au/openapi.json -o schema.d.ts
What's the pagination model?
Offset-based on most list endpoints. GET /api/v1/invoices?limit=50&offset=0 returns {items: [...], total: <int>, limit: <int>, offset: <int>}. Increment offset by limit to page through results.
The change-log endpoint (GET /api/v1/changes) uses cursor-based pagination via the X-Cursor-Next response header; pass ?since=<cursor> on subsequent calls.
See Conventions for the full pagination spec.
How does idempotency work?
Include X-Idempotency-Key: <uuid> on any state-changing request. SAE Books stores the response and returns it verbatim on replay. Generate a fresh UUID per logical operation; reuse the same UUID to retry a failed request safely.
Can I query across tenants?
No, and that's by design. Every API call is scoped to the authenticated user's tenant. Cross-tenant queries are an operator-role code path that is explicitly audited — not available via the customer API.
Development¶
Where do I report bugs?
GitHub Issues on saebooks/saebooks. Include your SAE Books version (from /api/v1/version), the relevant API endpoint, and the minimal reproduction case.
Is there a community?
discourse.saebooks.com.au — developers, operators, and the SAE Books team. SAE Books uses its own DiscourseConnect implementation for forum SSO, so you can sign in with your SAE Books account.
How do I run the test suite?
The test suite spins up an isolated Postgres on a private Docker
network, runs alembic upgrade head, then runs pytest. The live
app container is built without dev dependencies — do not run pytest
against it. The suite uses pytest-asyncio + httpx ASGITransport
and requires no external services. See Testing
for fixture conventions.
What Python version does SAE Books target?
Python 3.12+. The container image pins to 3.12-slim. pyproject.toml sets requires-python = ">=3.12".
How do I set up a dev environment outside Docker?
Most contributors don't bother — the compose setup is the dev environment. If you do want a bare-metal setup:
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Start a local Postgres, set DATABASE_URL in .env
alembic upgrade head
uvicorn saebooks.main:app --reload --port 8042
What's the commit format?
One logical change per commit. Imperative subject line, ≤72 characters. Body explains why, not what — the diff shows what. See Contributing for the full guide.
Operations¶
How do I upgrade SAE Books?
If a migration fails, the container exits non-zero. Read the logs before doing anything else. See Self-host in production for the full upgrade procedure.
Does SAE Books phone home?
Community edition: no. It does not contact SAE Engineering unless you configure an external service (lodge relay, feeds relay).
Business / Pro / Enterprise: the licence token refreshes daily from license.saebooks.com.au. No accounting data leaves your server — the refresh call sends only the current token (which contains the licence ID and edition, nothing more).
Where is data stored?
Postgres only. All state — journal entries, invoices, contacts, sessions, everything — is in the database. The app container is stateless; the Postgres data volume is the only thing you need to back up and restore.
How does multi-tenancy work for operators?
Multi-tenant mode is an Enterprise feature. Each tenant has its own tenant_id, and Postgres RLS policies enforce per-tenant data isolation at the database layer — not just at the application layer. The saebooks DB role has rolbypassrls=t (the owner/migration role); the saebooks_app role used by the running application has rolbypassrls=f (NOBYPASSRLS) so RLS policies bind it on every request. See Tenancy & RLS for the full picture.