Company data usually travels as CSV or PDF. Once you receive a file, you cannot tell whether it was altered after the fact, and "who issued this" rests on the sender's say-so.
I built a small API that returns company records as Ed25519-signed JSON, so a receiver can check two things with math instead of trust: that the record came from the issuer, and that not one character changed since. This post walks through verifying one yourself — it takes about ten seconds, in your browser, no signup and no key.
Try it first: https://regbase.local-connect-design.trade/verify
What the API returns
GET /record/{13-digit corporate number} returns a record like this (a real one, also embedded in the demo page):
{ "record_version": "1.0", "record_type": "regbase-record/1", "subject": { "corporate_number": "1010001000006" }, "claims": [ { "field": "company_name", "value": "五洋建設株式会社", "source": "EDINET code list (listed_codes_detail.csv)", "as_of": "2026-11-07" } ], "issued_at": "2026-11-08T08:41:36Z", "issuer": "RegBase T3 sandbox", "signature": { "algorithm": "Ed25519", "key_id": "9f01575a05050b62", "signature": "Uc17FDpy4evmULHWi6G7DAoww7TnJmjhqWxNxNzjPfWQx/86lDFukbEkQX10fXWkBbNMTUBtDAt5j0UzK+MTAA==" } } Two design points:
- Every claim carries its own provenance — a
sourcedataset and anas_ofdate. A signature proves issuer and integrity; it does not prove a claim is true. Provenance per claim is what lets a receiver judge the rest. - The signed payload is the record minus the
signatureobject, canonicalised as: object keys sorted recursively, compact JSON, UTF-8, no trailing newline. The verifier rebuilds the exact same bytes and checks them against the published public key.key_idis the leading bytes of the key's SPKI SHA-256, so a key rotation changes the ID visibly.
The walkthrough
- Open the demo: https://regbase.local-connect-design.trade/verify — it loads the sample record above and verifies it on the spot, entirely with WebCrypto in your browser. Nothing is sent to a server.
- Enter a corporate number (for example
1010001000006) and fetch a fresh record; it verifies the same way. - Paste any record JSON you have — verification works offline.
- Press the Tamper button. It changes exactly one character of a claim value and leaves the signature untouched. Verification now fails with
SIGNATURE_INVALID. Hand-editing any field fails the same way.
Failures come back with the same reason names the API itself uses: RECORD_MALFORMED, UNSUPPORTED_ALGORITHM, KEY_ID_MISMATCH, SIGNATURE_INVALID.
Endpoints
No auth is needed for the sandbox:
-
GET /record/{corporate_number}— a signed record -
POST /verify— send a record JSON, get the verification result -
GET /openapi.json— the OpenAPI definition -
GET /health— status and the currentkey_id
Data sources are public Japanese datasets — the National Tax Agency corporate number publication and EDINET. The current coverage is stated inside each record's source and as_of fields; an unknown number returns 404.
The hard part was not the signature
Signing is a few lines with any modern library. The fiddly part is deciding what exactly gets signed: JSON byte output changes with key order and whitespace, so the canonicalisation has to be fixed first and implemented identically on the issuing and verifying sides. Both sides are public here — the rules are in the OpenAPI description and in the demo page's code — so anyone can re-implement the verifier independently instead of trusting mine.
Links
- Verification demo: https://regbase.local-connect-design.trade/verify
- OpenAPI: https://regbase.local-connect-design.trade/openapi.json
- Postman public workspace: https://www.postman.com/regbase-198277/workspace/regbase/overview
- Free sample data (the daily registry diff, with the full-day census): https://github.com/lexusdev7/regbase-sample-data and on Zenodo, DOI 10.5281/zenodo.24046548
If you work with company data and have opinions on what a verifiable record should or should not promise, I would like to hear them.
