---
title: "Published daily Merkle anchors over the company snapshot archive"
method: GET
path: "/api/v1/public/anchors"
tags: ["Public"]
---

# Published daily Merkle anchors over the company snapshot archive

`GET /api/v1/public/anchors`

## Request

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

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.

## Query parameters

- `limit` integer

## Response `200`

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".

- object
  - `success` true, required
  - `data` object, required
    - `anchors` object[], required: Most recent anchors, newest first, at most `limit` rows.
      - `anchor_date` string, date, required: The UTC day this root commits to (YYYY-MM-DD). The day boundary is deliberately UTC, not Europe/Oslo: it only decides which rows land in which day's tree, and a UTC cut lines up with the archive's stored instants without a DST-dependent offset.
      - `merkle_root` string, required: Lowercase-hex SHA-256 Merkle root under the sha256-rfc6962 convention described above. For a zero-leaf day this is the SHA-256 of empty input (e3b0c442…).
      - `leaf_count` integer, required: Number of distinct company states first observed on `anchor_date` and committed to by `merkle_root`. 0 is a valid quiet-day value — the row exists precisely so a chain gap always means a failure.
      - `algorithm` 'sha256-rfc6962', required: Closed tree-convention label; append-only if it ever widens.
      - `tsa_provider` string, nullable, required: Which external RFC 3161 timestamp authority granted the token (e.g. `digicert`), or null while no token is attached. The attach is best-effort and retried daily, so a persistent null on an old anchor signals a timestamping outage — the anchor itself is still valid.
      - `tsa_obtained_at` string, date-time, nullable, required: When the RFC 3161 token was obtained (RFC 3339, UTC), or null while unattached. The authoritative signed time lives inside the token itself; this column is operational bookkeeping.
    - `chain_status` 'active' | 'not_initialised', required: Infrastructure marker for the anchor chain. `not_initialised` = the anchor table has not been migrated yet in this environment, so `anchors` is empty because the chain has not started — not because data is missing. `active` = the table exists; an empty `anchors` list then means no anchor has been computed yet. Lets a pre-migration deploy stay a valid 200 on this discoverable surface instead of a 500.
  - `_meta` TrustMetadata, required: Metadata included in every response for trust and verifiability.
    - `rulebook_version` string, required: Current Rulebook version. Internal rule-version records carry structured origin metadata (`initial_seed` | `human_review` | `lovdata_drift_response` | `audit_finding` | `customer_question` | `legal_correction`) for operator forensics, but that attribution is deliberately NOT exposed on public responses to avoid leaking operational signal.
    - `data_freshness` string, date-time, required: When this data was last fetched (ISO 8601 UTC).
    - `last_verified` string, date-time, required: When this data was last verified against source (ISO 8601 UTC).
    - `source` string, required: Origin of this data. Defaults to `apier.no`. The agent-readiness probe (`GET /api/v1/health/agent-readiness`) is the one surface that may instead emit `apier.no telemetry` — it does so when at least one of its endpoint rates came from measured live telemetry, and falls back to `apier.no` when every rate is a static default; all other endpoints always emit `apier.no`.
    - `data_source` string: Authoritative origin of the underlying data (listed alongside rulebook_version, data_freshness, last_verified, legal_basis for Rulebook-influenced endpoints). Distinct from `source` (the response origin): `data_source` identifies the authoritative data publisher — e.g. 'Brønnøysund Enhetsregisteret + Apier Universal Rulebook'. Present on registry-backed endpoints.
    - `served_from` 'cache' | 'live' | 'static': Cache-aware endpoints only — 'cache' when the response was served from the local copy, 'live' when the gateway just refreshed from the upstream, 'static' when the payload was computed deterministically from pure code with no DB or upstream read (e.g. /v1/public/deadlines). Absent on endpoints that don't carry this semantic.
    - `stale` boolean: Cache-aware endpoints only — true when the upstream was unreachable and a within-limit cached row was served as a fallback. Absent on normal responses.
    - `schema_version` string, required: OpenAPI contract version at the time of the response; matches `info.version`. Always present on every `/v1/*` response so a future schema break can be traced to the exact rows / audit events that predate / postdate it. Agent-facing machine interfaces (v1/capabilities, workflows.json) may override with their own narrower surface version; all other endpoints inherit the manifest-level version.
    - `legal_basis` string: Legal basis under which the response data is reused. Present on Rulebook-influenced endpoints (v1/company/{org}/obligations, /v1/company/{org}/deadlines). Common value: 'NLOD — public registry reuse'.
    - `rule_count` integer: Rule-evaluation count — total rules evaluated for this request. Present on endpoints that invoke the Rulebook evaluator (v1/company/{org}/obligations).
    - `applicable_count` integer: Count of rules whose evaluation_result is 'applicable'. Present on evaluator endpoints.
    - `not_applicable_count` integer: Count of rules whose evaluation_result is 'not_applicable'. Present on evaluator endpoints.
    - `insufficient_data_count` integer: Count of rules whose evaluation_result is 'insufficient_data' (Tier 2 gated). Present on evaluator endpoints. rule_count = applicable_count + not_applicable_count + insufficient_data_count.
    - `cache_age_ms` integer, nullable: Cache layer: milliseconds since the cached row was computed, or null on a cache miss (served_from === 'live'). Agents use it alongside data_freshness as a staleness signal. Present on endpoints wired to the cache (v1/company/{org}/obligations, /deadlines, /summary, /v1/public/obligations, /v1/public/deadlines).
    - `response_time_ms` integer: Speed signal: total server-side processing time in milliseconds, measured via performance.now() at the route boundary. Mirrored on the `X-Apier-Response-Time-Ms` header so agents can key on it without parsing the body. Present on every endpoint.
    - `response_timestamp` string, date-time: RFC 3339 / ISO 8601 UTC timestamp stamped by the response builder at wire-exit. Mirrored on the `provenance_log.response_timestamp` column by a fire-and-forget writer. Distinct from `data_freshness` (when upstream data was fetched) and `last_verified` (when the citation was verified); this is the moment the envelope left the server. Always populated at runtime — deliberately left out of the `required` list to avoid a cascading contract change that would invalidate every pre-existing example envelope in this spec (append-only response contracts). A future revision may promote it into the `required` list alongside a bulk example refresh.
    - `response_hash` string: SHA-256 hash of the canonical-JSON form of the envelope with `_meta` stripped before hashing, prefixed with `sha256:`. Mirrored on `provenance_log.response_hash` so a consumer can independently re-hash the bytes they received and compare against what we persisted (integrity receipt). Stripping `_meta` is load-bearing: volatile fields (response_time_ms, cache_age_ms, served_from) and the hash itself would otherwise feed into the digest and break the cache-hit-bytewise-identical invariant.
    - `source_snapshot_id` string: Optional free-text pointer into an upstream sync snapshot the response derives from — e.g. a Brreg sync's `brreg-<ISO>` snapshot id. Absent when the response is not tied to a specific upstream snapshot (capabilities, tools, static deadlines).
    - `is_sandbox` true: Present and `true` only when the response was generated from sandbox fixture data (currently `ALTINN_MODE=sandbox` on `/api/v1/altinn/list-acting-capacity`). Absent (NOT `false`) on every other response — the absent-vs-false distinction is deliberate so downstream agents branching on `if (response._meta.is_sandbox)` cannot be misled into treating non-sandbox responses as positively-confirmed-not-sandbox. Distinct from the `/v1/sandbox/*` `is_sandbox: true` literal in `SandboxMeta` (which carries the sandbox surface tag); this flag rides through the production `/api/v1/altinn/*` surface to forensically tag a sandbox-mode response that an OEM evaluator received.
    - `cost_nok` number: What this call debited from the caller's prepaid credit balance, in NOK, rendered from the integer-øre ledger (50 øre → 0.5; the ledger itself never leaves whole øre and no float arithmetic touches a balance). Present on the 2xx body of every metered company-data read — the same debit that sets the `X-Credits-Cost-Ore` header, so the two never disagree. `0` when nothing was charged: credit enforcement running dark (the response then also carries `X-Credit-Check: shadow`) or the sandbox surface. Absent on unmetered endpoints and on error envelopes — a refunded 5xx/403/404 claims no cost rather than a wrong one. Lives under `_meta`, which `response_hash` excludes, so the provenance receipt is unaffected; the route's ETag is computed before the credit wrapper stamps it, so a conditional request can still short-circuit to 304. Append-only addition.
    - `wallet_remaining_nok` number, nullable: The prepaid credit balance remaining AFTER this call's debit, in NOK (same rendering as `cost_nok`; mirrors the `X-Credits-Balance-Ore` header). `null` when no balance was read: credit enforcement running dark, or the sandbox surface. Present wherever `cost_nok` is. An agent planning a sequence of metered calls can divide this by the per-call price from GET /api/v1/pricing to know how many reads remain before a 402 INSUFFICIENT_CREDITS. Append-only addition.
    - `spend_cap_remaining_nok` number: What the calling key may still spend under its tighter spend cap (Europe/Oslo calendar day or month) AFTER this call's debit, in NOK (same rendering as `cost_nok`; never below 0). Present on the 2xx body of a metered read only when credit enforcement is live AND the key has a daily or monthly cap (set with PATCH /api/v1/account/keys/{id}); absent otherwise. When it reaches 0 the next metered call on this key returns 402 SPEND_CAP_REACHED until the cap resets. Append-only addition.

## Other responses

- `304`: Not Modified: the client's If-None-Match matched the current ETag — the anchor chain is unchanged, so the held representation is still valid. Empty body per RFC 9110; ETag and Cache-Control re-emitted, and the /api/v1 proxy still stamps X-Correlation-ID and the Link service-descriptor on the 304 exactly as on the 200.
- `400`: Invalid `limit` — zero, negative, non-integer, or above 100. Nothing is silently clamped; the `explanation.details` array names the offending value.
- `429`: Per-minute rate limit reached for this (IP, endpoint) bucket on a zero-auth public endpoint. The `Retry-After` header indicates how many seconds to wait before the window resets. Public endpoints share a single soft-cap of 1000 requests per minute PER IP — there is no tier scaling, because callers aren't authenticated. Every allowed response also carries the standard `X-RateLimit-*` headers. On rare infrastructure-side failures the gateway fails OPEN and passes the request through with `X-RateLimit-Check: skipped`.

---

[API](https://skmtc.dev/apier/apis/apier-no-api.md) · [All operations](https://skmtc.dev/apier/apis/apier-no-api/llms.txt) · [OpenAPI document](https://skmtc.dev/apier/apis/apier-no-api/revisions/9ec19bc87311?raw)
