Skip to main content
The Sintropix API is a JSON over HTTPS interface to the ERP backend that owns your ledger, your invoices, your bank movements, and your reports. Every business route is scoped to a single Entity (a legal company with its own books), authenticated with a Better Auth credential (a session cookie in the app, an API key from an integration), and validated end-to-end by a shared Zod contract in @sintropix/api-contract.
This reference documents the ERP backend as implemented in apps/erp-backend. The API is currently used by Sintropix’s own web client and by Erwin (the AI assistant); a public, versioned, externally supported surface has not been announced. Endpoint paths, request shapes, and response shapes may change without notice.

Base URL

All routes are mounted under a global /api prefix (see app.setGlobalPrefix('api') in apps/erp-backend/src/main.ts). Sintropix has not published a stable customer-facing base URL; substitute your deployment’s origin.
The API is not versioned in the URL path. Contract-breaking changes are coordinated through @sintropix/api-contract version bumps and the frontend that consumes it.

Route Shape

Most business resources live under a single Entity:
For example:
A handful of routes are Entity-independent: /api/entities (list and create), /api/fx-rates, /api/countries and /api/currencies, /api/onboarding/* (staff only), /api/agents/models, and /api/health. {entityId} is always a UUID. The tenant guard rejects a request where the caller has no Entity Membership to that Entity with a 404; it never returns 403, so probing does not reveal whether an unknown Entity exists.

Authentication

Every non-anonymous request carries one of two credentials:
  • Session cookie issued by Better Auth after a browser sign-in. Same-site only (the SPA and API share a registrable domain).
  • API key in the x-api-key header. Keys are minted by the owner from the settings UI; the API management surface is barred to key holders (a request to /api/auth/* with x-api-key returns 403).
Every mutating request (POST, PUT, PATCH, DELETE) authenticated with an API key must also send a Declared Actor:
x-audit-actor is a self-reported software label (for example claude-opus-4.8) that lands verbatim in the Audit Trail. Omitting it on a key-authenticated mutation returns 400. See Authentication for the full model, including read-only keys, the Erwin Key Lease, and staff versus client roles.

Request Format

Requests are plain JSON. Include Content-Type: application/json on any request with a body.
Path parameters that look like UUIDs are validated by ParseUUIDPipe; a malformed id returns 400.

Response Format

Responses are the resource itself as JSON. There is no top-level data wrapper, no meta envelope, and no pagination block on non-paginated endpoints. The exact shape of every response is the Zod schema exported from @sintropix/api-contract and enforced by nestjs-zod’s @ZodSerializerDto at serialization time, so the compiled schema is the source of truth for field names and types. A single resource comes back as an object:
A list endpoint returns a bare JSON array:
A page endpoint (endpoints whose path ends in /pages, plus a handful of reports) returns a { rows, nextCursor } object. nextCursor is null on the last page.
Pagination is opt-in per endpoint, not sitewide. Endpoints without /pages in their path return every row and take no cursor argument. Check the individual reference page for the endpoint you are calling.

Errors

Non-2xx responses are JSON. Application errors thrown as HttpException return the exception body; unhandled errors return "Internal server error". Every response, success or failure, carries a x-request-id header for support (REQUEST_ID_HEADER in @sintropix/observability). Domain-level errors carry a stable string code in the body — for example API_KEY_READ_ONLY_ERROR from apps/erp-backend/src/auth/error-codes.constants.ts. Match on code, not on the human-readable message.

Rate Limits

Better Auth’s apiKey plugin rate-limits each key at 5,000 requests per hour, sliding window (rateLimit in apps/erp-backend/src/auth/auth.ts). Session-cookie traffic is not rate-limited at the application layer. Exceeding the limit returns 429; there is no X-RateLimit-* header response contract.

CORS and Same-Site

The API enables CORS only for origins listed in the CORS_ORIGINS deployment env var, with credentials enabled. Browser clients must share a registrable domain with the API (for example app.sintropix.com and api.sintropix.com); the session cookie is same-site and Safari drops it across sites. Server-to-server API-key traffic is unaffected.

What Is Not Documented Here

The following are not part of the current API surface, even though the previous version of this documentation referenced them:
There is no webhook system. Sintropix does not push event notifications to external URLs. The only outbound HTTP is a Slack incoming webhook used for internal operational reporting.
There is no cursor pagination on most endpoints. Only endpoints whose path ends in /pages (plus the general-ledger, open-item-lines, journal, and ledger-entries pagers) return { rows, nextCursor }. Others return the full list.
There is no Authorization: Bearer scheme. API keys go in x-api-key, sessions in a same-site cookie.
There is no /v1 prefix. The base path is /api; contract versioning is handled through @sintropix/api-contract.