Public

Published daily Merkle anchors over the company snapshot archive

Zero-auth, cacheable Category A endpoint publishing the daily Merkle anchor chain over Apier's append-only company snapshot archive. One anchor per UTC day: merkle_root is a SHA-256 Merkle root committing to every distinct company state first observed that day, so altering or backdating archived history after its day has been anchored becomes publicly detectable rather than merely denied. WHY this exists: append-only-by-trigger is a claim about our own database that no third party is obliged to believe; a published, externally timestamped root turns the archive from an assertion into evidence, and the chain is worth exactly as much as its age.

Tree convention (algorithm: sha256-rfc6962): RFC 6962-style domain separation — leaf hash = SHA-256(0x00 || leaf bytes), internal node = SHA-256(0x01 || left || right) — combined pairwise bottom-up with the final node of an odd level promoted unchanged; this construction is root-equivalent to RFC 6962's recursive largest-power-of-two split for every leaf count, so a standard RFC 6962 verifier interoperates directly. Leaves are the UTF-8 canonical-JSON of exactly { content_hash, first_observed_at, org_number }, sorted ascending by (first_observed_at, org_number, content_hash). Leaves are DELIBERATELY payload-free: the snapshot payload (which contains person display names) is never read, hashed, stored or exposed by the anchoring path — content_hash already commits to the payload bytes, so anchoring the hash anchors the content without touching it. A zero-row day still writes an anchor with leaf_count: 0 and the empty-tree root (SHA-256 of empty input), so a GAP in the chain always means a failure, never a quiet day.

External timestamp (best-effort): tsa_provider / tsa_obtained_at report whether an RFC 3161 timestamp token was obtained from a public timestamp authority over the root; both are null while unattached (the next daily run retries). The token's messageImprint is SHA-256 over the UTF-8 lowercase-hex root string. The raw DER token bytes are deliberately not returned on this surface — they are multi-KiB and interesting only to a verifier, and can be exposed on a dedicated surface later if demand appears.

No personal data exists anywhere on this endpoint: every field is derived hash material, a date or a count (GDPR Article 5 data minimisation holds by construction, not by filtering).

Caching: strong SHA-256 ETag, If-None-Match honoured with 304 Not Modified, Cache-Control public, max-age=300 on success and no-store on errors. Rate limiting: soft per-IP cap of 1000 requests per minute (Category A — discovery is never hard-blocked).

Chain genesis and coverage: anchoring begins at the FIRST anchor_date in the chain — snapshot rows observed before that date are not committed to any root, so this list proves archive integrity from genesis forward only, never retroactively. Read absence accordingly: a date before genesis is simply outside the chain's coverage, while a missing date after genesis always means a failure (quiet days still anchor the empty-tree root, and the daily job back-fills failure gaps oldest-first).

Deployment state: data.chain_status is not_initialised when the anchor table has not been migrated yet in this environment — the response is still a normal 200 with anchors: [], so discovery agents get a valid envelope, never a 500, on a pre-migration deploy. active means the table exists; an empty list then just means no anchor has been computed yet.

Drift-detection limits (operational honesty): the daily job re-verifies only the NEWEST 7 anchored days, and only by row COUNT against each stored leaf_count (the archive is append-only, so a late-arriving backdated row is the only possible drift). It is an alarm for backdated writes, not a cryptographic re-proof of older days — full verification of any day remains the external verifier's Merkle-recomputation job, which is what the published roots exist for.

get/api/v1/public/anchors

Request

  • Base URL: https://www.apier.no
  • URL: https://www.apier.no/api/v1/public/anchors
  • Auth: none required

Query parameters

limitinteger
Example:7

How many of the most recent anchors to return, newest first. Integer 1-100; omitted means 30. Any other value (zero, negative, non-integer, above 100) is a 400 VALIDATION_FAILED — never silently clamped.

Response

The most recent anchors, newest first. An empty anchors array is a valid response on a deployment whose chain has not started yet (pre-migration or pre-first-run) — it means "no anchors exist", never "the chain is unavailable".

successtrue required

Example response

{
  "_meta": {
    "rulebook_version": "v1.0.0",
    "source": "apier.no",
    "data_source": "Brønnøysund Enhetsregisteret + Apier Universal Rulebook",
    "schema_version": "1.0.0",
    "legal_basis": "NLOD — public registry reuse",
    "response_timestamp": "2026-04-24T12:00:00.000Z",
    "response_hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "source_snapshot_id": "brreg-2026-04-24T02:00:00.000Z",
    "cost_nok": 0.5,
    "wallet_remaining_nok": 9.5,
    "spend_cap_remaining_nok": 12.5
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.