Senti Trust docs

Senti Trust API

Anchor the SHA-256 fingerprint of any record on the Senti Trust ledger and verify it forever. Your data never leaves you - only fingerprints travel. Your base URL is provided privately with your credentials during onboarding.

Authentication

Organizations are onboarded by Senti after vetting. Each org holds a Client ID (public identifier) and a Client secret - generated by you in the portal, shown once, stored by us only as a salted hash. Send both on every request:

-H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5"
-H "X-Client-Secret: EjOaZ+TD60QtfJZGm4cfUbU6aFvNYe0cBxa3BudU/NIVHZN/iz7V/bCgRDJwtvXo"

Lost secrets cannot be recovered, only rotated - instantly, in the portal. Rotation never affects anchored records.

Signed requests (advanced)

For organizations whose security policy forbids transmitting credentials: generate an Ed25519 keypair in the portal (the private key is created in your browser and never reaches us - we store only the public key). Then sign each request instead of sending the secret:

canonical = "{X-Timestamp}\n{METHOD}\n{path+query}\n{sha256hex(raw_body)}"

-H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5"
-H "X-Timestamp: 2026-06-13T09:15:00Z"        # within 5 minutes of server time
-H "X-Signature: <base64 Ed25519 signature of canonical>"
# Python (pip install cryptography requests)
import base64, hashlib, json, time, requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key

key = load_pem_private_key(open("senti-signing-key.pem", "rb").read(), password=None)
ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
body = json.dumps({"tx_hash": "2ecb0a53...e2f71"}).encode()
canonical = f"{ts}\nPOST\n/v1/verify\n{hashlib.sha256(body).hexdigest()}".encode()
sig = base64.b64encode(key.sign(canonical)).decode()

requests.post("$BASE_URL/v1/verify", data=body, headers={
    "Content-Type": "application/json",
    "X-Client-Id": "STIAK7Q2M4X9P6R3T8W5",
    "X-Timestamp": ts,
    "X-Signature": sig,
})

Each signature is valid once (replays rejected) and only within ±5 minutes. Both tiers work side by side - use whichever each of your systems needs. Either tier can be disabled, re-enabled, or rotated at any time in the portal.

Quickstart

1 · Hash your record (canonical form)

UTF-8 JSON · keys sorted · no whitespace · numbers as strings · include your unique record_id.

payload = {"record_id":"GC-2026-0001","event_time":"2026-06-12T09:30:00Z","mass_kg":"412.50","material":"PET","site":"DAR-01"}
tx_hash = sha256(json.dumps(payload, sort_keys=True, separators=(',',':')))

# tx_hash = "2ecb0a5391463fa5bb3bd9193d24d9c471f3a030f780bdb7d3d9d4eecb7e2f71"

2 · Anchor it

curl -X POST $BASE_URL/v1/anchor \
  -H 'Content-Type: application/json' \
  -H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5" \
  -H "X-Client-Secret: EjOaZ+TD60QtfJZGm4cfUbU6aFvNYe0cBxa3BudU/NIVHZN/iz7V/bCgRDJwtvXo" \
  -d '{"tx_hash":"2ecb0a5391463fa5bb3bd9193d24d9c471f3a030f780bdb7d3d9d4eecb7e2f71","source_tx_id":"GC-2026-0001","payload_type":"recycling.batch","on_behalf_of":"GreenCycle Ltd","metadata":{"schema":"v1"}}'

{
  "fabric_id": "b79f815b6cc60b7de54bb750957862808013b0719844b48269c0bccad53c4b1d",
  "block_number": 7,
  "status": "COMMITTED",
  "duplicate": false,
  "anchored_at": "2026-06-12T09:30:54.140Z"
}

3 · Verify any time

curl -X POST $BASE_URL/v1/verify \
  -H 'Content-Type: application/json' \
  -H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5" \
  -H "X-Client-Secret: EjOaZ+TD60QtfJZGm4cfUbU6aFvNYe0cBxa3BudU/NIVHZN/iz7V/bCgRDJwtvXo" \
  -d '{"tx_hash":"2ecb0a5391463fa5bb3bd9193d24d9c471f3a030f780bdb7d3d9d4eecb7e2f71"}'

{
  "verified": true,
  "tx_hash": "2ecb0a5391463fa5bb3bd9193d24d9c471f3a030f780bdb7d3d9d4eecb7e2f71",
  "fabric_id": "b79f815b6cc60b7de54bb750957862808013b0719844b48269c0bccad53c4b1d",
  "source_tx_id": "GC-2026-0001",
  "payload_type": "recycling.batch.v1",
  "metadata": {"schema": "v1"},
  "anchored_by": "GreenCycleLtdMSP",
  "on_behalf_of": "GreenCycle Ltd",
  "committed_at": "2026-06-12T09:30:54.140Z",
  "block_number": 7,
  "block_data_hash": "0f6fb47f0062de7f0223f3a1fe3699046f08b89241aab3a780de51ae222380a4",
  "chain_height": 8,
  "visibility": "private",
  "verified_at": "2026-06-13T08:02:11.412Z"
}

Batch anchoring

Up to 100 fingerprints in one call, committed as one ledger transaction. Items already anchored are reported per item - the rest still commit. Retries are safe.

curl -X POST $BASE_URL/v1/anchor/batch \
  -H 'Content-Type: application/json' \
  -H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5" \
  -H "X-Client-Secret: EjOaZ+TD60QtfJZGm4cfUbU6aFvNYe0cBxa3BudU/NIVHZN/iz7V/bCgRDJwtvXo" \
  -d '{"items":[
    {"tx_hash":"2ecb0a53…e2f71","source_tx_id":"GC-2026-0001","payload_type":"collection.event","on_behalf_of":"GreenCycle Ltd"},
    {"tx_hash":"9d41c8aa…07b32","source_tx_id":"GC-2026-0002","payload_type":"collection.event","on_behalf_of":"GreenCycle Ltd"}
  ]}'

{
  "fabric_id": "b79f815b6cc60b7de54bb750957862808013b0719844b48269c0bccad53c4b1d",
  "block_number": 12,
  "count": 2,
  "committed": 2,
  "duplicates": 0,
  "results": [
    {"tx_hash": "2ecb0a53…e2f71", "status": "COMMITTED", "duplicate": false, "visibility": "private"},
    {"tx_hash": "9d41c8aa…07b32", "status": "COMMITTED", "duplicate": false, "visibility": "private"}
  ]
}

On behalf of

Every anchor names who the record belongs to: on_behalf_of is required (max 120 chars). Anchoring your own record, use your own name; platforms anchoring for customers name the customer. Verification then reads anchored by Y, on behalf of X everywhere - verify responses, certificates, public verification pages. For certificate payloads it names who the certificate was issued to - an authority anchors a permit, the holder is on the record.

{"tx_hash":"…","source_tx_id":"DF-2026-0311","payload_type":"collection.event","on_behalf_of":"Kigamboni Collection Center"}

Public verification

Anchors are private by default: verifiable only by authenticated verifiers. Anchor with "visibility": "public" and the record also answers - for anyone, no account - at:

https://trust.senti.eco/verify?hash=<tx_hash>

Embed that link in reports and certificates you distribute. Toggle any time, both directions:

curl -X POST $BASE_URL/v1/anchors/<tx_hash>/visibility \
  -H 'Content-Type: application/json' \
  -H "X-Client-Id: STIAK7Q2M4X9P6R3T8W5" \
  -H "X-Client-Secret: EjOaZ+TD60QtfJZGm4cfUbU6aFvNYe0cBxa3BudU/NIVHZN/iz7V/bCgRDJwtvXo" \
  -d '{"visibility": "public"}'

Duplicates · one fingerprint, one anchor, forever

Anchoring a hash that already exists writes nothing - the response names who anchored it first, with their reference and the original commit time. Retries are therefore always safe.

{
  "fabric_id": "b79f815b6cc60b7de54bb750957862808013b0719844b48269c0bccad53c4b1d",
  "status": "ALREADY_ANCHORED",
  "duplicate": true,
  "anchored_by": "GreenCycleLtdMSP",
  "source_tx_id": "GC-2026-0001",
  "payload_type": "recycling.batch.v1",
  "committed_at": "2026-06-12T09:30:54.140Z"
}

Payload types

The sector accepts exactly four payload types - anything else is rejected with 422:

  • esg.report - sustainability / ESG report documents
  • recycling.batch - processed material batches (weights, materials)
  • collection.event - waste collection events from the field
  • certificate - certificates issued by an authority

Do not include a version suffix: it is appended automatically from the API version you call (/v1/anchor stores esg.report.v1). Your organization anchors only the types granted to it at onboarding - your granted scopes are shown in the portal under Developer.

Verifying files & documents

Any file format works - PDF, CSV, XLSX, images - because only raw bytes are hashed. Anchor the final distributed file and treat it as frozen: verification requires byte-for-byte identity. Renaming a file is safe (the name isn't in the bytes); re-saving or re-exporting it is not. To verify, hash the exact file you received:

shasum -a 256 report.pdf        # then POST the hash to /v1/verify

Or drop the file on the portal's Verify page - it is hashed inside your browser and never uploaded.

Verify response · field reference

verifiedtrue if this exact fingerprint is on the ledger
fabric_idthe ledger transaction id assigned at commit
source_tx_idthe anchorer's own reference for the record
payload_typerecord category; version suffix appended automatically
metadataoptional non-sensitive context attached at anchor time
anchored_bythe organization that vouched for this record - the legitimacy signal
on_behalf_ofwho the record belongs to - required at anchoring
committed_atledger timestamp of the anchoring - independent of the anchorer's clock
visibilityprivate (default) or public - public anchors answer on the public verification page
block_numberthe ledger block holding the anchor
block_data_hashthe block's content fingerprint
chain_heightledger height at verification time
verified_atwhen this verification was performed

Statuses & errors

201 COMMITTEDanchored - the fingerprint is now permanent ledger state
201 ALREADY_ANCHOREDfingerprint exists - nothing written; response names the original anchorer and date
401missing or invalid client credentials - check headers, rotate the secret if lost
403authenticated, but your organization lacks that permission or is suspended - contact Senti
422malformed input - the detail message says exactly what
502ledger temporarily unreachable - retry with backoff; anchoring is idempotent so retries are safe