Architecture
How a request moves through AccountingKit, and what the platform guarantees at each step.
Two surfaces, one policy
Your systems authenticate with environment-scoped API keys against the /v1 API. Your team
authenticates with a browser session in the developer console. Both surfaces execute the same
service and ownership policy, and a browser session is never accepted as machine authentication on
/v1.
PostgreSQL owns normalized resources, encrypted raw payloads, durable operations, the outbox, idempotency records, provider budgets, webhook deliveries, usage, and audit history. Your accounting provider remains the source of truth for accounting records; AccountingKit never becomes a competing ledger.
Provider isolation
One provider-neutral interface sits in front of every adapter, and provider SDK objects never cross that boundary into application code. Adding a provider therefore cannot change the shape of the public contract, and provider-specific depth stays reachable through explicit native actions instead of leaking into the normalized model.
Public request and response schemas are validated once, at the HTTP boundary, against definitions shared by the API, the developer console, and the TypeScript SDK. The three cannot drift apart.
Read and write path
Cached list reads are served from PostgreSQL for predictable latency and stable pagination. A point read acquires the connection's provider budget, refreshes OAuth tokens if needed, calls the provider, normalizes the response, and updates the cache in the same pass.
A write validates the normalized contract, maps it to the provider's representation, enforces idempotency and plan limits, calls the provider under its budget, caches the normalized result, and records an immutable usage event. Provider calls are never made inside a database transaction, so a slow provider cannot hold a lock on your data.
Durable work
Creating an automation commits the operation and its outbox record in a single transaction, so work
is never acknowledged without being recorded. A worker claims the operation with
FOR UPDATE SKIP LOCKED under a lease that expires, which is what allows recovery after a worker is
terminated mid-run. Retries preserve input and error history, and terminal exhaustion is visible as
dead_letter rather than a silent drop.
Failure is a state, not a gap
The design commitment behind all of the above is that every failure has an address. A failed automation has an operation ID, a persisted terminal error, and a replay endpoint. A failed webhook has a delivery ID, an attempt history, and a replay endpoint. A missed provider notification is caught by scheduled reconciliation. Nothing is expected to be recovered by reading logs.