Skip to content
Apier.no
Guides

How to check a receipt in the public receipt log

Every receipt Apier issues is logged as a hash in a public, append-only hash chain, anchored daily. Check that your receipt was logged, when, and that the log up to its daily anchor was not rewritten, with no API key.

[Cite this as: Apier.no Docs v0.1.0 — last updated 2026-10-29]

Every receipt Apier issues is appended, as a hash and never as content, to a public receipt log: an ordered, append-only hash chain that anyone can read without an API key. If you hold a receipt, you can check three things yourself:

  1. that the receipt was logged,
  2. when it was logged, and
  3. once a daily anchor covers it, that no entry up to that anchored head has been rewritten since. Entries after the newest anchor are covered when a later anchor records them.

The log holds no receipt content, no org number and no personal data. Every field is a sequence number, a closed label, a hash or a time.

Step 1 — Compute your entry hash

The entry hash of a receipt is the SHA-256 hex digest of the receipt's signature string, as UTF-8 text:

printf '%s' "<the receipt signature you hold>" | sha256sum

Only someone who holds the receipt has its signature, so the log reveals nothing about receipts you do not hold.

Step 2 — Look the hash up

curl "https://www.apier.no/api/v1/public/receipt-log?entry_hash=<your entry hash>"

One entry in data.entries means the receipt is in the log. Its seq is its position and logged_at is when it was appended, in the same database transaction that issued the receipt. An empty entries array means the hash is not in the log.

Step 3 — Check the chain

Each entry links to the one before it:

chain_hash = sha256_hex(utf8(prev_chain_hash + ":" + seq + ":" + kind + ":" + entry_hash))

The prev_chain_hash of seq 1 is 64 zeros, and every other entry's prev_chain_hash must equal the previous entry's chain_hash. The formula is also served in every response as data.chain.formula. Page the log from the start with after_seq=0, then pass data.next_after_seq as after_seq until it is null, and recompute every link. One changed, removed or reordered entry breaks every chain_hash after it.

Step 4 — Hold the log to its daily anchors

Once a day Apier's anchor job records the log's head as of the end of the previous UTC day: the highest seq logged before midnight UTC and its chain_hash. The same response lists the newest of these in data.anchors:

{
  "anchors": [
    {
      "anchor_date": "2026-10-29",
      "seq": 1,
      "chain_hash": "70112cfb2e30c37c2992be67c584d83ce50ab3322b2abf8afc03ab26183091c6"
    }
  ]
}

A receipt with seq n was logged by the end of the first anchored day whose seq is n or more. The chain you recomputed in Step 3 must reach that anchored chain_hash at that seq. Keep a copy of the anchors you have seen: an anchored head that later changes, or a chain that no longer reaches it, means the log was rewritten. seq 0 with 64 zeros means the log was still empty that day.

The anchors live on the same append-only daily anchor rows that commit to the company snapshot archive, served at /api/v1/public/anchors. The RFC 3161 timestamp on those rows covers the snapshot archive's Merkle root, not the receipt log head.

Reference

  • Endpoint: GET /api/v1/public/receipt-log, keyless, rate limited per IP.
  • Parameters: after_seq (integer from 0), limit (1 to 100, default 50) and entry_hash (64 lowercase hex characters). Any other parameter, or a value out of range, is a 400 VALIDATION_FAILED.
  • Caching: a strong ETag, 304 Not Modified on a matching If-None-Match, and Cache-Control: public, max-age=300.
  • Before the log exists in an environment, data.log_status is not_initialised and both entries and anchors are empty.
  • kind is a closed list that is only ever extended; receipt is the only value today.
Building with an LLM? Read llms.txt for agent-oriented integration guidance.