Live: attestation-api-production-c3d9.up.railway.app
A REST API for attestation-ledger —
human-in-the-loop provenance for AI-generated assertions, as a service instead
of a library you have to npm install.
A model can propose. Only a named human can attest.
Every value submitted carries who or what asserted it. A model-generated
value can never resolve to attested no matter what fields are sent with it
— that rule is enforced in attestation-ledger itself, not in this API layer.
Attestations decay on their own unless a named human re-verifies them before
the TTL runs out.
npm install
cp .env.example .env # set API_KEY at minimum
npm start # listens on :3000 (or $PORT)
npm test # vitest, 13 testsEvery write route (POST /assertions, /attest, /reverify, /reject)
requires Authorization: Bearer <API_KEY>. Reads (GET) are public — the
property worth protecting is who can write to the ledger, not who can read
it. Generate a key with:
node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"| Method | Path | Auth | Does |
|---|---|---|---|
POST |
/assertions |
✓ | Propose a value. Body: { payload, model, rationale }. |
GET |
/assertions |
List all assertions, with a status tally. | |
GET |
/assertions/:id |
Fetch one assertion. | |
POST |
/assertions/:id/attest |
✓ | A named human signs it. Body: { by, role, basis, verified, ttlDays }. 400 without by and basis. |
POST |
/assertions/:id/reverify |
✓ | Same operation as attest — resets the decay clock, keeps the prior record in attestation.supersedes. |
POST |
/assertions/:id/reject |
✓ | A named human turns it down. Body: { by, role, reason, reviewed }. Stays in the dataset, doesn't decay. |
GET |
/health |
Liveness check. | |
GET |
/ |
API description and endpoint list — human-friendly landing page, not a real resource. |
curl -X POST localhost:3000/assertions \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $API_KEY" \
-d '{"payload":{"hpf":"IN","lf":-5},"model":"Claude","rationale":"bright-instrument pattern"}'
# -> { "id": "...", "status": "proposed", ... }
curl -X POST localhost:3000/assertions/<id>/attest \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $API_KEY" \
-d '{"by":"David Petry","role":"FOH engineer","basis":"Verified at the console","verified":"2026-08-27"}'
# -> { "status": "attested", "daysRemaining": 730, ... }DATABASE_URL set → Postgres (src/stores/postgresStore.js), records
persist across restarts and deploys, schema created automatically on first
connection. Unset → an in-memory Map (src/stores/memoryStore.js), fine
for local dev, gone on restart. Both implement the same four-method
interface (insert/get/replace/list), selected in src/store.js and
injected into createApp({ store }) — swapping backends again means adding
a new file in src/stores/, not touching the route layer.
Runs on Railway: this service plus a Postgres
instance, wired together via DATABASE_URL (a Railway reference variable,
${{Postgres.DATABASE_URL}}). API_KEY is set directly on the service.
Health checks hit /health.
Auto-deploy on push to main requires Railway's GitHub App to be properly
installed on this repo, not just authorized. Those are two different
things in GitHub's settings — authorization alone lets Railway act via API
(manual redeploys work), but only an install registers the push webhook. Check
at github.com/settings/installations:
if attestation-api (or "All repositories") isn't listed there under Railway,
auto-deploy silently does nothing and every push needs a manual "Deploy" in
the Railway dashboard, or connect-service-source via the Railway MCP tools.
attestation-ledger was extracted from the
Live Sound EQ SOP and
is reused unchanged by the DJ Mixing SOP.
This API wraps the same engine so the same enforcement — model proposes, named
human attests, unattested values decay — is reachable over HTTP by any client,
not just JavaScript.
MIT