API.
Programmatic access to the Anima Provenance substrate. Reads are free, forever — that's invariant I-1. Stamping (writing claims) is metered. The same primitives are exposed via the CLI and SDKs.
http://129.213.98.159:8102/api/provenance. Reads are free per invariant I-1. Stamping is not yet open. Try a live read with the browser verifier or curl it directly.
Overview
The API surface is intentionally small. Three endpoints. JSON in, JSON out. Frozen schema.
| Endpoint | Purpose | Cost |
|---|---|---|
GET /v1/verify/<claim_id> | Return signed envelope + tri-state outcome | |
GET /v1/claim/<claim_id> | Return raw records and attestations | |
POST /v1/stamp | Submit a new signed claim (attesters only) | metered |
Authentication
Read endpoints (verify, claim) are unauthenticated. No API key needed, no rate ceiling beyond fair-use. This is doctrine, not policy — see invariant I-1.
Write endpoints (stamp) require:
- An attester keypair (secp256k1) registered with the substrate
- A signed envelope using the frozen EIP-712 domain
- A valid attester registration record
The substrate operator never holds attester private keys. Attesters sign locally and submit signed bytes.
GET /v1/verify/<claim_id>
Return the signed envelope, records root, attestations, and a tri-state outcome.
$ curl http://129.213.98.159:8102/api/provenance/v1/verify/0x19e7…ff2a
{
"claim_id": "0x19e7…ff2a",
"envelope": {
"domain": "AttestationReceipt",
"attester": "0x4f2c…91a8",
"records_root": "0xa8f0…0c12",
"signature": "0x…"
},
"outcome": "matches",
"attestations": [ { "type": "fact-check", "value": "…", "sig": "0x…" } ],
"ots_proofs": []
}
Outcomes (tri-state)
matches | Signature valid, records root reconciles |
drifted | Signature valid, but records root no longer reconciles |
unverifiable | Bad signature, missing key, or malformed envelope |
POST /v1/stamp
Submit a signed claim. The substrate anchors the records root and returns the assigned claim ID. Pricing TBA.
$ curl -X POST http://129.213.98.159:8102/api/provenance/v1/stamp \
-H 'content-type: application/json' \
-d @envelope.json
{
"claim_id": "0x19e7…ff2a",
"records_root": "0xa8f0…0c12",
"anchored_at": "2026-05-19T14:22:11Z"
}
GET /v1/claim/<claim_id>
Return the raw records and all attestations attached to a claim. Use this when you want to re-derive the records root yourself.
$ curl http://129.213.98.159:8102/api/provenance/v1/claim/0x19e7…ff2a
{
"claim_id": "0x19e7…ff2a",
"records": [ … ],
"attestations": [ … ]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | malformed_envelope | JSON is invalid or missing required fields |
| 401 | bad_signature | secp256k1 verification failed |
| 404 | claim_not_found | No record with that claim_id |
| 409 | records_root_drift | Re-derived root does not match submitted root |
| 429 | rate_limited | Fair-use ceiling exceeded (writes only) |
anima-verify CLI
Static binary. No daemon. No network calls unless you point it at an endpoint. Apache-2.0.
# verify a local envelope file
$ anima-verify envelope.json
matches claim_id=0x19e7…ff2a attester=0x4f2c…91a8
# verify against a remote substrate
$ anima-verify --substrate http://129.213.98.159:8102/api/provenance 0x19e7…ff2a
# print parity fixture (smoke test)
$ anima-verify --parity-fixture
Binaries available for linux/amd64, darwin/arm64, windows/amd64. Distribution channel pending — until then, build from source (private beta).
npm · TypeScript
Browser- and Node-compatible verifier twin. Byte-parity tested against the Go reference.
// installation (when published)
npm install @anima/provenance-verify
// usage
import { verify } from '@anima/provenance-verify';
const result = await verify(envelopeJson);
// result.outcome: "matches" | "drifted" | "unverifiable"
// result.claim_id, result.attester, result.records_root
The browser verifier on Verify.html uses this same library.
Go module
The Go reference implementation lives at anima.kerneld/pkg/provenance/verify. Vendor it directly or import as a module.
import "anima.kerneld/pkg/provenance/verify"
result, err := verify.Envelope(envelopeBytes)
if err != nil { /* unverifiable */ }
switch result.Outcome {
case verify.Matches: // ✓
case verify.Drifted: // ⚠
case verify.Unverifiable: // ✕
}
Wire format
JSONL — one signed envelope per line. EIP-712 typed data structure with frozen domain strings. The full TYPE_STRING and field order are part of the immutable surface (I-6) — changing them breaks verifier parity.
{"domain":"AttestationReceipt","claim_id":"0x…","attester":"0x…","records_root":"0x…","signature":"0x…","ots_anchor":null}
{"domain":"AttestationReceipt","claim_id":"0x…","attester":"0x…","records_root":"0x…","signature":"0x…","ots_anchor":null}
Parity fixture
A deterministic envelope your verifier can re-derive end-to-end as a smoke test. If your implementation returns the same records_root from the same input bytes, you have parity with the reference.
$ anima-verify --parity-fixture
input_bytes_sha256: 0x6c2f…7e91
records_root: 0xa8f0…0c12
outcome: matches
Frozen schema
The substrate's immutable surface (per invariant I-6):
- Hash primitive:
keccak256only - Signature primitive:
secp256k1only - Envelope schema: frozen field set, frozen field order
- EIP-712
TYPE_STRING: frozen - Records root construction: frozen
If any of the above change, the change is a substrate-version bump — not a backwards-compatible update.