Nordlet

Docs / Guides

API conventions

Request shape, auth scopes, list queries, money, errors, idempotency, rate limits.

Request shape

Every operation is an RPC-style POST:

POST /v1/{module}/{resource}/{action}

Modules: reference, partners, catalog, sales, purchases, ledger, bank, declarations, files, webhooks, audit. Actions are verbs: create, get, update, delete, list, plus domain actions like issue, register, match, apply-advance, generate.

Authentication and scopes

Authorization: Bearer <api key>. Keys carry scopes checked per action:

  • {module}:read — get/list actions
  • {module}:write — mutations
  • {module}:* or * — wildcards

Missing scope → 403 forbidden. Missing/invalid key → 401 unauthorized.

List queries

All list actions share one contract:

{
  "page": 1,
  "pageSize": 50,
  "sort": [{ "field": "createdAt", "dir": "desc" }],
  "filter": [{ "field": "paymentStatus", "op": "eq", "value": "unpaid" }]
}

Filter operators: eq, neq, gt, gte, lt, lte, like, in. Sortable and filterable fields are whitelisted per endpoint (see the OpenAPI spec); unknown fields return 400. Responses: { rows, total, page, pageSize }.

Money and dates

Monetary values are decimal strings with up to 4 decimal places ("121.0000") — never floats. VAT is computed per line with half-up rounding. Dates are YYYY-MM-DD; timestamps are ISO 8601 UTC.

Errors

One envelope everywhere:

{
  "error": {
    "code": "validation",
    "message": "Request validation failed",
    "requestId": "k1x…",
    "fieldErrors": { "lines.0.unitPriceExclVat": ["Invalid value"] }
  }
}

Codes: validation (400/422), unauthorized (401), forbidden (403), not_found (404), conflict (409), idempotency_key_reuse (422), idempotency_in_progress (409), rate_limited (429), internal (5xx). Include the requestId when reporting issues.

Idempotency

Add Idempotency-Key: <any string ≤255 chars> to any mutating call:

  • Retry with the same key and payload → the stored response is replayed byte-for-byte with x-idempotent-replay: true.
  • Same key, different payload → 422 idempotency_key_reuse.
  • Concurrent duplicate while the first is in flight → 409 idempotency_in_progress.
  • Keys expire after 24 hours. Error responses (4xx) are replayed too; 5xx responses are not stored so retries re-execute.

Rate limits

300 requests/minute per API key (per presented credential; unauthenticated requests are limited per IP). On 429, honor the retry-after header. x-ratelimit-* headers report your budget.

Webhooks

Events are written to a transactional outbox in the same database transaction as the change — a rolled-back document never emits an event, and no event is lost. Deliveries:

  • POST to your URL with the event JSON
  • x-nordlet-signature: sha256=<hex HMAC-SHA256 of the raw body> using your subscription secret
  • Retries with exponential backoff on non-2xx responses

Verify signatures with a constant-time comparison before trusting the payload.

Audit

Every mutation is recorded (actor, action, entity, diff) and queryable via audit/list.

Accounting guarantees

  • Journal postings are balanced — enforced by a deferred database trigger at commit time, not just application code.
  • Document numbers are gapless per series/year; allocation is concurrency-safe.
  • Locked accounting periods reject any posting dated inside them (409).
  • Soft-deleted documents keep their audit trail; issued/registered documents cannot be deleted.