Quickstart
UTP v1

Umbra Trust Protocol

Get to your first verified record in 5 minutes

You'll send an AI decision event to the UTP ledger, verify it with a public endpoint, and confirm it's sealed into an RFC3161-timestamped chain — without touching any infrastructure.

Estimated time: 5 minutes


01

Prerequisites

You need one API key tied to your organisation. Request access here — keys are typically issued within one business day. The key looks like:

API KEY FORMAT
utp_live_a3f7b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
Your key is shown exactly once. Store it in a secrets manager or environment variable immediately. The server stores only the SHA-256 digest — the raw key cannot be recovered.

Set it as an environment variable for the examples below:

export UTP_API_KEY="utp_live_your_key_here"
$env:UTP_API_KEY = "utp_live_your_key_here"
02

Send your first record

POST to /api/v1/records with a JSON body describing the AI decision event. Every record is immediately hashed, chained to the previous entry, and sealed.

$ curl -s -X POST https://kaironull.com/api/v1/records \
  -H "Authorization: Bearer $UTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pipeline_id":  "loan-approval-v2",
    "entry_type":   "pass",
    "stage_slug":   "credit-check",
    "summary":      "Automated credit check passed for application #8821",
    "trust_score":  0.91,
    "model_version": "gemini-2.5-flash",
    "detail": {
      "applicant_id": "APP-8821",
      "score_band":   "A",
      "rule_version": "CREDIT-RULES-2026-Q2"
    }
  }'
import os, requests

resp = requests.post(
    "https://kaironull.com/api/v1/records",
    headers={
        "Authorization": f"Bearer {os.environ['UTP_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "pipeline_id":   "loan-approval-v2",
        "entry_type":    "pass",
        "stage_slug":    "credit-check",
        "summary":       "Automated credit check passed for #8821",
        "trust_score":   0.91,
        "model_version": "gemini-2.5-flash",
        "detail": {
            "applicant_id": "APP-8821",
            "score_band":   "A",
        },
    },
)

record = resp.json()
print(f"Entry hash: {record['entry_hash']}")
const res = await fetch('https://kaironull.com/api/v1/records', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.UTP_API_KEY}`,
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    pipeline_id:   'loan-approval-v2',
    entry_type:    'pass',
    stage_slug:    'credit-check',
    summary:       'Automated credit check passed for #8821',
    trust_score:   0.91,
    model_version: 'gemini-2.5-flash',
    detail: { applicant_id: 'APP-8821', score_band: 'A' },
  }),
})

const { entry_hash } = await res.json()
console.log(`Hash: ${entry_hash}`)

Response 201 Created

{ "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "entry_hash": "a3f7b2c1d4e5f609...c8d9e0f1", // 64-char hex, SHA-256 "prev_hash": "GENESIS", // or previous entry_hash "written_at": "2026-07-13T06:05:00.000Z" }
entry_hash Save this. You'll use it to verify the record and link it to an audit trail or evidence bundle.

entry_type accepts: pass · flag · block · escalate


Required and optional fields:

Field Description
pipeline_id requiredYour pipeline or process identifier
entry_type requiredpass · flag · block · escalate
stage_slug requiredThe pipeline stage (e.g. credit-check)
summary requiredHuman-readable description of the decision
trust_score optional0.0 – 1.0 confidence float
model_version optionalModel identifier string
data_sources optionalArray of source identifiers
detail optionalArbitrary JSON — stored verbatim
input_payload optionalThe input that drove the decision
system_state optionalSnapshot of relevant system state
03

Verify a record

Anyone — including regulators and auditors — can verify a record using its entry_hash. This endpoint requires no authentication.

$ curl -s https://kaironull.com/api/v1/verify/a3f7b2c1d4e5f609...c8d9e0f1
entry_hash = record["entry_hash"]

resp = requests.get(
    f"https://kaironull.com/api/v1/verify/{entry_hash}"
)
result = resp.json()

if result["found"]:
    print("✓ Record verified on-chain")
    if result["anchor"]["anchored"]:
        print(f"  Anchored at {result['anchor']['anchored_at']}")
else:
    print("✗ Hash not found")
const vRes = await fetch(
  `https://kaironull.com/api/v1/verify/${entry_hash}`
)
const { found, anchor } = await vRes.json()

if (found) {
  console.log('✓ Record verified on-chain')
  if (anchor.anchored)
    console.log(`  RFC3161 anchor: ${anchor.anchored_at}`)
} else {
  console.error('✗ Hash not found')
}

Response 200 OK — verified and anchored

{ "found": true, "record": { "id": "f47ac10b-...", "pipeline_id": "loan-approval-v2", "entry_type": "pass", "stage_slug": "credit-check", "summary": "Automated credit check passed for #8821", "trust_score": 0.91, "entry_hash": "a3f7b2c1...", "prev_hash": "GENESIS", "written_at": "2026-07-13T06:05:00.000Z" }, "anchor": { "anchored": true, "anchored_at": "2026-07-13T07:05:03Z", // from FreeTSA RFC3161 "root_hash": "d9e8f7a6..." } }
anchor.anchored will be false until the next hourly anchor run completes. Records are in the ledger immediately; the RFC3161 timestamp is applied within the hour.

Response — not found

{ "found": false }
04

Check chain status

A public endpoint returns the current ledger tip and last anchor. Use it in dashboards, uptime monitors, or compliance checks.

$ curl -s https://kaironull.com/api/v1/chain/status | jq
{ "chain_height": 1, "latest_entry_hash": "a3f7b2c1...", "latest_written_at": "2026-07-13T06:05:00.000Z", "last_anchor": { "root_hash": "d9e8f7a6...", "anchored_at": "2026-07-13T07:05:03Z", "entry_count": 1 }, "healthy": true }
healthy: true — the chain tip is within 10 minutes of now
healthy: false — no recent write; check your ingestion pipeline
05

How the chain works

Each record is linked to the one before it. Altering any record breaks every hash that follows — making the chain tamper-evident by construction.

Entry 1
prev: GENESIS
hash: a3f7b2c1…
Entry 2
prev: a3f7b2c1…
hash: 9d2e4f8a…
RFC3161 Anchor
root: SHA-256 of all hashes
FreeTSA timestamp token

Every hour, an anchor job runs:

  1. All entry_hash values are concatenated in chronological order
  2. A SHA-256 root hash is computed over the concatenated bytes
  3. A DER-encoded RFC3161 TimeStampReq is submitted to FreeTSA
  4. The signed timestamp token is stored alongside the root hash
The ledger is append-only. Database triggers block any UPDATE or DELETE on umbra_ledger. Records are permanent from the moment they land.
06

Next steps