Accountflow Developer next

Conventions

The rules every endpoint follows — read once, rely on everywhere.

The rules that hold everywhere. Learn them once; every surface obeys them.

Identity and shape

Reach

Your token defines your reach: an API client sees its organization and the organizations below it. Anything outside your reach is a 404 — never a 403 — whether it exists or not. Resource ids are not guessable capabilities.

Settings writes

Company settings (settings:write) change as patches: a field you leave out stays as it is, and reset (where an operation has one) clears a value or hands it back to its default. Each settings read returns the version it shows — version, companyRoutineVersion or an account's settingsRevision — and each write takes it back as expectedVersion or settingsRevision. If the settings changed in between, the write is 409 conflict with the currentVersion: read them again and re-apply your change to what is there now, rather than retrying with the new version.

Pagination

Lists return:

{ "data": [ ... ], "pagination": { "nextCursor": "…", "hasMore": true } }

Follow nextCursor until it is null (it is nullable, and hasMore is false on the last page). Cursors are opaque, signed, and bound to your client and the exact filters — reuse with changed filters is invalid_cursor (restart from page one). pageSize accepts 1–200, default 50; a value outside that range is refused with 400 validation_failed (it is not clamped). Small bounded lists (chart of accounts, VAT terms, users) return everything with a null cursor.

Idempotency

Every mutation requires an Idempotency-Key header (any unique string; use a UUID per logical action). Retrying with the same key returns the stored response, byte-identical, flagged with Idempotency-Replayed: true. The same key with a different body is idempotency_key_reused (422). This makes timeouts safe: when in doubt, retry with the same key.

Keys are scoped per API client and per operation: the same key on two different operations is two different keys, and another client can never replay yours. Stored responses are replayed for 24 hours; a retry after that window executes the operation again, as a new request. One consequence worth knowing: a replayed create_document_upload returns the signed upload URL as it was stored, which may already have expired — on an expired upload URL, start a new upload with a new key.

When you PUT the file to that URL, send every header in upload.headers, exactly as given: Content-Type, x-goog-content-length-range (the body may not be larger than the sizeBytes you declared) and x-goog-if-generation-match: 0 (the URL creates the file once; it cannot replace it). They are part of the signature, so storage refuses a PUT without them. To send a different file, start a new upload.

400 or 422?

The two validation statuses split on shape versus meaning:

Both carry the same errors[] item shape ({field, message, index?, allowedValues?}) — see the error catalog. 409 conflict is neither: the request is fine, the state is not.

Async work

Heavy operations return 202 Accepted with the resource to poll — for imports and corrections, the import itself (GET /v1/companies/{companyId}/imports/{importId}). That resource names its jobId, which is also pollable at GET /v1/jobs/{jobId} and listed by GET /v1/jobs (the jobs this API client submitted, newest first, optional status filter). Poll until the status is terminal, or subscribe to job.succeeded/job.failed webhooks. One job per (type, scope) is in flight at a time — a duplicate submission is a 409 whose jobId extension names the blocking job.

Rate limits

Per-client quotas by plan: standard 300 requests/min (burst 60), premium 1200/min (burst 240). 429 responses carry Retry-After; every response carries RateLimit-Limit and RateLimit-Remaining. Design for backoff; for bulk reads prefer a larger pageSize over more requests.

Honest note on precision: the plan quota is enforced per API pod, without a shared counter. When the API runs on more than one replica, the effective quota is the plan figure multiplied by the replica count, and RateLimit-Remaining describes the pod that answered. Treat the published figures as the guaranteed minimum, not a hard ceiling; the gateway's volumetric limit above the plan tiers is what actually stops floods.

Versioning and deprecation

The API is /v1 and — from GA — additive-only: new fields, endpoints, and event types may appear at any time — build tolerant readers. Anything scheduled for removal is announced in the changelog, marked with Deprecation and Sunset response headers, and kept working for at least 12 months after the sunset announcement (the full policy: deprecation-policy).

Request tracing

Every response carries an X-Request-Id header. Problem documents repeat the same value as the requestId property; successful responses carry it only in the header. If you send your own X-Request-Id on a request, it is honoured and echoed back, so you can correlate against your own logs. Include it in support requests — it links directly to our logs and audit trail.

Response headers

Header On Meaning
X-Request-Id every response correlation id (see above)
RateLimit-Limit, RateLimit-Remaining every response the plan quota and what is left of it on the answering pod
Retry-After 429, 503, 409 idempotency_in_flight seconds to wait
Idempotency-Replayed replayed mutations true when the body is a stored response
Deprecation, Sunset deprecated operations see the deprecation policy
WWW-Authenticate 401, 403 insufficient_scope RFC 6750 challenge; names the missing scope