reference api · v1

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.

Status: Reference API is live at 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.

EndpointPurposeCost
GET /v1/verify/<claim_id>Return signed envelope + tri-state outcomefree
GET /v1/claim/<claim_id>Return raw records and attestationsfree
POST /v1/stampSubmit 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:

The substrate operator never holds attester private keys. Attesters sign locally and submit signed bytes.

GET /v1/verify/<claim_id>

GETfree

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)

matchesSignature valid, records root reconciles
driftedSignature valid, but records root no longer reconciles
unverifiableBad signature, missing key, or malformed envelope

POST /v1/stamp

POSTmetered

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>

GETfree

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

StatusCodeMeaning
400malformed_envelopeJSON is invalid or missing required fields
401bad_signaturesecp256k1 verification failed
404claim_not_foundNo record with that claim_id
409records_root_driftRe-derived root does not match submitted root
429rate_limitedFair-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):

If any of the above change, the change is a substrate-version bump — not a backwards-compatible update.