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
| verified | true if this exact fingerprint is on the ledger |
| fabric_id | the ledger transaction id assigned at commit |
| source_tx_id | the anchorer's own reference for the record |
| payload_type | record category; version suffix appended automatically |
| metadata | optional non-sensitive context attached at anchor time |
| anchored_by | the organization that vouched for this record - the legitimacy signal |
| on_behalf_of | who the record belongs to - required at anchoring |
| committed_at | ledger timestamp of the anchoring - independent of the anchorer's clock |
| visibility | private (default) or public - public anchors answer on the public verification page |
| block_number | the ledger block holding the anchor |
| block_data_hash | the block's content fingerprint |
| chain_height | ledger height at verification time |
| verified_at | when this verification was performed |
Statuses & errors
| 201 COMMITTED | anchored - the fingerprint is now permanent ledger state |
| 201 ALREADY_ANCHORED | fingerprint exists - nothing written; response names the original anchorer and date |
| 401 | missing or invalid client credentials - check headers, rotate the secret if lost |
| 403 | authenticated, but your organization lacks that permission or is suspended - contact Senti |
| 422 | malformed input - the detail message says exactly what |
| 502 | ledger temporarily unreachable - retry with backoff; anchoring is idempotent so retries are safe |