Skip to content

Latest commit

 

History

History
114 lines (70 loc) · 5.62 KB

File metadata and controls

114 lines (70 loc) · 5.62 KB

HTTP API design guidelines

This document describes how clients should use the Saldra HTTP API and how we extend it without surprise breaking changes. Product routes live under /v1; a future /v2 would preserve /v1 for integrators.

The normative contract for implemented routes is openapi/openapi.yaml. When you change a handler or response shape, update OpenAPI in the same change.


Who uses this API?

Internal web apps (SPA, mobile) and external integrators (ERP, payroll, partner services) should use the same URLs, versions, and JSON shapes. Differences are only in how the caller authenticates and which org they act within.

  • Today: interactive flows send X-Principal-ID (UUID) after auth middleware in dev; production will replace this with JWT/OIDC or similar without changing path structure.
  • Tomorrow: machine clients will use api_clients + X-API-Key (or mTLS) mapped to a principals row with kind = integration and memberships in an org. The same /v1/organizations/{orgID}/… routes apply; RBAC checks scopes the same way.

Keeping one API avoids "internal vs integration" drift and makes OpenAPI the single contract.


Versioning

  • URL prefix: All authenticated product APIs are under /v1.
  • Breaking changes: Add /v2, keep /v1 stable for a deprecation window. Do not silently change field types or meanings on /v1.
  • OpenAPI info.version: Tracks the published API contract (e.g. 1.0.0), not the Git tag of the server binary.

Authentication (current and planned)

Mechanism Header Status
Dev / legacy header X-Principal-ID Implemented
Bearer / OIDC Authorization Planned
Integration client X-API-Key Reserved

Constants live in internal/authn. principals.kind may be user or integration (migration 000011); users remains only for human principals.


Tenancy and RBAC

  • Routes under /v1/organizations/{orgID}/… require org membership; the resolved org (role + effective scopes) is attached in context by middleware.
  • Services enforce internal/authz checks (accounting:read, accounting:manage, etc.). Do not branch in handlers on role strings.

Errors

Structured errors use internal/apperror and map to JSON via internal/httpapi/respond. Clients should rely on error.code, error.message, and optional error.details.fields, not on free-text parsing.


Pagination

Convention (offset / limit):

  • Query parameters: limit (positive, capped per endpoint), offset (non‑negative).
  • Collection lists (journal entries, import batches and rows, etc.) include a meta object: { "limit": <n>, "offset": <n> } so clients can page without guessing defaults.
  • The general ledger report keeps pagination fields at the top level of the JSON body (limit, offset, has_more, total_count) for backward compatibility; it does not use a meta wrapper. See OpenAPI for each route.

Future: Keyset (cursor) pagination may be added for large tables; new query params would be additive (e.g. after_id).


Filtering and sorting

  • Use query parameters with clear names (status, date_from, …).
  • Prefer enumerated values documented in OpenAPI (enum in the spec).
  • Default sort order is documented per list endpoint (e.g. journal entries by posting date descending).

Idempotency

For mutating POST endpoints where duplicate submits are costly or dangerous (journal draft creation, post, reverse, add line, import batch commit, link attachments), clients may send:

Idempotency-Key: <opaque string, max 128 chars>

Behavior:

  • If the header is omitted, the request behaves as before.
  • If sent, the server keys by (organization_id, route pattern, idempotency key) and a fingerprint of method + path + body.
  • Same key + same request after a successful (2xx) response replays the stored status and JSON body.
  • Same key + different body/path409 Conflict.
  • Failures (non-2xx) do not persist the replay cache for that key, so the client can fix the request and retry.

Implementation: api_idempotency_records + middleware in internal/httpapi/middleware/idempotency.go.


Stable JSON shapes

  • Prefer explicit keys over wrapping everything in generic data.
  • Nullable fields are null in JSON when absent (document in OpenAPI).
  • Money amounts are minor units (e.g. øre) as integers (*_minor).
  • Dates are ISO YYYY-MM-DD where calendar dates are meant; timestamps date-time (RFC3339) for audit fields.

Background jobs

Async work is persisted in background_jobs and processed by cmd/worker. The API enqueues (e.g. import validation); clients poll GET .../accounting/jobs/{jobID} for status, last_error, and result.


Checklist for new endpoints

  1. Register route under /v1/... with existing middleware (authn → org → handler).
  2. Enforce authorization in services, not only in HTTP.
  3. Add or update openapi/openapi.yaml (paths, schemas, enums, Idempotency-Key if POST is retried).
  4. For new list endpoints, include meta with limit / offset (and has_more / total_count when available).
  5. If POST must be safe to retry, wire With(deps.Idempotency) and document the header.