sanctifai-trust-proof-of-human · v2.0.0 · 2026-09-07 · sha256 8e69f3309f5a8707
sanctifai-trust-proof-of-human v2.0.0B
Immutable. This exact content is served forever at /api/v1/blob/8e69f3309f5a8707.
---
name: sanctifai-trust-proof-of-human
description: Integrate SanctifAI Trust Proof-of-Human attestations. Use when an app needs cryptographic proof a human performed a task or human-in-the-loop verification.
homepage: https://trust.sanctifai.com
version: 2.0.0
updated: 2026-09-07
---
# SanctifAI Trust — Proof of Human
SanctifAI Trust turns a unit of human work into a verifiable **Proof of Human**:
the person confirms presence with WebAuthn (Touch ID / Windows Hello / passkey),
and you get a privacy-preserving participation plus a public certificate URL
(and optional on-chain seal). Raw task data never leaves the client — only
`0x`+SHA-256 commitments are sent.
Use this when you need to **prove a human did something** (approved a wire,
reviewed content, signed off a release, completed a gig) and receive
`participation_id` + `certificate_url`.
**Base API:** `https://trust.sanctifai.com`. Three equal surfaces, same outcome:
1. **Embedded** — Proof of Human inside the customer's own app (`/api/presence/*`, API key on their backend). No package install.
2. **Chrome extension** — SanctifAI Chrome extension + hosted `sanctifai-presence.js` (worker-bound identity). No package install.
3. **Chat bridge** — attestations minted for an AI agent via the hosted bridge at `https://bridge.trust.sanctifai.com`.
Local Trust on `localhost` is the docs site, not the production API.
## Choose a surface
Pick the surface that matches where the human actually works. All three mint the
same participation + `certificate_url`.
```
Where does the human work?
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
You control the External / BYO AI agent / chat
app (employees, workforce, or an with no WebAuthn
your product UI) app you don't control browser context
│ │ │
▼ ▼ ▼
EMBEDDED EXTENSION CHAT BRIDGE
REST /api/presence/* Chrome extension + Hosted mint →
API key on YOUR backend sanctifai-presence.js approve_url in Chrome
You supply user_id Worker supplies identity → poll → certificate_url
```
- **Embedded** — default for apps you control. WebAuthn runs in your page.
- **Extension** — the worker installs the SanctifAI extension and attests from pages you don't own.
- **Chat bridge** — the agent has no `navigator.credentials`; a human opens the approval link in Chrome.
Don't pick Extension merely because credentials are missing — get Embedded
credentials instead.
## Credentials
**Embedded** needs two values on the **backend only**:
```bash
TRUST_TENANT_ID=your_tenant_id # ≤ 12 chars
TRUST_API_KEY=sk_live_... # never in the browser, never in agent chat
```
Sign up at **https://trust.sanctifai.com**. The **Developer** plan is free
forever (no trial, no expiry): **100 attestations/month** (calendar month, UTC),
**1 tenant**, **unlimited reviewers**, 30-day audit history. Onboarding
provisions the tenant and a default API key (`sk_live_…`, shown once). Paid
plans raise limits — see [pricing](https://sanctifai.com/trust/pricing).
**Extension:** the worker configures tenant / user / key / RP ID in the
extension. Your page never holds those secrets.
**Chat bridge:** the agent never holds the API key. The hosted bridge does.
Register the Embedded origin + RP ID (`hostname` only, e.g. `app.example.com`)
on the tenant allowlist, or presence calls are rejected. Console → Tenants →
Manage for Tenant ID, keys, and origins. Don't guess or fabricate IDs.
## Shared rules
These apply on every surface. Don't restate them per flow.
### Taxonomy
`task_type` and `domain` are **fixed 3-letter codes** (enums). Descriptive
strings are rejected. Put the human-readable title in `task_subtype` (≤ 200
chars) and the detail inside `taskData` / `resultData` **before** hashing.
- **`task_type`** (required): `ENT ANN COL RND EVA RPA ORC GEN MOD STT TRA DSN DEV CXO SLS CMP ANA PMT CUR`. Default `GEN`.
- **`domain`** (optional): `GEN AUT DFS EDU ENE FIN INS HRM HOS LOG TRN LEG MED MDA RTL ROB SPT TEC GOV AGR REA TEL ESG`. Default `GEN`.
```txt
# WRONG
task_type: "PHARMABOT_TREATMENT_REVIEW"
# RIGHT
task_type: "EVA"
domain: "MED"
task_subtype: "Treatment plan review"
```
Full labels: [reference.md](reference.md).
### Privacy
The **certificate URL is public**. No PII in `user_id`, `task_subtype`,
`task_id`, `taskData`, or `resultData`. Use opaque IDs (`emp_a8f3c2`, not
`jane.doe@company.com`). Keep human-readable detail in your own system.
### After-the-fact proof
Trust proves a human participated and binds SHA-256 commitments. It does **not**
store raw `taskData` / `resultData`. To prove later *what* was attested:
1. Retain the exact payload that was hashed.
2. Re-hash it the same way: key-sorted JSON, then SHA-256, then `0x` + hex.
3. Match `task_commitment` / `result_commitment` on the certificate.
```js
JSON.stringify(payload, Object.keys(payload).sort()) // then SHA-256 → 0x+hex
```
A different key order or encoding will not match. Discard the payload and you
still have proof a human showed up — not proof of contents. `task_subtype` is
only the public headline.
### Fields at a glance
You send opaque IDs, taxonomy codes, a short `task_subtype`, and commitments.
Success returns `participation_id`, `certificate_url`, and usually `qr_url`.
Embedded also requires `idempotency_key` (UUID, generated on the backend).
Full request/response and EAS V3 matrices: [reference.md](reference.md).
Never ship a Trust API key to client JavaScript. Embedded mints the session on
your backend; Extension keeps the key in the worker's extension; Chat bridge
holds it on the hosted service.
## Surface A — Embedded
WebAuthn runs in **your** page. Mint the presence session on **your** backend
(it holds the key) and pass only `session_id` to the browser.
**Minimum flow:** enroll the reviewer's passkey if they don't have one on this
device (`POST /api/webauthn/registration/options` + `/verify` — see
[reference.md](reference.md)), then start → options → `navigator.credentials.get()`
→ verify. If `presence/options` returns **404 No WebAuthn credentials found**,
enroll once and retry.
`registration/verify` needs `tenant_id`, `user_id`, `rp_id`, `challenge_id`,
and `credential` including `clientExtensionResults`.
**Backend** (holds `TRUST_API_KEY`):
```js
// POST /api/trust/presence/start — your server
export async function POST(req) {
const body = await req.json();
const userId = await getEmployeeIdFromSession(req); // from YOUR session, not the body
const r = await fetch('https://trust.sanctifai.com/api/presence/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.TRUST_API_KEY}` },
body: JSON.stringify({
...body,
tenant_id: process.env.TRUST_TENANT_ID,
user_id: userId,
idempotency_key: crypto.randomUUID(),
}),
});
const { session_id } = await r.json();
return Response.json({ session_id });
}
```
**Browser** (raw task data stays local):
```js
const b64uToBuf = (s) => { const p = s.replace(/-/g,'+').replace(/_/g,'/').padEnd(s.length+(4-s.length%4)%4,'='); const b = atob(p); const u = new Uint8Array(b.length); for (let i=0;i<b.length;i++) u[i]=b.charCodeAt(i); return u.buffer; };
const bufToB64u = (buf) => { const u = new Uint8Array(buf); let s=''; for (let i=0;i<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); };
const sha256Hex = async (d) => { const j = typeof d==='string'?d:JSON.stringify(d,Object.keys(d).sort()); const h = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(j)); return '0x'+Array.from(new Uint8Array(h)).map(b=>b.toString(16).padStart(2,'0')).join(''); };
const API = 'https://trust.sanctifai.com';
async function createAttestation(taskData, resultData, { taskId, taskType='GEN', domain='GEN', taskSubtype } = {}) {
const task_commitment = await sha256Hex(taskData);
const result_commitment = await sha256Hex(resultData);
const { session_id } = await fetch('/api/trust/presence/start', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
task_id: taskId ?? `task-${Date.now()}`,
task_type: taskType, domain, task_subtype: taskSubtype,
task_commitment, result_commitment, bond_eligible: true,
rp_id: location.hostname, origin: location.origin,
}),
}).then(r => r.json());
const { options } = await fetch(`${API}/api/presence/options`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id }),
}).then(r => r.json());
const cred = await navigator.credentials.get({ publicKey: {
...options,
challenge: b64uToBuf(options.challenge),
allowCredentials: (options.allowCredentials || []).map(c => ({ ...c, id: b64uToBuf(c.id) })),
}});
return fetch(`${API}/api/presence/verify`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id, expedite: true, credential: {
id: cred.id, rawId: bufToB64u(cred.rawId), type: cred.type,
clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
response: {
clientDataJSON: bufToB64u(cred.response.clientDataJSON),
authenticatorData: bufToB64u(cred.response.authenticatorData),
signature: bufToB64u(cred.response.signature),
userHandle: cred.response.userHandle ? bufToB64u(cred.response.userHandle) : null,
},
}}),
}).then(r => r.json()); // -> { participation_id, certificate_url, qr_url, ... }
}
```
Enrollment, Origin-forwarding for proxied enroll, and CORS: [reference.md](reference.md).
## Surface B — Extension
The worker installs the SanctifAI Chrome extension and configures **their**
tenant id, user id, API key, and RP id. Your page includes only the hosted
script — no secrets, **no** `user_id` from the page. Identity is worker-bound
and portable across the customers they work for.
```html
<script src="https://trust.sanctifai.com/sanctifai-presence.js"></script>
<script>
await SanctifAIPresence.waitForReady(5000);
const result = await SanctifAIPresence.createAttestation({
taskData: { item: 'POST-913', content: '…' },
resultData: { decision: 'approved' },
taskType: 'GEN',
});
// result.participation_id, result.certificate_url, result.qr_url
</script>
```
`window.SanctifAIPresence` exposes `createAttestation`, `detectExtension`,
`isReady`, `waitForReady`, and `sha256Hex`.
## Surface C — Chat bridge
For agents with no WebAuthn context. Default
`APP_BASE_URL=https://bridge.trust.sanctifai.com`. Mint a request, the human
opens `approve_url` in Chrome, you poll until `certificate_url`. Do not invent
localhost approve links. Never request or print `TRUST_API_KEY`.
Self-host the plugin only if you need a custom allowlisted origin.
```
1. POST {APP_BASE_URL}/api/v1/attestations
2. Human opens approve_url in Chrome (passkey)
3. Poll GET {APP_BASE_URL}/api/v1/attestations/{id}
or GET {APP_BASE_URL}/api/v1/attestations/{id}/wait
4. status=completed → certificate_url (hosted on trust.sanctifai.com)
```
**`POST {APP_BASE_URL}/api/v1/attestations`**
```json
{
"task_type": "GEN",
"domain": "GEN",
"task_subtype": "Chat approval",
"requesting_agent": "Support bot",
"taskData": { "summary": "opaque task payload" },
"resultData": { "decision": "approved" }
}
```
Optional `requesting_agent` (≤ 120 chars) is shown on the approve page.
Empty/whitespace values are omitted.
**Response:** `{ attestation_id, approve_url, status: "pending" }`
Tell the human: *Open this HTTPS link in Chrome and confirm with your device
passkey: `{approve_url}`*. If they have no passkey on this origin, the page
enrolls and retries.
**`GET …/wait`** blocks up to ~25s for a terminal status; if still `pending`,
call it again.
**Done when:** `{ status: "completed", certificate_url: "https://trust.sanctifai.com/certificate/..." }`
Retain the exact `taskData` / `resultData` you posted (see Shared rules). The
bridge approve client uses the same `sha256Hex` helper as Embedded.
Shorter chat-only skill: https://bridge.trust.sanctifai.com/skill.md
## Verify
Success is a non-empty `participation_id` + `certificate_url` (Chat bridge:
`status=completed` + `certificate_url`). Open the cert. Show `qr_url` if you
have a UI.
## Deeper material
- [reference.md](reference.md) — REST endpoints, taxonomy labels, enrollment, CORS, errors
- Product: https://trust.sanctifai.com
- Chat bridge skill: https://bridge.trust.sanctifai.com/skill.md