# TrustGraph For coding agents: sign the exact submitted query, before server defaults. Pin issuer JWKS out of band through trusted application configuration; never fetch keys from the answer issuer and treat them as trust. Verify positive path proofs locally with the reviewed SDK verification key. Negative answers are signed reports, not proofs of nonexistence. Develop on staging: https://trust-staging.rebellion.systems. Production: https://trust.rebellion.systems. Keep environment pins separate. Early access: hello@rebellion.systems (SDK access, app-origin allowlist, staging setup). Public SDK, docs MCP server and skills: coming soon. ## Core TrustGraph --- URL: https://docs.trust.rebellion.systems/api/attestations # Attestations Verify signed reachability answers and pin their trust anchors. **Auth:** No device authorization. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/query/verify HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "attestation": "" } ``` Response shape: ```json role="response" { "verified": "", "scheme": "", "assertion": "", "epochBinding": { "citedGraphEpochRoot": "", "currentGraphEpochRoot": "", "matchesCurrentEpoch": "", "unsignedServerAssertion": true } } ``` Reachability evidence is the `attestation` returned by [the query API](/api/query). Positive v2 answers combine an Ed25519-signed envelope with a zero-knowledge path-existence proof. Verify both against independently pinned signing keys, the expected question and the supported circuit verification key. ## Attestation keys `GET /v1/attestation-keys` returns the current public Ed25519 key as JWKS. No authorization is required. **Never** Do not turn a key fetched from the answer's issuer into your trust anchor during verification. Distribute trusted JWKS pins through application configuration and use the SDK's pinned circuit verification key. The SDK `/zk` verifier checks the envelope signature, pinned circuit key hash and circuit inputs against the signed question and Poseidon epoch root, then verifies the proof. Release its resources with `destroy()` when finished. See [local verification in the quickstart](/quickstart). A negative v2 answer explicitly carries null `zkProof`, `circuitPublicInputs` and `verificationKeyHash`. Applications should present it as a signed report. ## Ask the server to verify `POST /v1/query/verify` accepts `{attestation}` without device authorization. Its HTTP 200 response has: | Field | Meaning | | --- | --- | | `verified` | Whether verification succeeded | | `scheme` | Attestation scheme reported by verification | | `assertion` | Explicit description of what the result establishes and does not establish | | `verdict` | Signed answer projection, present when available | | `reason` | Failure explanation when available | | `epochBinding` | Comparison between the cited and current graph epoch root | `epochBinding` contains `citedGraphEpochRoot`, `currentGraphEpochRoot`, `matchesCurrentEpoch` and `unsignedServerAssertion: true`. **Does not establish** The server verification response is a report by that server. Its epoch comparison is unsigned and is not covered by the submitted attestation. Use local verification for an independent check, and apply your application's freshness policy to the signed evaluation time. ## Deprecated envelope check `POST /v1/proofs/verify` accepts the legacy `dev-reachability-v0` envelope. It reports checksum consistency with `envelopeIntegrityOnly: true`, `deprecated: true` and `valid: false`. It is not a path-proof verifier and must not authorize a trust decision. --- URL: https://docs.trust.rebellion.systems/api/bind # Bind a device Register a device key through an AuthenSee ceremony. **Auth:** Two matching one-time bind proofs. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/graph-keys/bind//finalize HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "initiationProof": "", "completionProof": "" } ``` Response shape: ```json role="response" { "status": "complete", "graphKey": "" } ``` Binding associates a P-256 public key with an AuthenSee-authenticated persona. Generate and retain the private key on the device. A bearer credential cannot replace this ceremony. ## Start `POST /v1/graph-keys/bind/start` | Field | Requirement | | --- | --- | | `publicKeyJwk` | Required public P-256 JWK | | `appOrigin` | Exact allowlisted application origin, including scheme and port | | `nodeKind` | `human` (default) or `agent` | | `label` | Optional device label, at most 200 characters | | `metadata` | Optional JSON object; identity-routing fields are not accepted | | `ttlSeconds` | Optional integer, 300–604800; device lifetime | | `recoverNodeId` | Optional existing 64-character lowercase hex node ID | | `recoverByPersona` | Optional boolean persona recovery selector | | `purpose` | `graph` or `content-signing`; content signing needs a dedicated fresh key and `recoverNodeId` | | `nativeReturn` | Optional boolean; requires an origin with configured native return support | Use the SDK's recovery flow to select `recoverNodeId` or `recoverByPersona`; do not initiate an unrelated first bind to recover an account. Incompatible recovery combinations are rejected. Caller-selected provider identities and the removed `deviceAdd` field are rejected. ```http POST /v1/graph-keys/bind/start Content-Type: application/json { "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "public-x", "y": "public-y" }, "appOrigin": "https://your-app.example", "nodeKind": "human" } ``` The response supplies `bindId`, `graphKeyId`, `graphNodeId`, `hostedUrl`, `expiresAt` and `initiationProof`. Persist the initiation proof, intended public key, exact origins and expiry before opening `hostedUrl`. The start response's node ID is provisional; the completed key supplies the authoritative node. ## Complete After the person authenticates, `GET /v1/authensee/callback` exchanges the provider result and returns the completion proof through the configured relay. The web return path is `/trust-graph/bind/callback`; `bindId` and `completionProof` arrive in its fragment. Remove the fragment promptly and match the saved bind and origins. `POST /v1/graph-keys/bind/:bindId/finalize` accepts: ```json role="request" { "initiationProof": "gbi_one-time-proof-from-start", "completionProof": "gbc_one-time-proof-from-callback" } ``` Successful completion returns `{status: "complete", graphKey}`. Both proofs must match the same unexpired bind. For a first bind with device-chain creation required, also supply `root: {record, signature}`. Use the SDK bind helpers to construct and persist that prospective root and handle supported root corrections. `GET /v1/graph-keys/bind/:bindId` returns `status`, `expiresAt` and, when complete, `graphKey`. Status is `pending`, `exchanged`, `complete`, `expired` or `failed`. Polling does not reveal either proof. Match a recovered completion to the public key and node saved in the pending intent. **Never** Keep both proofs private. A completed bind is not device approval: refresh and verify the device directory before granting graph access. ## Recovery and errors Fresh recovery devices on chained nodes start quarantined. Existing expired graph-purpose keys can use the ceremony to refresh their binding when the authenticated persona matches; revoked keys cannot be revived. Ordinary active-key rebinds return `GRAPH_KEY_REBIND_NOT_DUE`, except where the independent authentication horizon makes reauthentication eligible. | Response | Meaning | | --- | --- | | 403 `GRAPH_RECOVERY_NOT_FOUND` | Recovery found no existing account | | 403 `GRAPH_BIND_SESSION_MISMATCH` / `GRAPH_BIND_ACCOUNT_MISMATCH` | Provider result does not match the pending ceremony | | 403 `GRAPH_BIND_PERSONA_FORBIDDEN` | Authenticated persona does not own the selected node | | 409 `GRAPH_KEY_REVOKED` | Same-key recovery cannot revive this key | | 400 `BIND_ROOT_REQUIRED` | First-bind root is required | | 400 `BIND_ROOT_UNSUPPORTED` | Chain creation is disabled | | 409 `BIND_ROOT_UNEXPECTED` | Ceremony resolved to an existing node | | 409 `BIND_RECEIPT_MISMATCH` | Retry differs from the committed bind receipt | | 503 `BIND_IDENTITY_UNAVAILABLE` | Identity routing is temporarily unavailable | Recovery starts have a separate per-reference budget, with 429 and `retry-after`. Callback errors are HTML with the corresponding HTTP status, rather than the JSON format used by bind API calls. **Does not establish** This ceremony is scoped to its initiating client and application origin. A forwarded hosted URL is not a supported cross-device binding flow. The `human` or `agent` classification does not independently establish humanhood or delegated authority. --- URL: https://docs.trust.rebellion.systems/api/content-certificates # Content certificates Issue device membership evidence and check current lifecycle status. **Auth:** Own-device signed read, operation `issue-device-certificate`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/graph-keys/certificate HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphKeyId": "", "graphNodeId": "", "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "certificate": "" } ``` A membership certificate links a device public key to a stable node through an issuer signature. It can travel with signed content so recipients can verify the content locally and separately check current device status. ## Issue your own certificate `POST /v1/graph-keys/certificate` Send `{graphKeyId, graphNodeId, authorization}`. Sign read operation `issue-device-certificate` over the exact two-field query. The signing device must be the requested key and belong to the requested node. Graph-purpose devices on chained nodes require approval; content-signing keys can request only their own certificate. The response is `{certificate}`: ```typescript export type DeviceMembershipCertificate = { v: 'trust-graph-device-certificate-v1'; issuer: string; certificateId: string; graphKeyId: string; graphNodeId: string; publicKeyJwk: { kty: 'EC'; crv: 'P-256'; x: string; y: string }; issuedAt: string; expiresAt: string; attestationKeyId: string; signature: string; }; ``` The issuer is the exact API origin pinned by your application. The Ed25519 signature covers canonical JSON of the other fields. Certificate lifetime is capped at seven days, the device's expiry and, for content-signing keys, the remaining signing window. Issuance requires positive remaining lifetime; exact expiry returns 403 `GRAPH_KEY_EXPIRED` after proof of possession. ## Check lifecycle status `POST /v1/graph-keys/certificate/status` accepts `{certificate, nonce}`. Use a fresh nonce of 16–256 characters. The complete valid issuer-signed certificate authorizes this check; no graph-device read authorization is needed. The response is `{status}`. The signed status object contains `v: "trust-graph-device-status-v1"`, `certificateDigest`, `nonce`, `evaluatedAt`, `status`, `revokedAt`, `attestationKeyId` and `signature`. The digest covers canonical JSON of the **complete certificate**, including its signature. | Status | Meaning | | --- | --- | | `valid` | Certificate and device pass current lifecycle checks | | `revoked` | Device has been revoked; `revokedAt` supplies its time | | `expired` | Device, certificate or content-signing window has expired | | `unavailable` | Membership cannot be established | Verify both issuer signatures, pinned origin and key, certificate times, certificate digest, nonce and status freshness. Renewal does not extend an existing certificate. Invalid issuer signatures are refused with 401. Both issuance and status use query rate limits. **Establishes** The certificate is issuer-attested device membership. Status binds a current lifecycle report to that certificate and your nonce. No plaintext or content digest is sent to these endpoints. **Does not establish** Membership does not establish humanhood, trust, authorship or when content was signed. Status is lifecycle-based; it is not a fresh device-chain approval assertion. Verify content signatures locally, then query from your own node and verify a positive path locally before claiming a connection. --- URL: https://docs.trust.rebellion.systems/api/devices # Device authority Read and append device approval history. **Auth:** Device-signed read, operation `device-log-read`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/devices/log HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphNodeId": "", "limit": 256, "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "chain": [ "" ], "head": "", "nextAfterSequence": "", "signingKeys": "" } ``` A device chain is a node's signed approval history. Binding proves persona membership; approval determines whether a device may act for a chained node. Existing chains remain enforced even when creation of new chains is disabled. ## Read the directory `POST /v1/devices/log` uses signed read `device-log-read` over `{graphNodeId, afterSequence?, limit?}`. Add `authorization` to send the request. `graphNodeId` is the directory being requested. The requester may read its own directory or a directory connected to its node by an active, unexpired edge in either direction. `afterSequence` is an optional integer from 0 through 4294967295; `limit` is 1–256, default 256. The response excerpt and table list the core authority fields: | Field | Contents | | --- | --- | | `chain` | Signed authority records for this page | | `head` | Current sequence, record hash, epoch, root and approved/revoked state | | `nextAfterSequence` | Pagination cursor | | `signingKeys` | Public signing JWKs and lifecycle/recovery metadata | Fetching or parsing this response does not verify its chain. Use the SDK device verification helpers with persisted trusted pins, and continue pagination until the verified state reaches the advertised head. ## Append an authority record `POST /v1/devices/log/append` takes `{action, signature}`. The `device-log-append` action contains `graphKeyId`, `record`, `recordSignature`, `nonce` and `signedAt`. These records are required for clients to bootstrap a root when the server requires one, or approve a new device on an existing chain. Use `buildDeviceRecord` and the SDK signer’s `signFeedRecord` helper. The helper name and signing domain are compatibility identifiers for this core device-authority operation. The inner record has `kind`, `graphNodeId`, `epoch`, `sequence`, `previousRecordHash`, `issuerGraphKeyId`, `subjectGraphKeyId` and `issuedAt`. Sign canonical JSON with `v: "omnibus-feed-authority-v1"`; then sign the outer action in the normal action domain. All signatures on these routes are canonical base64, low-S P-256 DER. Success returns `{recordHash, head}`. | Kind | Authority and effect | | --- | --- | | `root` | Genesis key opens epoch 1 | | `approve` | Approved usable device admits a sibling | | `revoke` | Approved device revokes a subject; a usable device may revoke itself | | `reset` | Eligible recovered, unapproved key closes the old epoch and opens another | Each sequence is consecutive and links the preceding hash. Reset additionally includes `closesRootRecordHash` and `resetReason`, one of `all-devices-lost`, `user-choice`, or `bootstrap`. Eligibility requires a durable recovery marker for the current root and no chain revocation. A reset revokes displaced devices. ## Limits and errors A node can have at most 64 approved devices. Daily rolling limits are 8 approvals, 16 revocations and 2 resets per node. Responses use `Cache-Control: no-store`. Device-authority codes include `DEVICE_LOG_CONFLICT` (409; refresh the head), `DEVICE_LOG_NOT_APPROVED` (403), `DEVICE_LIMIT` (409) and `DEVICE_CHAINS_DISABLED` (409). Also handle authentication failures, nonce replay, rate limits and unavailable responses by their HTTP status; this is not an exhaustive error-code list. **Does not establish** A quarantined key may read its own directory, device list and handle, renew under lifecycle rules, and revoke itself. It cannot perform ordinary graph operations. Successful AuthenSee recovery alone does not approve it, and a signed directory must still be verified locally. --- URL: https://docs.trust.rebellion.systems/api/edges # Edges Declare, replace, list and revoke directed trust. **Auth:** Source-device signed edge certificate. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/edges/request HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "signerGraphKeyId": "", "subjectGraphKeyId": "", "signingGraphKeyId": "", "trust": 80, "scope": "identity", "createdAt": "", "nonce": "", "signature": "" } ``` Response shape: ```json role="response" { "edge": "", "graphEpochRoot": "", "poseidonEpochRoot": "", "eventLogRoot": "", "eventLogSize": "" } ``` A declaration is a one-way score from one node to another, active immediately. Scores are integers from 0 to 100. New declarations use `scope: "identity"`; this is a protocol compatibility constant, not a relationship label. ## Declare trust `POST /v1/edges/request` accepts a signed edge certificate: ```json role="request" { "signerGraphKeyId": "source-node-id", "subjectGraphKeyId": "target-node-id", "signingGraphKeyId": "source-device-id", "trust": 80, "scope": "identity", "createdAt": "2026-11-04T12:00:00.000Z", "nonce": "fresh-random-nonce", "signature": "base64-edge-certificate-signature" } ``` `expiresAt` is optional. All timestamps must parse. Sign the certificate fields using the SDK's edge signing helper; `signature` and `signingGraphKeyId` are not certificate preimage fields. Include `signingGraphKeyId` when the signing device ID differs from the source node ID. Success is 201 with `{edge, graphEpochRoot, poseidonEpochRoot, eventLogRoot, eventLogSize}`. A fresh declaration for the same direction atomically supersedes the previous one. Public edge projections omit the certificate signature and nonce. ## Conditional replacement `POST /v1/edges/replace` accepts the same body but requires the signed nonce: ```text replace-v1.. ``` The random suffix is 8–128 letters, digits or hyphens. The declaration applies only if the named edge is still current. This is the preferred route when a user edits a score they previously read. | Code | Meaning | | --- | --- | | 400 `EDGE_REPLACE_NONCE_REQUIRED` | Replacement nonce missing | | 400 `EDGE_NONCE_INVALID` | Replacement nonce has an invalid shape | | 400 `EDGE_REPLACE_ROUTE_REQUIRED` | Conditional certificate sent to `/request` | | 409 `EDGE_SUPERSEDED` | Another declaration is current; response includes `currentEdgeId` | | 409 `EDGE_NOT_CURRENT` | No declaration currently stands | A refused conditional certificate consumes its nonce; a wrong-route refusal does not. Refresh state and sign anew. If `/replace` returns 404 because it is unsupported, stop the conditional operation. **Never** Do not fall back from `/replace` to `/request`: an unconditional declaration does not preserve the user's condition. ## List incident edges `POST /v1/edges/list` takes `{graphKeyId, authorization}`, with read operation `list-edges`. Here the query's `graphKeyId` is the **own node ID**, while `authorization.graphKeyId` is its signing device. The response is `{edges, edgeTags}`. Incoming and outgoing edges are included. Outgoing scores are visible to the owner; incoming projections do not disclose the other source's private score. Private tags appear only on supported current outgoing edges. Optional handle and deactivation fields help display already-visible endpoints. `GET /v1/edges` always returns 401. ## Revoke and legacy confirm `POST /v1/edges/:edgeId/revoke` takes `{action, signature}`. Sign action `revoke-edge` with `graphKeyId`, `edgeId`, `nonce` and `signedAt`. Only a usable device of the source node can revoke its declaration. The response contains `edge` and the epoch commitment fields. Removing a superseded edge can return `EDGE_SUPERSEDED`; refresh before asking the user to remove the current declaration. `POST /v1/edges/:edgeId/confirm` remains for legacy clients. It takes the corresponding signed `confirm-edge` action from the subject node's device. On active trust it is an authenticated no-op; it does not create a reciprocal declaration. Revoked trust cannot be confirmed. **Does not establish** Receiving a vouch does not mean you vouch back. Device expiry and revocation do not erase node connections; edge expiry and source revocation determine whether the declaration remains usable. --- URL: https://docs.trust.rebellion.systems/api/epochs # Epoch commitments Read current graph roots and compare an earlier event-log commitment. **Auth:** No device authorization. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http GET /v1/graph/epoch/current HTTP/1.1 Host: trust-staging.rebellion.systems ``` Response shape: ```json role="response" { "graphEpochRoot": "", "poseidonEpochRoot": "", "eventLogRoot": "", "eventLogSize": "" } ``` ## Current commitment `GET /v1/graph/epoch/current` requires no authorization. It returns: ```typescript export type EpochCommitment = { graphEpochRoot: string; poseidonEpochRoot: string; eventLogRoot: string; eventLogSize: number; }; ``` `graphEpochRoot` commits to current active edge state. `poseidonEpochRoot` is the circuit-compatible graph commitment. `eventLogRoot` and `eventLogSize` identify the append-only history commitment. Keep a previously trusted root-and-size pair if you want to compare history later. `eventLogSize` is a non-negative safe integer. `poseidonEpochRoot` is a canonical BN254 field element encoded as `0x` followed by 64 lowercase hexadecimal characters. The current server always returns both fields. The SDK makes both fields optional for legacy responses and saved snapshots. Missing fields remain absent; they do not become zero or null. Explicit null, malformed values, unsafe or negative sizes, and non-canonical field elements are rejected. ## Append-only report `POST /v1/graph/epoch/append-only` takes `{eventLogRoot, eventLogSize}`. The size must be a nonnegative integer; malformed request shapes return 400. No device signature is required. The response contains `appendOnly`, `unsignedServerAssertion: true`, `prior`, `current`, `assertion` and an optional `reason`. The server compares the supplied prior pair with the prefix of its current log at that size. **Establishes** A true report says the server's current log is at least as long as the supplied prior log, with the same prefix root. The comparison concerns whether entries in that prefix changed, moved or disappeared. **Does not establish** This endpoint returns an unsigned server assertion, not a signed attestation or an independently verified consistency proof. It does not establish the authenticity of the prior pair, completeness of what was logged, or that the reported state is the latest. The current-epoch GET response is also unsigned. Use the commitment fields inside a verified reachability attestation when you need signed evidence tied to a query. A graph state root may change while earlier event history remains a valid prefix. --- URL: https://docs.trust.rebellion.systems/api/external-keys # External signing keys Attach and resolve an existing OpenPGP signing key. **Auth:** Own-device signed read, operation `list-external-keys`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/external-keys/list HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphNodeId": "", "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "externalKeys": [ { "externalKeyId": "<40-hex-fingerprint>", "kind": "openpgp", "graphNodeId": "", "primaryFingerprint": "<40-hex-primary-fingerprint>", "attachedAt": "", "revokedAt": "" } ] } ``` An ordinary graph device can attach one selected OpenPGP primary or signing subkey to its node using both a device signature and OpenPGP proof of possession. All routes require a usable, approved graph-purpose device. Bearer credentials and content-signing devices cannot authorize these requests. ## Attach `POST /v1/external-keys/attach` takes: ```json role="request" { "action": { "action": "attach-external-key", "graphKeyId": "device-id", "externalKeyId": "40-character-lowercase-fingerprint", "materialDigest": "64-character-lowercase-sha256", "proofDigest": "64-character-lowercase-sha256", "nonce": "fresh-random-base64url-nonce", "signedAt": "2026-11-04T12:00:00.000Z" }, "signature": "base64-device-action-signature", "externalKey": { "kind": "openpgp", "publicKey": "ASCII-armored public key block" }, "proof": "ASCII-armored clearsigned proof" } ``` The proof signs exactly this single ASCII line, without a trailing newline: ```text trustgraph-external-key-v1 node= key= digest= nonce= ``` `materialDigest` is SHA-256 of sanitized protocol-framed public key material, not the raw armor. `proofDigest` is SHA-256 of the sanitized canonical framed proof signature packet. Use the integration's reviewed OpenPGP preparation implementation; do not substitute a hash of the submitted strings. Freeze material and proof before signing the device action. The clearsigned proof contains exactly one type 0x01 signature from the selected eligible key. Its hashed creation time must be within one hour of `signedAt`. The action itself follows the normal five-minute freshness window and durable nonce rules. Success is 201 `{externalKey}`. Attach, revoke and list use this public record: ```typescript export type ExternalKeyRecord = { externalKeyId: string; kind: 'openpgp'; graphNodeId: string; primaryFingerprint: string; attachedAt: string; revokedAt: string | null; }; ``` ## Revoke or list `POST /v1/external-keys/:externalKeyId/revoke` accepts `{action, signature}`. Sign `revoke-external-key` with `graphKeyId`, `externalKeyId`, `nonce` and `signedAt`; omit the attach digests. The signed fingerprint must exactly match the URL or the request returns 403. Success is 200 `{externalKey}`. `POST /v1/external-keys/list` takes `{graphNodeId, authorization}`, read operation `list-external-keys`. It is own-node only and returns `{externalKeys}`, including revoked records. The per-node limit is 32 total records, counting revoked keys. **Never** Fingerprint revocation is terminal. A reserved fingerprint cannot be attached again to the same or another persona. Attach only the exact signing key the user intends to reserve. ## Resolve a signer `POST /v1/external-keys/resolve-signer` takes either `{graphNodeId, signingFingerprint, authorization}` or `{graphNodeId, signingKeyId, authorization}`. Sign operation `resolve-external-signer` over the exact query. The node ID is your own; supply exactly one locator, never null or both. Fingerprints are 40 lowercase hex characters; key IDs are their last 16. Key-ID resolution requires a unique reserved fingerprint before lifecycle filtering. Missing, ambiguous, revoked or inactive membership returns the same 404 `EXTERNAL_KEY_NOT_FOUND`. Success returns `{certificate, status}`. The certificate includes `v: "trust-graph-external-key-certificate-v1"`, issuer, certificate ID, attestation key ID, proven fingerprint, `kind`, parent fingerprint, base64 `signingKeyPacket`, node ID, attachment time, issuance, expiry and signature. The packet is the public-key packet body, not an armored block. Lifetime is 24 hours. Status uses `v: "trust-graph-external-key-status-v1"` and includes the complete certificate digest, authorization nonce, evaluation time, `valid` / `revoked` / `unavailable`, nullable revocation time and issuer signature. There is no separate external-key status endpoint. Verify both issuer signatures and pins; recompute the v4 fingerprint from the packet and match the requested locator. Require attachment no later than issuance, issuance no more than 60 seconds ahead, a positive certificate lifetime at most 24 hours, unexpired evidence, and status evaluation within 60 seconds of your clock. Verify the document signature locally. If a path check takes more than 30 seconds after status evaluation, resolve again and require unchanged node and fingerprint before presenting the result. Before displaying a successful result, recheck expiry. Bound its lifetime by the earliest of certificate expiry, document-signature expiry (when present), status evaluation + 60 seconds, path evaluation + 60 seconds (when used), and check time + 60 seconds. A revoked status must carry a revocation time no later than evaluation; other statuses use null. ## Supported material and errors Supported v4 keys are RSA 2048–8192, ECDSA P-256/P-384 and legacy-OID Ed25519, with SHA-256/384/512 signatures. Public-key armor is capped at 64 KiB; proof armor at 8 KiB. Signing subkeys require verified binding and back-signature. Other key formats can be refused rather than imported partially. | Code | HTTP | | --- | --- | | `EXTERNAL_KEY_REJECTED`, `EXTERNAL_KEY_UNSUPPORTED`, `EXTERNAL_KEY_MALFORMED` | 400 | | `EXTERNAL_KEY_PROOF_INVALID`, `EXTERNAL_KEY_ACTION_UNAUTHORIZED` | 401 | | `EXTERNAL_KEY_UNAVAILABLE` | 409 | | `EXTERNAL_KEY_NOT_FOUND` | 404 | | `EXTERNAL_KEYS_SERVER_UNAVAILABLE`, `EXTERNAL_KEY_PERSISTENCE_FAILED` | 503 | The existing schema, freshness, lifecycle and rate-limit errors also apply. Capability availability depends on the server deployment; unavailable storage returns 503. **Does not establish** Attachment establishes current control of one selected key, not control of its siblings or its parent. It makes no claim about historical signing time. The full submitted key block reaches the service for processing; public records omit key material and proof text. Membership alone does not establish a trusted path, human authorship or document safety. --- URL: https://docs.trust.rebellion.systems/api/git-signers # Git signer membership Resolve a known device signing key to current persona membership. **Auth:** Own-device signed read, operation `resolve-git-signer`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/graph-keys/resolve-signer HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphNodeId": "", "targetGraphKeyId": "", "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "certificate": "", "status": "" } ``` Status: **Ships**. `POST /v1/graph-keys/resolve-signer` resolves an exact known device key after you have verified a commit signature locally. | Field | Meaning | | --- | --- | | `graphNodeId` | Requesting device's own node ID, 64 lowercase hex characters | | `targetGraphKeyId` | Exact locally verified signing key ID, 64 lowercase hex characters | | `authorization` | Read operation `resolve-git-signer`, signed over the two fields above | The requester must be a usable approved ordinary graph device; content-only keys cannot query. Use a fresh authorization nonce of 16–256 characters. Read freshness and query rate limits apply. Success returns `{certificate, status}` in the [device certificate formats](/api/content-certificates). The status binds the complete certificate digest and the authorization nonce. Verify the certificate's public JWK and key ID against the locally verified commit signer, both issuer signatures, the pinned issuer, times and nonce. Then query reachability from your own node to the certificate's `graphNodeId`. Locally verify a positive path with the expected question and pinned keys before displaying connected membership. Unknown, inactive or unapproved graph-purpose targets return the same 404. Treat that as unavailable evidence. The endpoint accepts no commit bytes, digest or repository name; it exposes no prefix search or bulk directory. **Does not establish** This resolves current operator-attested membership. It does not prove commit-time authorization, human authorship, review or code safety. Commit timestamps are signer-controlled. A 404 is not signed proof of absence or historical revocation, and a negative reachability answer is only a signed report. For OpenPGP signers, use [external signing keys](/api/external-keys). For an end-to-end workflow, see [the Git recipe](/build/git-signers). --- URL: https://docs.trust.rebellion.systems/api/graph-keys # Graph keys List, renew and revoke device signing keys. **Auth:** Own-device signed read, operation `list-devices`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/graph-keys/list HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphNodeId": "", "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "graphKeys": [ "" ] } ``` A graph key is one device's P-256 key. A stable node can have several independent devices; connections belong to the node. Register keys through [binding](/api/bind). The legacy `POST /v1/graph-keys` registration endpoint has been removed. ## List keys | Route | Authorization | Response | | --- | --- | --- | | `POST /v1/graph-keys/list` | Signed read `list-devices` over `{graphNodeId}` | `{graphKeys}` | | `GET /v1/graph-keys/me` | Verified AuthenSee persona credential | `{graphKeys, nodeHandles?}` | For the POST, send `{graphNodeId, authorization}`. The signing device must belong to the requested node. Usable quarantined devices may use their own device listing. Key records include `graphKeyId`, `graphNodeId`, `publicKeyJwk`, `nodeKind`, `createdAt`, `expiresAt` and `authBoundAt`; optional fields include `label`, `metadata`, `purpose`, `revokedAt` and recovery metadata where supplied by the own-key surface. Omitted metadata is absent, not a JSON null. Keep these owner responses private. ## Renew a key `POST /v1/graph-keys/:graphKeyId/renew` ```json role="request" { "action": { "action": "renew-graph-key", "graphKeyId": "own-device-id", "nonce": "fresh-random-nonce", "signedAt": "2026-11-04T12:00:00.000Z" }, "signature": "base64-device-signature" } ``` Renewal is self-only and returns `{graphKey}`. The device must still be unexpired and within its independent lifetime. Ordinary graph devices renew their seven-day expiry until the thirty-day AuthenSee authentication horizon; after that, refresh authentication through binding. The route does not accept `requestedExpiresAt`. ## Revoke a key `POST /v1/graph-keys/:graphKeyId/revoke` Send a signed `revoke-graph-key` action with `graphKeyId`, `nonce` and `signedAt`. To revoke a sibling, add `targetGraphKeyId` and put that target in the URL. An approved usable device of the same node must sign a sibling revocation; a usable quarantined device may revoke itself. Alternatively, send an empty JSON object with a verified persona credential belonging to the target key's AuthenSee subject. This allows recovery when that device key is unavailable. The response is `{graphKey}`. **Never** Revocation is terminal. A revoked key cannot renew or rebind. Generate a fresh device key when recovering. ## Refusals A signed target that differs from the URL is rejected with 403. Invalid authorization is refused; after proof of possession, lifecycle errors can include `GRAPH_KEY_REVOKED`, `GRAPH_KEY_EXPIRED`, `GRAPH_KEY_PURPOSE_FORBIDDEN` and `GRAPH_KEY_NOT_APPROVED`. Persona credentials do not authorize renewal. **Does not establish** Revoking or expiring a device removes its access. It does not remove the persona's connections. Use an authorized source-device [edge revocation](/api/edges) to stop vouching. --- URL: https://docs.trust.rebellion.systems/api/handles # Public handles Claim, release and resolve an optional node handle. **Auth:** Own-device signed read, operation `read-own-handle`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/handles/me HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "graphNodeId": "", "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "nodeHandle": { "graphNodeId": "", "handle": "", "handleRevision": "", "claimedAt": "", "nextClaimAt": "" }, "staleBefore": "" } ``` A node may hold one optional public handle. Node IDs continue to identify connections and authorize actions. Handles use `lower-ascii-v1`: 3–30 lowercase letters, digits or underscores, beginning with a letter. Normalize before signing; the server never rewrites signed text. ## Read your current revision `POST /v1/handles/me` takes `{graphNodeId, authorization}` using signed read operation `read-own-handle` and exact query `{graphNodeId}`. A usable own device, including a quarantined one, may read it. The response is `{nodeHandle, staleBefore}`. The owner view includes `graphNodeId`, `handle`, `handleRevision`, `claimedAt` and `nextClaimAt`. The handle and dates may be null when not set. Use the current revision for any change. `staleBefore` is the server's cutoff for actions too old to be admitted. ## Claim, rename or release | Route | Signed action fields beyond device ID, nonce and timestamp | | --- | --- | | `POST /v1/handles/claim` | `action: "claim-handle"`, `handle`, `expectedRevision` | | `POST /v1/handles/release` | `action: "release-handle"`, `expectedRevision` | Both accept `{action, signature}` from a usable approved graph-purpose device. `expectedRevision` is an integer from 0 to 2147483645. Success returns `{nodeHandle, graphEpochRoot, poseidonEpochRoot, eventLogRoot, eventLogSize}` and advances the handle revision. Claiming a different name while holding one is an atomic rename. A node may claim or rename once every 30 days. Release takes effect immediately. Released or renamed-away handles remain unavailable to everyone for 30 days. Handle changes advance the event log without changing the graph commitment roots. ## Resolve a handle `POST /v1/handles/lookup` accepts `{graphNodeId, handle, authorization}`. Sign operation `lookup-handle` over `{graphNodeId, handle}`; `graphNodeId` is the requesting device's own node. Success returns `{nodeHandle: {graphNodeId, handle, claimedAt}}`, where the returned node ID is the resolved holder. Unknown, released and cooling-off names return the same 404 `HANDLE_NOT_FOUND`. ## Errors and uncertain outcomes | Code | HTTP | | --- | --- | | `HANDLE_INVALID`, `HANDLE_RESERVED` | 400 | | `HANDLE_UNAVAILABLE`, `HANDLE_UNCHANGED`, `HANDLE_NOT_CLAIMED`, `HANDLE_REVISION_CONFLICT` | 409 | | `HANDLE_CHANGE_LIMIT` | 429 | | `HANDLES_DISABLED`, `HANDLE_PERSISTENCE_UNAVAILABLE` | 503 | After a lost response, use `/handles/me`. The intended name at revision + 1 establishes success; any different revision means the old action can no longer apply. An unchanged revision is conclusive only after the action can no longer be admitted: its `signedAt` must be earlier than a subsequent `staleBefore`, with a conservative time margin. Then sign a new retry. Do not replay the old action. **Does not establish** A handle is a public discovery hint. It grants no authority, proves no real-world identity and can change owners after its cooling-off period. Retain the verified node ID when establishing a connection. --- URL: https://docs.trust.rebellion.systems/api/health # Health and readiness Distinguish process liveness from service readiness. **Auth:** No device authorization. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http GET /healthz HTTP/1.1 Host: trust-staging.rebellion.systems ``` Response shape: ```json role="response" { "ok": true } ``` These probes require no device authorization. | Route | Healthy response | Meaning | | --- | --- | --- | | `GET /healthz` | 200 `{ok: true}` | Process is running | | `GET /readyz` | 200 with `ready: true` | Instance can serve under its configured checks | Liveness does not query the database or migration state. A database outage does not by itself make `/healthz` fail. Readiness returns `{ready, checks}` with named checks for `database`, `migrations` and `attestation`, plus capability information where supplied. Each check has `name`, `status` and an optional fixed `reason`. It returns 503 when the database is unavailable, migrations do not match the required state, or the attestation backend has no verification key. The probe reads state without migrating or writing. Readiness may expose `edgeTags` and `nodeHandles` capability descriptions. Read capabilities before offering optional operations, and still handle a refusal when submitting a request. **Does not establish** A successful liveness probe does not establish readiness, a working bind provider or successful proof generation. Readiness reports this instance's configured checks at the time of the request; it is not a signed availability guarantee. --- URL: https://docs.trust.rebellion.systems/api/nodes # Nodes and account deletion Use stable node identity and permanently deactivate an account. **Auth:** Approved own-device signed action `delete-node`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/nodes//delete HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "action": { "action": "delete-node", "graphKeyId": "", "graphNodeId": "", "nonce": "", "signedAt": "" }, "signature": "" } ``` Response shape: ```json role="response" { "deletion": { "graphNodeId": "", "status": "deleted", "deletedAt": "" }, "graphEpochRoot": "", "poseidonEpochRoot": "", "eventLogRoot": "", "eventLogSize": "" } ``` A node is the stable endpoint for a persona's connections. Binding returns its `graphNodeId` alongside the device's `graphKeyId`. Devices may change without moving the node's edges. Node kinds are `human` and `agent`; the kind is metadata, not independent evidence of humanhood or delegated authority. ## Delete your node `POST /v1/nodes/:graphNodeId/delete` accepts a signed action: ```json role="request" { "action": { "action": "delete-node", "graphKeyId": "own-device-id", "graphNodeId": "own-node-id", "nonce": "fresh-random-nonce", "signedAt": "2026-11-04T12:00:00.000Z" }, "signature": "base64-action-signature" } ``` The node ID must match both the URL and the signing device's own node. The device must be usable, graph-purpose and approved on a chained node. A quarantined key returns 403 `GRAPH_KEY_NOT_APPROVED`. Persona credentials and content-signing keys cannot authorize deletion. Success is 200 with `deletion: {graphNodeId, status, deletedAt}` and the four epoch commitment fields. `status` is `deleted` or `already-deleted`. ## Effects The node becomes deactivated and cannot participate in reachability. Its device keys are revoked; its own vouches and tags are removed from active use; its handle is released into the normal cooling-off period; external fingerprints are revoked and remain reserved. Recovery cannot revive the node. A later sign-up creates a fresh empty node. Incoming declarations belong to their sources and remain for those sources to manage. They no longer contribute to paths through the deactivated node. An owner's incident edge listing can show `subjectDeactivated: true`, allowing that owner to revoke its own declaration. The deletion appends to history. Existing event-log entries and previously signed attestations remain, and device public keys remain sufficient for certificate status to report revocation. ## Retry after a lost response Sign a **new**, fresh `delete-node` action using a key of the deactivated node. A valid repeat returns `already-deleted` with the original deletion time. A stale request, bad signature or wrong-node target is refused. Do not infer deletion merely from an unrelated request failing. **Never** Deletion is irreversible. Confirm the chosen node with the user before signing the action. There is no separate AuthenSee confirmation step on this endpoint. **Does not establish** Deletion deactivates an account; it is not removal of all historical public evidence. Other people's signed incoming declarations, event commitments, issued attestations and already downloaded content are not recalled. --- URL: https://docs.trust.rebellion.systems/api/query # Reachability Ask whether directed trust reaches a target under your policy. **Auth:** Own-device signed read, operation `query-reachable`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/query/reachable HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "sourceGraphKeyId": "", "targetGraphKeyId": "", "policy": { "maxHops": 3, "minTrust": 50, "scope": "identity" }, "authorization": { "graphKeyId": "", "nonce": "", "signedAt": "", "signature": "" } } ``` Response shape: ```json role="response" { "reachable": "", "pathLength": "", "effectivePolicy": "", "attestation": "", "proof": "", "integrity": { "authoritative": "attestation", "unsignedProjections": [ "reachable", "pathLength", "effectivePolicy", "proof" ] } } ``` `POST /v1/query/reachable` asks from the authenticated device's own node. Sign read operation `query-reachable` over the exact query body before adding `authorization`. | Field | Meaning | | --- | --- | | `sourceGraphKeyId` | Your stable node ID | | `targetGraphKeyId` | Target stable node ID | | `policy.maxHops` | Integer 1–3, default 3 | | `policy.minTrust` | Integer 0–100, default 50 | | `policy.scope` | `identity`, the default and supported new-query scope | | `policy.currentTime` | Optional future horizon, parseable timestamp | | `authorization` | Fresh own-device signed read | Include all intended policy values explicitly. Out-of-range values return 400 rather than being clamped. A horizon can only narrow the answer: edges must qualify now and at that future time. A past horizon is rejected. The server chooses evaluation time and the current graph commitment. ```typescript import { createTrustGraphSdk, signBrowserGraphRead, } from '@rebellion-systems/trust-graph-sdk/browser'; export async function requestReachability( privateKey: CryptoKey, deviceId: string, ownNodeId: string, targetNodeId: string, ) { const graph = createTrustGraphSdk({ serverUrl: 'https://trust-staging.rebellion.systems', }); const query = { sourceGraphKeyId: ownNodeId, targetGraphKeyId: targetNodeId, policy: { maxHops: 3, minTrust: 50, scope: 'identity' as const }, }; const authorization = await signBrowserGraphRead(privateKey, { operation: 'query-reachable', graphKeyId: deviceId, graphNodeId: ownNodeId, query, }); return graph.queryReachability({ ...query, authorization }); } ``` This function requests evidence; it does not verify that evidence. Verify `response.attestation` locally with the SDK verifier and your pinned keys before acting on it. ## Response The response includes `reachable`, `pathLength`, `effectivePolicy`, the legacy `proof`, the signed `attestation`, and `integrity`. Optional `sourceHandle` and `targetHandle` are display metadata. `integrity.authoritative` is `attestation`. Its `publicInputs` carry the signed answer, endpoints, policy, evaluation time, horizon and commitment fields. Top-level convenience fields are unsigned projections. **Establishes** A locally verified positive v2 attestation establishes a qualifying non-empty directed path in the committed graph. The path can have at most three hops. Intermediate nodes and edges are not disclosed by the proof. **Does not establish** A negative answer is a signed server report, not a zero-knowledge proof of absence. The service sees the graph and query. A valid proof does not establish that the published graph is complete or the latest state. A path returning to its starting node can satisfy a query from a node to itself. ## Errors Malformed policy returns 400. An invalid or missing read signature returns 401; a device from another source node is refused. Lifecycle and approval failures use the common [authorization errors](/api). Unknown or deactivated endpoints are unavailable. Proof-generation failure returns 503 instead of a signature-only positive. See [attestations](/api/attestations) for local verification and the server verification endpoint. --- URL: https://docs.trust.rebellion.systems/api/tags # Relationship tags Replace private labels on an outgoing connection. **Auth:** Source-device signed action `set-edge-tags`. ## Request and response Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending. ```http POST /v1/edges//tags HTTP/1.1 Host: trust-staging.rebellion.systems Content-Type: application/json { "action": { "action": "set-edge-tags", "graphKeyId": "", "edgeId": "", "tagsFormat": "plain-v1", "tags": [ "family" ], "expectedRevision": 0, "nonce": "", "signedAt": "" }, "signature": "" } ``` Response shape: ```json role="response" { "edge": "", "graphEpochRoot": "", "poseidonEpochRoot": "", "eventLogRoot": "", "eventLogSize": "" } ``` `POST /v1/edges/:edgeId/tags` replaces the complete label set for a current active outgoing edge. Authenticate with a usable graph-purpose device of the source node. ```json role="request" { "action": { "action": "set-edge-tags", "graphKeyId": "source-device-id", "edgeId": "current-edge-id", "tagsFormat": "plain-v1", "tags": ["close friends", "family"], "expectedRevision": 0, "nonce": "fresh-random-nonce", "signedAt": "2026-11-04T12:00:00.000Z" }, "signature": "base64-action-signature" } ``` Read [the own edge list](/api/edges) first. Its `edgeTags` capability contains `enabled`, `formats`, `maxTags`, `maxCodePoints` and `keyRule`. Supported outgoing metadata contains `tagsFormat: "plain-v1"`, `tags` and `tagsRevision`. Missing metadata means unavailable, not an empty list. | Parameter | Rule | | --- | --- | | `tagsFormat` | `plain-v1` | | `tags` | At most 16 labels, each 1–64 Unicode code points | | Text | NFC and canonical whitespace; matching uses `lower-v1` | | Duplicates | Duplicate matching keys are rejected | | `expectedRevision` | Current revision, integer 0–2147483645 | Normalize on explicit Add or Save before signing, using the SDK tag helpers. The server does not rewrite signed text. `tags: []` explicitly clears the set. Success returns `{edge, graphEpochRoot, poseidonEpochRoot, eventLogRoot, eventLogSize}`. Tag edits advance the event log, not the graph commitment roots. Tags follow the directed node pair through rescoring, device changes and expiry. Expired edges are read-only. Explicit edge removal clears tags; recreating trust starts empty. ## Conflicts and retries A stale revision returns `EDGE_TAG_REVISION_CONFLICT`. Other documented codes include `EDGE_SUPERSEDED`, `EDGE_REVOKED`, `EDGE_EXPIRED`, `EDGE_TAG_FORMAT_UNSUPPORTED`, `EDGE_TAGS_DISABLED` and `EDGE_TAG_INVALID`. If the response is lost, re-list. Exactly one revision advance with the intended set means success. An unchanged revision permits a newly signed retry; another advance is a conflict. Do not automatically resend the old signed mutation. **Does not establish** Tags are hidden from other graph users, including the recipient, but the service can read them. They are not end-to-end encrypted or part of a zero-knowledge certificate, and they grant no resource access by themselves. There is no server-side tag search. --- URL: https://docs.trust.rebellion.systems/api # API reference Authentication, request signing, errors and resource endpoints. Use `https://trust-staging.rebellion.systems` for development through [early access](/early-access), or `https://trust.rebellion.systems` for production. Request origin allowlisting as part of onboarding. There is no sandbox environment. Public SDK distribution is coming soon. Send JSON with `Content-Type: application/json`. Examples use descriptive placeholders for IDs and signatures; generate real values with the SDK before sending them. ## Choose the authorization model | Surface | Authorization | | --- | --- | | Bind start, callback, finalize and polling | The [bind ceremony](/api/bind), with both one-time proofs at finalize | | Graph reads | Fresh device-signed `authorization` for the exact operation and query | | Graph mutations | Device-signed edge certificate or action | | Own persona key listing | Verified AuthenSee persona credential | | Key revocation | Device signature, or the target's own persona credential | | Certificate status | A complete issuer-signed certificate plus a fresh nonce | | Health, current epoch, attestation keys, append-only report, attestation verification | No device authorization | A `graphNodeId` identifies a stable persona node. A `graphKeyId` identifies one device key. In edge certificates, `signerGraphKeyId` and `subjectGraphKeyId` are **node IDs**. In reachability queries, `sourceGraphKeyId` and `targetGraphKeyId` are also **node IDs**. In `authorization` and signed actions, `graphKeyId` is the **signing device**. ## Signed reads An authorization contains `graphKeyId`, `nonce`, `signedAt` and `signature`. Sign the UTF-8 canonical JSON of this structure using ECDSA P-256 with SHA-256, then encode the DER signature as base64: ```json { "v": "trust-graph-read-v1", "operation": "list-edges", "graphKeyId": "signing-device-id", "nonce": "fresh-random-nonce", "signedAt": "2026-11-04T12:00:00.000Z", "query": { "graphKeyId": "own-node-id" } } ``` The query is exactly the request body without `authorization`. Sign before defaults, and send exactly what you signed. The signer must own the source node. Use a new nonce and timestamp for each request, including retries. The freshness window is five minutes. Prefer the SDK signing helpers over implementing canonical encoding yourself. **Does not establish** Read replay tracking is process-local and resets on restart. An authorization may be accepted once per instance within its freshness window. Mutation replay tracking is separate and durable. ## Signed mutations Actions travel as `{action, signature}`. Sign canonical JSON of `{v: "trust-graph-action-v1", ...action}`. Each action names its operation, signing device, nonce, timestamp and operation-specific fields. The exact fields are listed on each resource page. Edge declarations use their own certificate signing format; use `signBrowserEdgeCertificate` or the corresponding signer helper. A bound key must be usable for the operation: unrevoked, unexpired, of the right purpose and, on a node with a device chain, approved. [Device authority](/api/devices) describes the limited operations available to quarantined devices. ## Errors and retries | HTTP | Typical meaning | Client behavior | | --- | --- | --- | | 400 | Invalid shape, timestamp, policy or signed field | Correct the request before signing again | | 401 | Missing or invalid authorization | Check signing bytes and credentials | | 403 | Authenticated key lacks authority, purpose or usable lifecycle | Refresh device state; use recovery when appropriate | | 404 | Resource or current membership unavailable | Treat evidence as unavailable | | 409 | Nonce reuse, stale revision or conflicting state | Read current state and reconcile | | 429 | Rate limit | Honor `retry-after` where returned | | 503 | Required capability or service unavailable | Retry later with a fresh authorization | Graph errors use `{error, code?}`; some conflicts include `currentEdgeId`. Shape errors use `{error: "Invalid request", issues}`. Dispatch on a documented `code` when present; do not assume every error has one. After an uncertain mutation response, read the relevant state before signing another mutation. Revision-based APIs document how to distinguish success from a conflicting write. A failed attestation verification can return HTTP 200 with `verified: false`; HTTP success alone is not a trust result. ## Rate limits Limited graph routes return 429 with `retry-after` and `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset`. The default general budget is 300 requests per minute per connection origin, with 120 successful writes per minute per graph key; deployments may configure those budgets. Devices, recovery and external keys also have resource-specific limits. Use the returned headers rather than assuming a deployment's budget. ## Resources | Resource | Reference | | --- | --- | | Identity and authority | [Bind](/api/bind), [graph keys](/api/graph-keys), [devices](/api/devices), [nodes](/api/nodes) | | Connections | [Edges](/api/edges), [tags](/api/tags), [handles](/api/handles) | | Query and evidence | [Reachability](/api/query), [attestations](/api/attestations), [epochs](/api/epochs) | | Signing integrations | [Content certificates](/api/content-certificates), [Git signers](/api/git-signers), [external keys](/api/external-keys) | | Service probes | [Health](/api/health) | --- URL: https://docs.trust.rebellion.systems/build/agents # Recognize an agent through trust Combine an agent-classified node, signed content and trust paths with application-owned permissions. **Build today** — use agent-classified nodes, signed vouches and local evidence verification. An application can recognize an agent whose signed messages come from a persona that the recipient's trust reaches. This is a foundation for “acting for someone you trust”; your application must separately define who may authorize which actions. ## 1. Bind the agent's key Arrange [early access](/early-access), including the app origin allowlist, and develop on staging. The bind-start contract accepts `nodeKind: 'agent'`. Complete the AuthenSee ceremony and applicable device approval flow; selecting the classification does not bypass binding or device authorization. **Does not establish** The agent classification is a label, not an independent check of software, ownership, humanhood or delegated authority. ## 2. Make the owner's vouch explicit An owner can issue a signed, directed vouch from their own node to the agent's node. Use the standard [vouch model](/concepts/vouches). The owner controls their outgoing score and can update or revoke that vouch. Treat that edge as a trust declaration. If your product needs to say “this owner authorizes this purchase,” define and verify that separate authorization in your application. A path through an owner does not by itself specify permitted actions. ## 3. Verify the agent's message and your connection Have the agent carry [signed message evidence](/build/signed-messages). The recipient checks the content signature, certificate and fresh device status, then queries from their own node to the agent's persona and verifies the proof locally. Show which checks succeeded. Use “signature verified” and “connected within your policy” as separate results. A server-side agent cannot query from another user's node using its own key. ## 4. Keep action permission in your app Bind sensitive requests to the intended operation and recipient, set an application freshness rule and require whatever confirmation the action needs. Model owner approval and revocation explicitly if you offer delegated actions. **Establishes** Signed content can establish the signing key's issuer-attested persona membership. A verified positive path separately establishes that the recipient's policy reaches that persona in the committed graph. **Does not establish** Neither check certifies the agent's behavior or grants it permission to spend money, read an account or act on a user's behalf. TrustGraph does not provide a general delegated authorization flow for querying as another user. --- URL: https://docs.trust.rebellion.systems/build/call-screening # Explore trust-based call screening A concept for cooperating calling applications, with no phone-number directory or turnkey blocker. **Concept, help wanted** — there is no TrustGraph phone-number binding or ready-made call blocker. A cooperating calling app could carry signed evidence before ringing, then let the recipient apply a trust policy. Ordinary caller ID supplies a phone number, not a TrustGraph signed object. ## A possible application flow 1. The calling app delivers a fresh, session-bound challenge and response through its signaling channel. 2. The recipient verifies the signing evidence and current membership. 3. The recipient queries from their own node and verifies any positive trust path locally. 4. The calling app applies its own allow, silence or prompt policy. This is a proposed composition. The [live challenge specification](/build/calls), pre-call delivery and binding to the actual call remain missing pieces. Device platform integration, background execution and timely delivery also require implementation and testing by the calling app. ## Design an unknown outcome A caller without evidence is unknown to this flow. A network error, expired certificate or missing challenge is also unknown. A verified negative path response is a signed report about the selected policy; it is not evidence that the caller is malicious. Your app must make its screening behavior clear to its user. **Does not establish** A displayed number does not establish a persona, a live speaker or key possession. There is no public phone number to persona lookup, and none is planned. Do not claim that the current primitives can screen arbitrary phone-network calls or identify their speakers. ## Help build the missing pieces We welcome partners building cooperating calling apps or researching device call-screening integrations. [Reach out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration) to discuss the protocol and platform work. Early access includes SDK access and app-origin allowlisting; development happens on staging. Start with the available [signed-message flow](/build/signed-messages) to understand membership and [proof verification](/build/trust-gated-features). --- URL: https://docs.trust.rebellion.systems/build/calls # Explore connection checks in calls A concept for Meet and Zoom integrations, with the missing live challenge protocol made explicit. **Concept, help wanted** — a live meeting check needs a challenge protocol and meeting integration. The intended experience is a participant badge answering “Is this participant connected to me?” TrustGraph already provides signed content, membership status and own-node trust queries. Those primitives alone do not establish a live participant's identity. ## What you can use today - [Signed content](/build/signed-messages) binds text to a certified device key. - A fresh certificate status check reports the device's current lifecycle status. - A [locally verified path](/build/trust-gated-features) checks the recipient's connection policy against a persona. ## What the integration still needs A proposed flow would have the verifier issue a fresh challenge, the participant sign a response, and the verifier check the response before checking membership and reachability. This is a design sketch, not an existing TrustGraph wire protocol. The missing specification must define canonical challenge bytes, verifier-generated nonces, expiry, replay handling, the intended verifier and the meeting session. A Meet or Zoom integration also needs to bind that exchange to the participant displayed in its interface. Relaying a response between participants or sessions must be considered when defining what the badge means. **Does not establish** Signed text alone does not establish signing time, current key possession by the displayed participant, the origin of speech or a connection between a face and a persona. Even fresh key-possession evidence does not automatically establish that the visible participant controls the key. Do not present this concept as an available meeting identity check. ## Help define the next step We are interested in meeting-client partners who can help specify challenge delivery, session binding and clear participant-facing outcomes. Start by [reaching out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration). Early-access work uses staging after we allowlist the app origin; the SDK's public package is coming soon. For asynchronous evidence that is buildable now, use [signed messages](/build/signed-messages). --- URL: https://docs.trust.rebellion.systems/build/git-signers # Check a Git signer's membership Verify a commit locally, resolve its signing key and check whether your trust reaches the signer. **Ships** — the server provides exact-key Git signer membership resolution. Your client supplies Git signature verification and the user interface. The useful result is precise: “This commit's signature is valid, its signing key has current persona membership, and a verified path meets your policy.” Begin with [early access](/early-access), an allowlisted app origin and a usable graph device on staging. ## 1. Verify the commit locally Read the actual signed Git object and verify its signature. Extract the verified signing public key; an author name, email address or hosting provider badge is not a substitute. For a TrustGraph graph-device signing key, continue below. For an OpenPGP signer, use [external signing keys](/build/openpgp-keys) to resolve the verified fingerprint instead. ## 2. Resolve the exact graph key Send `POST /v1/graph-keys/resolve-signer` with `graphNodeId`, `targetGraphKeyId` and `authorization`. Sign the exact two-field query under operation `resolve-git-signer`, using your own usable ordinary graph device and a fresh nonce. This route uses the existing device certificate and status formats. Verify both issuer signatures, the pinned issuer origin, times and nonce. Match the returned key identifier and public JWK to the public key that verified the commit. No commit bytes, digest or repository name need to enter this lookup. ## 3. Verify the current trust path Query from your own node to the returned persona with your chosen policy. Use the [local proof verification flow](/build/trust-gated-features) before displaying a positive connection result. Keep the commit signature, current membership and current path results separate. **Establishes** The combined checks establish the commit signature, issuer-attested current membership of that exact key, and a policy-satisfying path in the committed graph at the path evaluation time. ## Handle unavailable evidence Unknown and inactive target keys return the same 404. Report membership as unavailable; this is neither a signed proof of absence nor a historical revocation record. Typed SDK wrappers for signer resolution are not available yet; use the [graph-key reference](/api/graph-keys) for the HTTP contract. **Does not establish** An authenticated requester who already knows a key can learn its current persona membership. There is no prefix search or bulk enumeration route. Current membership and reachability do not establish authorization when the commit was signed: Git timestamps are signer-controlled. These checks do not establish human authorship, review or code safety. --- URL: https://docs.trust.rebellion.systems/build/openpgp-keys # Attach an OpenPGP signing key Link a proven OpenPGP signing key to a persona and resolve its current attachment. **Ships** — server routes for attachment, revocation, listing and signer resolution. Mail and document integrations are **Build today**. Use an existing OpenPGP signing key with TrustGraph. A usable ordinary graph device authorizes the attachment, and the selected OpenPGP key separately proves possession. Start with [early access](/early-access) and an allowlisted app origin on staging. ## 1. Attach one signing key `POST /v1/external-keys/attach` accepts a device-signed `attach-external-key` action, an armored public key and a clearsigned proof. The action binds the selected fingerprint, sanitized key-material digest and proof digest. The proof binds the persona, selected fingerprint, material digest and nonce. This is an HTTP integration: typed SDK wrappers for these routes are not available yet. Follow the [external-key reference](/api/external-keys) for exact bytes and supported OpenPGP formats. Do not substitute a hash of the original armor for the canonical sanitized-material digest. **Establishes** An attachment associates the one proven primary key or signing subkey with the persona at attachment time. Sibling signing keys need their own attachments. ## 2. Verify the document, then resolve the signer Verify the mail, document or Git signature locally and obtain its actual signing fingerprint. Send a signed `resolve-external-signer` read to `POST /v1/external-keys/resolve-signer`, using your own `graphNodeId` and exactly one locator: `signingFingerprint` or `signingKeyId`. The response contains a certificate and status. Verify both issuer signatures against your pins; recompute the signing packet's fingerprint; match the locator, certificate digest, request nonce and freshness. An absent, ambiguous, revoked or inactive attachment returns the same unavailable response. Prefer the full fingerprint when you have it. ## 3. Check a path and keep the result fresh Query from your own node to the resolved persona and [verify the proof locally](/build/trust-gated-features). If that check takes more than 30 seconds after status evaluation, resolve again and require the same node and fingerprint. Recheck expiry immediately before showing a current result; the API reference defines the result's validity bound. ## 4. Revoke an attachment Use `POST /v1/external-keys/:externalKeyId/revoke` with a signed `revoke-external-key` action. Revocation is terminal for that fingerprint: it cannot be attached again, even to the same persona. Own-node listing includes revoked records. **Does not establish** Attaching makes a fingerprint-to-persona link resolvable by authenticated graph members who know the locator. It does not establish historical key control, authorship or code safety. Later OpenPGP expiry or revocation is not automatically refreshed after import; revoke the TrustGraph attachment explicitly. Document signature expiry still applies. The full submitted public block reaches the server for validation, although user IDs, names, email addresses and photos are not retained. --- URL: https://docs.trust.rebellion.systems/build/signed-messages # Verify signed mail and messages Combine portable signed text, current device status and a locally verified trust path. **Build today** — for cooperating senders who include signed evidence in their messages. Build a mail reader, chat client or message checker that answers: “Was this text signed by a key belonging to someone my trust reaches?” The same portable text format can travel through email, Telegram, Signal or another text channel. Your application supplies the channel integration. ## Before you start Arrange [early access](/early-access), including SDK access and your app origin allowlist entry. Develop on [staging](/environments). The recipient needs an approved, usable graph device to query from their own node, plus independently pinned issuer keys and the SDK proof verifier. ## 1. Carry evidence with the message The sender obtains their own membership certificate with `issueDeviceCertificate`, verifies its issuer and checks that its key and node match the selected device. Use `createContentStatement`, `signer.signContent` and `formatSignedContent` to produce portable signed text. Text normalization changes CRLF and CR line endings to LF. It does not strip whitespace or normalize Unicode. Keep the evidence block intact when transporting or displaying the message. ## 2. Check the signature locally Use `parseContentMessage` and `verifySignedContent` with your pinned issuer and cryptographic adapters. Reject malformed or ambiguous evidence. For PGP-signed mail, use the separate [OpenPGP integration](/build/openpgp-keys); MIME parsing and mail-client integration belong to your app. **Establishes** A successful content check establishes that the text matches the signature and that the issuer attested the signing device's persona membership. ## 3. Check current status, then your trust path Call `getDeviceCertificateStatus` with the certificate and a fresh random nonce. Verify the response using `verifyDeviceStatus`, and require valid status and unexpired evidence for a current result. Only the certificate and nonce go to this endpoint; message text and its digest stay local. Query reachability from the recipient's own node to the certificate's node, then verify the exact question and proof locally. Follow [trust-gated features](/build/trust-gated-features) for this step. ## 4. Keep the outcomes distinct Show separate outcomes for a valid signature, current membership and a verified positive path. Label offline status as “not checked.” A negative path result is a signed report. Errors and missing evidence remain unknown. **Does not establish** Unsigned messages cannot be attributed through this flow. A valid signature does not prove signing time, current device access, human authorship, delivery or permission to act. A bot has no delegated authorization to query as a recipient: personalized queries require that recipient's device signature. There is no public email or phone number to persona lookup, and none is planned. Next: [What a proof tells you](/concepts/what-a-proof-tells-you). --- URL: https://docs.trust.rebellion.systems/build/trust-gated-features # Gate a feature on a verified trust path Ask from your user's own node and use the locally verified verdict in your application policy. **Build today** — compose signed reachability queries with the SDK's local proof verifier. Your application chooses which feature a trust path can unlock. TrustGraph answers whether a path meets the submitted policy. Start with [early access](/early-access), an allowlisted origin, a bound and approved graph device, and the issuer pins supplied through trusted application configuration. ## 1. Choose the question Use the signed-in user's node as the source and an explicitly selected persona as the target. `minTrust` is a score from 0 to 100; `maxHops` is 1 to 3. Use `scope: 'identity'` for the current circuit. Node identifiers go in the query; the authorizing device identifier goes in the signed-read envelope. ## 2. Sign and verify Pass an initialized SDK client, the user's local device key, their node and device identifiers, a target node, and independently pinned issuer JWKS to this function. It returns the verified reachability verdict and throws if verification fails. Complete the client and key setup in the [quickstart](/quickstart). ```ts import type { TrustGraphClient } from '@rebellion-systems/trust-graph-sdk'; import { signBrowserGraphRead } from '@rebellion-systems/trust-graph-sdk/browser'; import { createZkVerifier, wotPathVerificationKey } from '@rebellion-systems/trust-graph-sdk/zk'; export async function checkConnection(input: { graph: TrustGraphClient; privateKey: CryptoKey; myGraphNodeId: string; myGraphKeyId: string; targetGraphNodeId: string; pinnedAttestationJwks: { keys: (JsonWebKey & { kid: string })[] }; }): Promise { const query = { sourceGraphKeyId: input.myGraphNodeId, targetGraphKeyId: input.targetGraphNodeId, policy: { scope: 'identity' as const, minTrust: 50, maxHops: 3 }, }; const authorization = await signBrowserGraphRead(input.privateKey, { operation: 'query-reachable', graphKeyId: input.myGraphKeyId, graphNodeId: input.myGraphNodeId, query, }); const response = await input.graph.queryReachability({ ...query, authorization }); const verifier = createZkVerifier(); try { const result = await verifier.verifyAttestation({ verificationKey: wotPathVerificationKey(), attestation: response.attestation, jwks: input.pinnedAttestationJwks, question: { sourceGraphKeyId: query.sourceGraphKeyId, targetGraphKeyId: query.targetGraphKeyId, ...query.policy, }, }); if (!result.verified) throw new Error(`Answer rejected: ${result.code}`); return result.verdict.reachable; } finally { await verifier.destroy(); } } ``` Sign the exact submitted query with a fresh nonce on every attempt, including retries. Only use `result.verdict` after successful verification. A verification error is an unknown outcome; do not turn it into a negative path report. **Never** Never authorize a positive result from the response's unsigned convenience fields, a checksum or a server signature alone. Never replace your pinned issuer keys or circuit verification key with keys supplied by the answer's issuer. ## 3. Apply your application's authorization rules Bind the check to the application's selected user, target and policy. Keep normal authentication, action permissions and freshness requirements alongside the graph check. A trust path is one input to that decision. **Establishes** A verified positive establishes a qualifying path in the committed graph at evaluation time without revealing its intermediate nodes and edges to the recipient. **Does not establish** A negative result is an operator-signed report, not a proof that no path exists. Proof validity does not establish that the committed graph is complete or latest, that the target is human, or that an action is safe. The service sees the graph and query; the proof does not hide them from the service. --- URL: https://docs.trust.rebellion.systems/build-with-ai # Build with AI Give your coding assistant the current protocol, exact question, and integration boundaries. Use the Markdown version of any page as context for a coding assistant. Start with [/llms.txt](/llms.txt) for the documentation index or [/llms-full.txt](/llms-full.txt) for the complete text. SDK access remains private early access. Public package availability is coming soon. Arrange [package access and your app origin](/early-access) before asking an assistant to run an integration. ## Copy this integration brief ```text Build a browser integration with TrustGraph using the current TypeScript SDK. Read /quickstart.md, /api/attestations.md, /concepts/identity.md, /concepts/what-a-proof-tells-you.md, and /security.md first. Use https://trust-staging.rebellion.systems for development. Obtain SDK access and exact app-origin allowlisting through early access. Use the private package installation instructions supplied for this project. Keep graphNodeId (persona) separate from graphKeyId (signing device). Persist the non-extractable browser key and pending bind before navigating. Use the SDK's durable beginBind/parseBindCallback/finalizeBind APIs. Implement the application's storage and verified-device-state adapters. Do not infer device approval from a completed bind. Sign the exact query with a fresh nonce; query only from the user's own node. Use scope: 'identity', minTrust: 0..100 and maxHops: 1..3. Verify the returned attestation locally with the SDK /zk entry and bundled VK. Supply attestation JWKS from independently trusted application configuration. Use only the verified verdict; distinguish a positive path proof from a negative signed report. Destroy the verifier when finished. Do not invent SDK methods, public email/phone lookup, public package access, a live-call identity guarantee, or an in-browser verifier hosted by these docs. Typecheck each sample against the SDK version supplied to this project. ``` ## Keep the application boundary explicit Ask the assistant to declare your storage, navigation, and cryptography adapters by type. A function named `savePendingBind` is application code, not a hidden SDK API. Browser keys require structured-clone storage such as IndexedDB; JSON serialization cannot preserve a `CryptoKey`. For native integration, use an injected signer and the platform key store. Keep the exact canonical payload and signature format consistent with the [API signing rules](/api). Browser-specific helpers are not native key-store adapters. ## Review the generated result Check the source node in every query, the local source of all verification pins, and the handling of recovery and quarantine. Follow a bind through a real callback and reload. Exercise a reachable answer, a negative answer, and a verification failure before presenting a connected badge. **Never** Never give an assistant real private keys, live bind proofs, provider credentials, or account recovery material as example data. Use test accounts and synthetic values for prompts and fixtures. **Does not establish** Generated code and a successful typecheck do not demonstrate a completed AuthenSee ceremony or a valid proof. Verify the complete flow in your configured staging application. --- URL: https://docs.trust.rebellion.systems/changelog # Changelog Public integration milestones and historical documentation changes. This history records dated documentation and integration changes. Use the current [API reference](/api) and [quickstart](/quickstart) for request contracts. ## 4 November 2026 — Integration documentation The documentation now separates concepts, integration guides, API contracts and ecosystem examples. Mail, messages and agents are described as compositions of available primitives. Git signer membership has a server route. Calls and call screening are concepts with missing pieces documented explicitly. Access remains by arrangement: [reach out](/early-access) for SDK access and app-origin allowlisting, then develop on staging. Public SDK distribution is **coming soon**. ## November 2026 — Operations This archived note preserves the operations documentation published on 4 November 2026. Deployment status and external-service settings below are dated operator observations, not current-status guarantees. The policy describes the operated TrustGraph staging and production services, not an automatic retention rule for clients or independently operated deployments. The retention rollout was reported live on staging and production by the founder's operator on 4 November. The pinned server deployment and migration workflows were reported complete. Omnibus remains one example client: its device storage and interface choices are documented [separately](/ecosystem/omnibus). Data and log retention The operated staging and production services use persistent host journals with a 29-day retention setting and hourly rotation and cleanup. Whole-file expiry and scheduling delay mean the published bound is 30 days in healthy operation, not a strict 29-day per-record deadline. Failed cleanup jobs and powered-off disks cannot provide live erasure. Operators alert within two hours of a missed sweep and treat records older than 30 days as an incident. Login-history archives are rotated and expired by timestamp to 29 days. The separate per-account last-login record remains outside that established expiry scope. The legacy host log daemon is retired; old container log copies are removed only after replacement collection is verified. Public administrative access is closed; operators use a private network. The separate log store retains logs and traces for 15 days. CI logs and artifacts have 30-day retention for new runs; this does not shorten existing artifacts retroactively or change other repositories' defaults. There is no third-party edge log export. Diagnostic copies retain the original event's expiry. The dated operator report recorded no host snapshots or backups; these controls do not establish a blanket database or historical-backup deletion policy. Request-record minimisation The collector selects application and proxy records, rather than exporting the whole host journal. It removes journal metadata and limits proxy request fields. Routine proxy health-check request records are dropped; application warnings and errors are kept. Other bodies may still contain personal data. This is request-record minimisation, not general redaction. Operator details The message becomes the log body. Selected container labels, service and environment labels, timestamps and severity remain; host, process, command, image identifiers and journal cursors are discarded. JSON objects are parsed and recognized severity levels are mapped. Unknown severity remains unset. Plain text, malformed JSON and non-object JSON retain their plain body. Only proxy request records whose parsed path equals /healthz or /readyz are dropped after query stripping. Application records are not filtered by this rule. Remaining proxy requests retain only time, level, message, path, method, status and duration. Client addresses, ports, user agents, headers, host/target, query strings and client request identifiers are removed without retaining linkable hashes. Other JSON retains its fields except top-level process/host fields and query strings in path fields. Plain text is unchanged. Path segments and other bodies may contain identifiers. Minimisation does not rewrite the local journals or previously exported records. Only receiver cursors persist; there is no on-disk payload queue. Export retries last five minutes, so extended outages can lose remote copies while local retention still applies. Operational boundaries Deployment success is reported only after rollout and readiness checks pass. Unclear deployment state stops for operator review. Operators verify log age, cleanup health and receipt of application and proxy records before retiring legacy copies. Production changes require the founder's explicit approval at the time; this documentation grants no operational approval. These settings are dated operator observations from 4 November 2026, not current-status guarantees or automatic rules for independently operated deployments. Omnibus-specific services launched; they are part of the [Omnibus app layer](/ecosystem/omnibus/app-layer), separate from the core TrustGraph API. ## August 2026 — Authentication archive The August documentation introduced the AuthenSee key-binding ceremony, device-signed graph mutations, strict request validation and signed reachability responses. Its request examples are historical and should not be used for current integrations. **Note** The August signature-only reachability scheme was not a zero-knowledge proof. Current positive `ultrahonk-wot-path-v2` attestations require local proof verification. Negative answers remain signed reports without a proof of non-existence. Binding, finalization and device approval have also evolved. Continue with [how TrustGraph works](/concepts/how-it-works) and [what a proof tells you](/concepts/what-a-proof-tells-you). --- URL: https://docs.trust.rebellion.systems/concepts/glossary # Glossary The identifiers, evidence, and policy terms used throughout TrustGraph. | Term | Meaning | | --- | --- | | Persona / node | The stable graph identity that owns directed connections. Identified by `graphNodeId`. | | Device / graph key | A P-256 key bound to a persona. Identified by `graphKeyId`; its lifecycle and current authority govern use. | | Bind | The AuthenSee-backed ceremony joining a device key to a persona, completed using initiation and completion capabilities. | | App origin | The exact scheme, host and optional port allowed to receive the bind callback. | | Vouch / edge | A source persona's directed, signed trust declaration toward a target persona. | | Trust score | A source-selected value from 0 to 100. An incoming listing uses null when the reader cannot see that score. | | Scope | A protocol field. New declarations and queries use `identity`. | | Policy | The constraints on a reachability query, including minimum score and maximum path length. | | Reachability | The existence of a non-empty, directed, policy-satisfying path between the selected nodes. | | Signed read | A fresh device signature over the exact operation and query. | | Nonce | A fresh value included in a signed request for replay handling. | | Attestation | The issuer-signed envelope binding the reachability answer and its public inputs. | | Positive path proof | A zero-knowledge proof that a qualifying path exists in the cited committed graph. | | Negative report | A signed answer reporting no qualifying path, without a proof of nonexistence. | | Public inputs | The values exposed and checked alongside a proof, including endpoints and policy. | | Graph epoch root | A commitment to the graph state used for an answer. The path circuit uses a Poseidon root. | | Event-log root | A commitment to graph event history. A larger log size alone is not a prefix-continuity proof. | | JWKS | A JSON Web Key Set. Clients independently pin the Ed25519 issuer keys they trust. | | Verification key / VK | The circuit key used to check proof bytes; the SDK supplies the pinned path-circuit key. | | Device chain | A persona's signed, append-only history of device roots, approvals, revocations and resets. | | Quarantined | A usable persona member that lacks current chain approval for ordinary graph operations. | | Membership certificate | An issuer-signed statement associating a device public key with a stable persona. | | Signed content | Normalized text plus a device-signed statement and membership evidence. | | `lf-v1` | Text normalization that converts CRLF and CR to LF without trimming or Unicode normalization. | | Handle | An optional public name resolving to a node through the supported signed lookup flow. | | Private relationship tag | Owner-accessible relationship metadata, readable by the service and separate from proof policy. | Start with [how TrustGraph works](/concepts/how-it-works) or return to the [quickstart](/quickstart). --- URL: https://docs.trust.rebellion.systems/concepts/how-it-works # How TrustGraph works Bind a device, declare directed trust, then ask and verify from your own persona. TrustGraph lets an application ask whether one persona's trust reaches another under a chosen policy. A positive answer includes a path-existence proof that the application can verify locally. ## Bind a device to a persona Your application generates a P-256 key on the device. The user completes an AuthenSee ceremony. The initiating app finalizes the bind using two separate one-time capabilities and retains the returned device and persona identifiers. The stable persona is `graphNodeId`; its signing device is `graphKeyId`. A persona can have independently keyed devices. On a persona with a device chain, joining the persona and being approved to act are separate steps. App retains initiationProof; callback returns completionProof. Both proofs converge at finalize with the matching local key. Continue with [identity and devices](/concepts/identity). ## Declare a vouch A usable authorized device signs a directed trust declaration from its persona to another persona. The score is 0–100. The declaration becomes active immediately; the recipient does not accept it. The opposite direction is a separate choice. Connections belong to personas. A device's revocation does not itself remove its persona's active connections. The source can update or revoke its declaration. Continue with [vouches and scores](/concepts/vouches). ## Ask from your own node The user's device signs a query naming its own node, a target node, and a policy. `minTrust` sets the minimum score and `maxHops` bounds the path to 1–3 hops. The current scope is `identity`. The service searches its graph and returns a signed attestation. For a positive answer, it also generates a zero-knowledge proof of a qualifying path. Your app checks the exact question, independent issuer pins, the pinned circuit key, and the proof bytes locally. Private witness: intermediate path nodes and edges. Public: endpoints, policy, time, path length and epoch commitment. Positive: ZK path proof. Negative: signed report. **Establishes** A verified positive establishes a qualifying path in the cited graph commitment without disclosing its intermediate nodes and edges to the verifier. **Does not establish** The service knows the graph and query. A proof does not establish that the committed graph is complete or latest. A negative answer is a signed server report, not a proof of nonexistence. ## Build around the result A [signed message](/build/signed-messages) can carry evidence of its signing device and persona. The recipient separately queries reachability from their own persona to that sender. [Trust-gated features](/build/trust-gated-features) use the same query-and-verify flow with an application-specific acceptance policy. Follow the [quickstart](/quickstart) to build that flow on staging. --- URL: https://docs.trust.rebellion.systems/concepts/identity # Personas and devices Separate a stable graph identity from the devices authorized to act for it. A persona owns its connections. A device holds a key that can act for that persona when its binding, lifecycle and authority permit it. | Identifier | Names | Use | | --- | --- | --- | | `graphNodeId` | A stable persona node | Connection endpoints, query source and target | | `graphKeyId` | One device's P-256 public key | Read authorization, action signatures, lifecycle | A first device can have the same identifier as its new persona. Do not rely on that equality. Keep both values returned by binding. ## Read the legacy field names correctly An edge's `signerGraphKeyId` and `subjectGraphKeyId` contain node identifiers. A query's `sourceGraphKeyId` and `targetGraphKeyId` also contain node identifiers. Authorization's `graphKeyId` names the device signing for that node. The server verifies both the signature and the device-to-node relationship. An app cannot query from an arbitrary persona just by knowing its identifier. ## Bind with AuthenSee Persist the local key and pending ceremony before opening the hosted flow. Finalize with the initiation capability retained by the app and the completion capability returned to its allowed callback origin. The durable SDK helpers validate the callback and returned key against the saved intent. To add a device to a known persona, start a fresh ceremony with `recoverNodeId`. Authentication-only recovery with `recoverByPersona` uses an existing verified provider mapping. If that mapping is ambiguous, use an explicit node reference in a fresh ceremony. **Does not establish** A public node reference is not authentication. Matching a human's display name or starting an unrelated first-bind ceremony does not establish that the new device belongs to the existing persona. ## Approval is a separate state A device chain records signed roots, approvals, revocations and resets. An approved, usable device can authorize ordinary graph operations. A newly recovered device on a chained persona starts quarantined until approved or until it completes an eligible reset. Pending binding → authenticated device → approved or quarantined; authorized device approval changes the approved set. Revoked and expired keys cannot authorize ordinary requests. Quarantined devices retain limited own-device inspection and maintenance operations. Ordinary reads and actions are refused with `GRAPH_KEY_NOT_APPROVED`. Expired and revoked keys fail lifecycle checks; legacy personas without a chain retain their existing authorization model. `devices.log` parses the directory. Verify its records and continuity using the SDK's device-chain verifier and retained pins before passing authority into `deviceState`. Binding completion alone is not an approval signal. ## Recovery and revocation Each device generates its own private key. Device recovery preserves the persona and its connections. An eligible reset opens a new authority epoch and revokes the displaced devices. Recovered devices do not inherit another device's old encrypted-content delivery entitlements. Revoking a device prevents its future authorized use; it does not automatically remove active persona connections. See [devices API](/api/devices) for the implementation sequence. **Does not establish** Persona membership and the `human` or `agent` classification are not proof of humanhood or a real-world identity. A membership certificate also does not establish a recipient's trust in that persona. --- URL: https://docs.trust.rebellion.systems/concepts/reachability-and-proofs # Reachability and proofs Ask a bounded policy question and verify a positive path locally. Reachability asks whether a directed path connects your persona to a target under the policy you choose. The query is authorized by a device belonging to your source node. Schematic path: your node and target are public; intermediate nodes and edges are hidden from the verifier, not from the service. ## Choose a policy | Field | Meaning | Accepted values for new queries | | --- | --- | --- | | `scope` | Circuit compatibility scope | `identity` | | `minTrust` | Minimum score on the path | 0–100 | | `maxHops` | Maximum path length | 1–3 | | `currentTime` | Optional future horizon that narrows eligibility | A parseable future timestamp | Out-of-range values are rejected, not silently clamped. The service controls evaluation time. A requested future horizon can only narrow the result: edges must be active now and at that horizon. A path is non-empty; a query from a node to itself still needs qualifying edges. ## Sign the question you submit Sign the exact query, including the policy fields you provide. Do not apply defaults after signing or reuse an authorization for another target. Generate a fresh read signature on a retry. The source and target fields use node IDs despite their legacy names, `sourceGraphKeyId` and `targetGraphKeyId`. Authorization identifies the signing device separately. Canonical JSON: trust-graph-read-v1, operation, graphKeyId, fresh nonce, signedAt, exact query. Sign with the device P-256 key. ## Verify the attestation Use the SDK's `/zk` entry with your independently pinned attestation JWKS and `wotPathVerificationKey()`. The verifier checks the issuer signature, expected question, pinned circuit-key hash, correspondence of the circuit inputs, and proof bytes. Use the verified result rather than the response's unsigned convenience fields. A failed check must remain a verification failure; it is not a negative reachability result. Private witness: intermediate path nodes and edges. Public: endpoints, policy, time, path length and epoch commitment. Positive: ZK path proof. Negative: signed report. **Establishes** A verified positive proves that a qualifying path exists in the cited committed graph. Intermediate node identifiers and edges are hidden from the verifier. **Does not establish** The proof does not establish that the service published the complete graph. Endpoints, policy, evaluation time and path length remain public. The service sees the graph and query. A valid proof does not establish that the server committed the complete or latest graph. ## Treat a negative differently A negative attestation has null proof bytes, circuit inputs and verification-key hash. Its signature can be verified, but it reports the server's answer without a proof of nonexistence. Label it “No path reported under this policy”, rather than “proved unconnected”. A positive proving failure is an error. There is no signature-only positive fallback. ## Commitments and history The current-epoch endpoint exposes graph and event-log commitments, including a Poseidon epoch root. A saved event-log size and root are useful continuity inputs. Growth alone does not prove that the old log is an unchanged prefix, and the current-epoch endpoint itself returns unsigned server claims. Read [what a proof tells you](/concepts/what-a-proof-tells-you) before turning a result into a user-facing badge or an access decision. Positive answers use the UltraHonk `ultrahonk-wot-path-v2` proof scheme; negative answers remain signed reports. --- URL: https://docs.trust.rebellion.systems/concepts/signed-content # Signed content and membership Verify a message, its signing device, and the separate relationship to its recipient. Portable signed text lets someone send a message with evidence of the device that signed it and the persona that device belongs to. The recipient can verify that evidence locally, then separately ask whether their own trust reaches the sender. ## Three checks, three meanings | Check | What it establishes | | --- | --- | | Content signature | The normalized text matches the statement signed by the device key | | Issuer-signed device certificate | The issuer attested that key's membership in the named persona | | Recipient's verified reachability query | A qualifying path from the recipient's persona to the sender in the cited graph commitment | Do not merge these into a single unexplained “verified” label. ## Create a portable message The sender requests a certificate for their own device and node, verifies the issuer and selected key/node, then creates a content statement. `createContentStatement` hashes text after `lf-v1` normalization: CRLF and CR become LF. It does not strip whitespace or normalize Unicode. `signer.signContent` signs the statement. `formatSignedContent` appends a canonical evidence block. The statement commits to the whole membership certificate, including its random certificate identifier. The recipient uses `parseContentMessage` and `verifySignedContent` with pinned issuer verification and cryptographic adapters. Parsing rejects ambiguous markers, extra text and malformed evidence. ## Check current status separately A certificate can have a valid signature even after expiry or revocation. A fresh signed certificate-status response binds the certificate digest and a caller nonce. The SDK checks the issuer and a 60-second freshness bound. Status checks send the certificate and nonce, not the message plaintext or content hash. If status is not checked, label it “status not checked”. Offline verification does not become a current-valid claim. **Does not establish** A valid content signature does not prove when the text was signed, current device access, delivery, recipient trust, historical validity, or permission to act. A sender-chosen date is not signing-time evidence. ## Add the recipient's perspective After verifying the sender evidence, query reachability from the recipient's own node to the sender's stable node. Verify the returned attestation locally using the same rules as the [quickstart](/quickstart). [Signed messages](/build/signed-messages) applies this pattern to mail and text channels. [OpenPGP keys](/build/openpgp-keys) and [Git signer membership](/build/git-signers) use related membership checks for existing signing keys. --- URL: https://docs.trust.rebellion.systems/concepts/vouches # Vouches and scores Directed, signed declarations with independently chosen scores. A vouch is a directed trust declaration between two persona nodes. Its source chooses a score from 0 to 100 and signs the declaration with an authorized device. ## One direction at a time Alice → Bea and Bea → Alice are separate declarations. Each source controls its own score. A new declaration is active immediately; there is no acceptance step. If your interface offers “Trust them too”, let the recipient choose a fresh score for their own direction. Send a fresh signed declaration to update a score. The server replaces the current declaration for that pair. Only the source can revoke it. The legacy confirmation method exists for compatibility and is not part of the normal flow. **Does not establish** A score records the source's choice. It is not a centrally assigned reputation or a guarantee about a person's conduct. Receiving a vouch gives no authority to change the source's declaration. ## Scores and query policy A query's `minTrust` sets the minimum allowed score along its path. `maxHops` limits path length to 1–3. Use `scope: 'identity'` for new declarations and queries; this is a protocol compatibility value, not a user-selectable relationship category. Zero is a valid score. An expired or revoked edge does not satisfy the current path policy. Connections between persona nodes remain independent of the lifecycle of any particular device. ## Incoming score privacy The declaring persona can read its numeric score. The receiving persona sees the incoming connection and direction, with `trust: null`. Keep that null distinct from zero and from a reciprocal declaration. **Does not establish** Hiding numeric incoming scores from listing responses does not prevent an authorized path querier from learning information through threshold queries. ## Updating without overwriting a newer choice Use conditional replacement when a user edits a declaration they previously loaded. The signed nonce binds the current edge identifier, and the replacement route rejects a stale choice. Refresh and ask the user to review the current state when the edge has changed. See the [edges API](/api/edges). ## Private relationship tags Tags organize your outgoing relationships and are separate from the trust score. They are private to the owner in API access, but readable by the service. They do not change what the path circuit proves. Continue with [reachability and proofs](/concepts/reachability-and-proofs) or [declare a vouch in the quickstart](/quickstart). --- URL: https://docs.trust.rebellion.systems/concepts/what-a-proof-tells-you # What a proof tells you Use precise labels for signatures, membership, positive proofs, and negative reports. A successful cryptographic check answers a particular question. Keep that question visible in your application's decisions and labels. ## A positive reachability proof **Establishes** The verified answer contains a path satisfying the checked policy between the checked endpoints in the cited graph commitment. The verifier does not learn intermediate node identifiers or edges. The application must independently pin the issuer's attestation keys and the circuit verification key, pass the exact expected question, and verify the proof bytes. A signature alone is insufficient for a positive result. **Does not establish** The proof does not establish that the service published the complete graph. The service knows the graph and path. A proof does not establish completeness or recency of the committed graph, authenticate every original edge signature inside the circuit, establish humanhood, or certify a person's intentions. Endpoints, policy, evaluation time and path length remain public. ## A negative reachability report The signature establishes that the configured issuer made the report. There is no zero-knowledge proof that a qualifying path does not exist. Use “No path reported under this policy”. Reserve “Proof verified” for a successfully verified positive path proof. ## A device membership certificate The issuer attests that a device key belongs to a persona. Current certificate status is a separate signed check, and current device approval is governed by device authority. **Does not establish** Membership does not prove humanhood or your trust in the persona. A lifecycle-valid certificate is not by itself evidence of current device-chain approval. ## A signed message The signature binds normalized text to a device key and its membership evidence. It can be checked separately from the channel that delivered the message. **Does not establish** The message does not establish signing time, live presence, ownership of the surrounding email address or phone number, current device access, or permission to execute an action. A copied valid signed message remains signed text. ## A current-epoch response A graph root commits to a graph snapshot. The public epoch endpoint returns server claims; retaining a larger event-log size does not prove append-only continuity with an earlier root. ## Design labels around evidence | Evidence available | Suitable wording | | --- | --- | | Locally verified positive reachability attestation | Connected under your policy · proof verified | | Verified negative attestation | No path reported under this policy · signed report | | Valid content and membership signatures, status unchecked | Signature and membership verified · status not checked | | Verification failed | Could not verify this evidence | Do not turn a failed verification into a “not connected” answer. See [security](/security) for verification responsibilities and [calls](/build/calls) for the additional work a live-call experience needs. Positive answers use the UltraHonk `ultrahonk-wot-path-v2` proof scheme; negative answers remain signed reports. --- URL: https://docs.trust.rebellion.systems/early-access # Request early access Get SDK access, register your application origin, and start building on staging. TrustGraph onboarding is handled by hand. [Reach out to hello@rebellion.systems](mailto:hello@rebellion.systems?subject=TrustGraph%20integration) with what you want to build and your application origins. ## What to send Include your use case, your web or native platform, and each exact application origin you will use. An origin includes the scheme, hostname, and port when present. Development and deployed app origins should be listed separately. ## What happens next 1. We arrange access to the private SDK package and its installation instructions. Public package availability is **coming soon**. 2. We add your exact application origin to the service allowlist and configure browser access. This is a required onboarding step before binding a device. 3. You configure the staging API and independently supplied attestation-key pins in your application. 4. You work through the [quickstart](/quickstart) to bind a device, declare a vouch, and verify a reachability answer locally. The SDK package is `@rebellion-systems/trust-graph-sdk`. Access to these public docs does not grant access to the package. Use the installation instructions supplied during onboarding. ## Develop on staging Use `https://trust-staging.rebellion.systems`. There is no sandbox environment. Production uses `https://trust.rebellion.systems`; moving to it requires a separate configuration and onboarding review. See [environments](/environments). **Note** A successful origin registration allows the browser ceremony to return to your app. Each user's device still completes its own AuthenSee binding and signs its own requests. ## Choose a first integration [Signed messages](/build/signed-messages), [trust-gated features](/build/trust-gated-features), and [agents](/build/agents) can be composed from the existing primitives. [Git signer membership](/build/git-signers) has a server route. [Calls](/build/calls) and [call screening](/build/call-screening) are concepts seeking collaborators. --- URL: https://docs.trust.rebellion.systems/ecosystem # Built on TrustGraph Omnibus and the building blocks for your own TrustGraph integration. TrustGraph is a service and integration toolkit that clients can use directly. An app chooses its interface, storage and permission rules around the graph's signed operations and verifiable answers. ## Omnibus [Omnibus, built on TrustGraph](/ecosystem/omnibus), is one app using core identity binding, vouches and reachability. It also has its own app layer. You can integrate the core platform directly. ## Your integration | Start here | Status | | --- | --- | | [Git signer membership](/build/git-signers) | Ships | | [Signed mail and messages](/build/signed-messages) | Build today | | [Agents acting for someone you trust](/build/agents) | Build today | | [Trust-gated features](/build/trust-gated-features) | Build today | | [Meet and Zoom connection checks](/build/calls) | Concept, help wanted | | [Call screening](/build/call-screening) | Concept, help wanted | “Ships” identifies an available server route. “Build today” identifies an integration you can compose from shipped primitives. Concept guides describe missing work and invite collaborators. ## Get access [Reach out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration) with what you want to build. We arrange SDK early access and add your app origin to the allowlist. Develop on [staging](/environments), then coordinate production access. Public SDK distribution is **coming soon**. --- URL: https://docs.trust.rebellion.systems/environments # Environments Use staging for development and configure independent trust anchors for each environment. TrustGraph has two API environments. Start through [early access](/early-access); there is no sandbox. | Environment | API origin | Use | | --- | --- | --- | | Staging | `https://trust-staging.rebellion.systems` | Integration development through early access | | Production | `https://trust.rebellion.systems` | Production integrations after onboarding | ## Configure your application Keep the selected server origin, allowed app origin, issuer identity, pinned Ed25519 attestation keys, and SDK/circuit version together in reviewed application configuration. Obtain pins through onboarding and your application's trusted release process. An app origin is an exact `scheme://host[:port]` value. Register each origin you need; changing a port changes the origin. The bind return path is `/trust-graph/bind/callback` on that origin. Serve that path in your application, including on a direct navigation after a browser reload. Browser CORS access and the bind-origin allowlist are separate configuration requirements. Arrange both during onboarding. **Never** Never accept attestation keys or a circuit verification key supplied by the response being verified. A signature checked against a key chosen by that response does not establish trust in the configured service. ## Keep state separate Store device keys, pending binds, node references, and verified device-chain pins under the selected environment. Finish a pending bind against its recorded server origin. Do not change its origin to switch networks. The service's public origin is also the issuer identity for content membership certificates. Pin it explicitly. An authentication page or callback host does not replace that issuer identity. ## Prepare production Confirm production package access, application origins and browser configuration with the onboarding team. Supply production attestation pins through a trusted channel, test the complete bind callback, and verify positive and negative query handling. Review [device recovery](/api/devices), [API errors and limits](/api), and [security](/security). Publish replacement pins in clients before an attestation signing-key rotation. A circuit update needs a reviewed verification-key update and compatible SDK release. **Does not establish** An endpoint URL does not guarantee that an optional capability is enabled. Check the documented readiness capabilities for optional core features such as tags and handles, and handle explicit unavailable responses. --- URL: https://docs.trust.rebellion.systems/ # What do you want to build? Connect your app to TrustGraph. Let people carry signed evidence, ask whether trust reaches them, and verify the answer locally. Start with a browser key. [Follow the quickstart →](/quickstart) [ Build today Mail you can verify Signed by someone you’re connected to. Verify sender-carried text and query from your own node. Guide → ](/build/signed-messages) [ Build today Signed messages Build a Telegram or Signal filter for cooperating senders carrying signed text. Guide → ](/build/signed-messages) [ Ships Git signer membership Resolve a known signing key, then apply your own trust policy. Guide → ](/build/git-signers) [ Build today Agents acting for someone you trust Combine agent nodes, signed evidence and source-controlled vouches. Guide → ](/build/agents) [ Concept, help wanted Connected to you, on a call Explore a Meet or Zoom integration with a session-bound challenge. Guide → ](/build/calls) [ Concept, help wanted Call blocker Explore trust decisions for a cooperating calling application. Guide → ](/build/call-screening) ## Bind. Vouch. Ask and verify. A device key joins a persona. People declare directed trust. Your app asks from its user's own node and verifies the returned attestation and path proof locally. Note: A positive proves a qualifying path in a committed graph. It does not prove human identity or the completeness of the server’s graph. Bind a device to your persona. Declare a directed vouch. Submit your policy, then verify the answer locally. Explore [how it works](/concepts/how-it-works), [what a proof tells you](/concepts/what-a-proof-tells-you), or the [API reference](/api). ## Choose your environment | Environment | API origin | Start here | | --- | --- | --- | | Staging | `https://trust-staging.rebellion.systems` | Development through early access | | Production | `https://trust.rebellion.systems` | Coordinate your application setup and issuer pins | ## Build on the graph. Reach out. Early access includes SDK access, your app-origin allowlist entry, and staging setup. The public SDK package is **coming soon**. [Request access](mailto:hello@rebellion.systems?subject=TrustGraph%20integration) · [What we set up](/early-access) · [Build with AI](/build-with-ai) --- URL: https://docs.trust.rebellion.systems/quickstart # Your first verified path Bind a browser device, declare a vouch, query from your persona, and verify the answer locally. Bind a browser device through AuthenSee, vouch for another persona, and verify the resulting path on staging. The steps below show excerpts from the [full module](#full-module); use its complete imports, checks, and application adapters when integrating. ## 1. Arrange access and configuration [Request early access](/early-access) for the private SDK and register your exact application origin, including its port. Public package availability is **coming soon**. Follow the installation instructions supplied during onboarding and arrange browser CORS access. The optional `/zk` entry requires the exact peer `@aztec/bb.js@5.2.0`. Configure your bundler to serve its module and WASM assets locally. Supply staging attestation JWKS through trusted application configuration, independently of query responses. ```ts excerpt="browser-module" const serverOrigin = 'https://trust-staging.rebellion.systems'; const graph = createTrustGraphSdk({ serverUrl: serverOrigin }); ``` Exchange a second consenting test participant's `graphNodeId` directly. There is no public email-address or phone-number lookup. ## 2. Implement the application adapters Your app supplies durable storage and verified device state. These methods are application code, not SDK APIs: ```ts excerpt="browser-module" export interface BrowserApp { loadKey(): Promise; saveKey(key: BrowserKey): Promise; loadPending(): Promise; savePending(pending: PendingBind): Promise; loadCompletion(): Promise; saveCompletion(completionProof: string): Promise; loadBound(): Promise; saveBound(bound: FinalizeBindResult): Promise; clearCeremony(): Promise; refreshDeviceState: FinalizeBindInput['refreshDeviceState']; } ``` Persist the non-extractable browser `CryptoKey` with IndexedDB structured cloning. Keep bind capabilities private. `savePending` must durably replace the pending record without clearing its matching key or completion capability. Save the bound result before `clearCeremony` removes those capabilities. `refreshDeviceState` must fetch and verify the own-node directory against retained chain pins, finish pagination to the advertised head, persist verified state atomically, and call `deviceState` with current lifecycle metadata. Pass `key.recovery?.rootRecordHash` as `recoveryRootRecordHash`; missing and null differ. See [identity](/concepts/identity) and the [devices API](/api/devices). **Never** Never infer approval from a completed bind or a parsed directory. A recovered device may be quarantined; the adapter must return its verified state. ## 3. Save the key and start binding Call the module's `start()` from your sign-in action. It saves the key and pending ceremony before navigating. For another device on an existing persona, call `start(recoverNodeId)` with that persona's reference. ```ts excerpt="browser-module" let key = await app.loadKey(); if (!key) { key = await generateBrowserGraphKey(); await app.saveKey(key); // Durably persist before any navigation. } const pending = await beginBind({ serverOrigin, appOrigin: window.location.origin, publicKeyJwk: key.publicKeyJwk, ...(recoverNodeId ? { recoverNodeId } : {}), }); await app.savePending(pending); window.location.assign(pending.hostedUrl); ``` The full module also refuses to overwrite a pending ceremony. Resume it or explicitly clear a definitely failed or expired ceremony before starting again. ## 4. Complete the callback Serve `/trust-graph/bind/callback` on your allowed origin. Initialize the same adapters there and call `complete()`. The SDK checks the callback against the saved origin, path and bind identifier. Retain the completion capability before removing the fragment: ```ts excerpt="browser-module" let completionProof = await app.loadCompletion(); if (window.location.hash) { const completion = parseBindCallback(pending, window.location.href); completionProof = completion.completionProof; await app.saveCompletion(completionProof); history.replaceState(null, '', window.location.pathname); } if (!completionProof) throw new Error('Waiting for the AuthenSee callback'); ``` Finalize with the saved key and both capabilities. The full module creates the signer from the local private key: ```ts excerpt="browser-module" const bound = await finalizeBind(pending, { completionProof, publicKeyJwk: key.publicKeyJwk, persistPending: value => app.savePending(value), signRoot: record => signer.signFeedRecord(record), refreshDeviceState: value => app.refreshDeviceState(value), }); await app.saveBound(bound); await app.clearCeremony(); ``` The `signRoot` adapter signs the initial device-authority record. The SDK currently calls this helper `signFeedRecord`; it is required for first-device binding when the server requires a root. Keep this callback even when your application only uses core graph operations. See [device authority](/api/devices). Exclude third-party analytics from the callback page. Show the returned `approved`, `legacy`, or `quarantined` state; continue to approval or eligible recovery before offering ordinary actions to a quarantined device. ## 5. Declare a vouch and sign the query Have the user review the target and score before calling `vouchAndVerify(targetGraphNodeId)`. The full module checks current device authority, then makes this real directed declaration on staging. The recipient does not need to accept or reciprocate it. ```ts excerpt="browser-module" const certificate = { signerGraphKeyId: graphNodeId, subjectGraphKeyId: targetGraphNodeId, trust: 80, scope: 'identity' as const, createdAt: new Date().toISOString(), nonce: crypto.randomUUID(), }; await graph.requestEdge({ ...certificate, signingGraphKeyId: graphKeyId, signature: await signBrowserEdgeCertificate(local.privateKey, certificate), }); ``` Query from your own persona's node. The authorization identifies your signing device separately: ```ts excerpt="browser-module" const query = { sourceGraphKeyId: graphNodeId, targetGraphKeyId: targetGraphNodeId, policy: { scope: 'identity' as const, minTrust: 50, maxHops: 3 }, }; const authorization = await signBrowserGraphRead(local.privateKey, { operation: 'query-reachable', graphKeyId, graphNodeId, query, }); const response = await graph.queryReachability({ ...query, authorization }); ``` A direct active vouch with score 80 satisfies the example's minimum of 50. Sign the exact query, and generate fresh read authorization for each retry. ## 6. Verify locally and use the checked result Verify the attestation with the independently pinned JWKS and bundled circuit key. Use only the verified verdict; release the WASM verifier when finished. ```ts excerpt="browser-module" const verifier = createZkVerifier(); try { const result = await verifier.verifyAttestation({ verificationKey: wotPathVerificationKey(), attestation: response.attestation, jwks: pinnedAttestationJwks, question: { sourceGraphKeyId: query.sourceGraphKeyId, targetGraphKeyId: query.targetGraphKeyId, ...query.policy, }, }); if (!result.verified) throw new Error(`Answer rejected: ${result.code}`); return result.verdict.reachable ? { label: 'Connected under your policy · proof verified', ...result.verdict } : { label: 'No path reported under your policy · signed report', ...result.verdict }; } finally { await verifier.destroy(); } ``` **Establishes** A verified positive proves a policy-satisfying path in the cited graph commitment, without revealing intermediate nodes or edges to the verifier. **Does not establish** The proof does not establish that the service published the complete graph. The service sees the graph and query. A proof does not establish graph completeness or latest state. A negative is a signed report, not a proof that no path exists. ## Full module Implement `BrowserApp`, then call `createBrowserQuickstart(app, pinnedAttestationJwks)` to obtain the three UI actions. Each excerpt above is checked against this module and typechecked in its complete context. Full module · browser integration ```ts sample="browser-module" import { beginBind, parseBindCallback, finalizeBind, createGraphSigner, createTrustGraphSdk, generateBrowserGraphKey, signBrowserEdgeCertificate, signBrowserGraphRead, type PendingBind, type FinalizeBindInput, type FinalizeBindResult, } from '@rebellion-systems/trust-graph-sdk/browser'; import { createZkVerifier, wotPathVerificationKey, } from '@rebellion-systems/trust-graph-sdk/zk'; type BrowserKey = Awaited>; // Application adapters: IndexedDB storage and verified device authority. export interface BrowserApp { loadKey(): Promise; saveKey(key: BrowserKey): Promise; loadPending(): Promise; savePending(pending: PendingBind): Promise; loadCompletion(): Promise; saveCompletion(completionProof: string): Promise; loadBound(): Promise; saveBound(bound: FinalizeBindResult): Promise; clearCeremony(): Promise; refreshDeviceState: FinalizeBindInput['refreshDeviceState']; } export function createBrowserQuickstart( app: BrowserApp, pinnedAttestationJwks: unknown, // Trusted app configuration, not response data. ) { const serverOrigin = 'https://trust-staging.rebellion.systems'; const graph = createTrustGraphSdk({ serverUrl: serverOrigin }); async function start(recoverNodeId?: string): Promise { if (await app.loadPending()) { throw new Error('Resume or explicitly clear the existing ceremony first'); } let key = await app.loadKey(); if (!key) { key = await generateBrowserGraphKey(); await app.saveKey(key); // Durably persist before any navigation. } const pending = await beginBind({ serverOrigin, appOrigin: window.location.origin, publicKeyJwk: key.publicKeyJwk, ...(recoverNodeId ? { recoverNodeId } : {}), }); await app.savePending(pending); window.location.assign(pending.hostedUrl); } async function complete(): Promise { const pending = await app.loadPending(); const key = await app.loadKey(); if (!pending || !key) throw new Error('Missing saved ceremony or device key'); let completionProof = await app.loadCompletion(); if (window.location.hash) { const completion = parseBindCallback(pending, window.location.href); completionProof = completion.completionProof; await app.saveCompletion(completionProof); history.replaceState(null, '', window.location.pathname); } if (!completionProof) throw new Error('Waiting for the AuthenSee callback'); const signer = createGraphSigner({ signatureFormat: 'ieee-p1363', randomUUID: () => crypto.randomUUID(), sign: async bytes => new Uint8Array(await crypto.subtle.sign( { name: 'ECDSA', hash: 'SHA-256' }, key.privateKey, bytes as Uint8Array, )), }); const bound = await finalizeBind(pending, { completionProof, publicKeyJwk: key.publicKeyJwk, persistPending: value => app.savePending(value), signRoot: record => signer.signFeedRecord(record), refreshDeviceState: value => app.refreshDeviceState(value), }); await app.saveBound(bound); await app.clearCeremony(); return bound; } async function vouchAndVerify(targetGraphNodeId: string) { const local = await app.loadKey(); const bound = await app.loadBound(); if (!local || !bound) throw new Error('Bind this device first'); const state = await app.refreshDeviceState(bound.key); if (state.state !== 'approved' && state.state !== 'legacy') { throw new Error('This device is not currently authorized'); } const { graphKeyId, graphNodeId } = bound.key; const certificate = { signerGraphKeyId: graphNodeId, subjectGraphKeyId: targetGraphNodeId, trust: 80, scope: 'identity' as const, createdAt: new Date().toISOString(), nonce: crypto.randomUUID(), }; await graph.requestEdge({ ...certificate, signingGraphKeyId: graphKeyId, signature: await signBrowserEdgeCertificate(local.privateKey, certificate), }); const query = { sourceGraphKeyId: graphNodeId, targetGraphKeyId: targetGraphNodeId, policy: { scope: 'identity' as const, minTrust: 50, maxHops: 3 }, }; const authorization = await signBrowserGraphRead(local.privateKey, { operation: 'query-reachable', graphKeyId, graphNodeId, query, }); const response = await graph.queryReachability({ ...query, authorization }); const verifier = createZkVerifier(); try { const result = await verifier.verifyAttestation({ verificationKey: wotPathVerificationKey(), attestation: response.attestation, jwks: pinnedAttestationJwks, question: { sourceGraphKeyId: query.sourceGraphKeyId, targetGraphKeyId: query.targetGraphKeyId, ...query.policy, }, }); if (!result.verified) throw new Error(`Answer rejected: ${result.code}`); return result.verdict.reachable ? { label: 'Connected under your policy · proof verified', ...result.verdict } : { label: 'No path reported under your policy · signed report', ...result.verdict }; } finally { await verifier.destroy(); } } return { start, complete, vouchAndVerify }; } ``` ## Resume and handle failures After a transient finalize failure, retain the pending record and completion capability and call `complete()` again. The SDK validates the returned device against the saved key. Clear capabilities for a definitely expired or failed ceremony; choose whether to reuse or replace the key through your recovery flow. Honor `retry-after` on rate limits. Treat a proving failure as unavailable, never as a positive without a proof. Do not turn verification failure into “not connected”. Because `vouchAndVerify` also writes a vouch, provide a separate query-only retry action in a production UI. Continue with [signed messages](/build/signed-messages), [trust-gated features](/build/trust-gated-features), or [attestation verification](/api/attestations). Positive answers use the UltraHonk `ultrahonk-wot-path-v2` proof scheme; negative answers remain signed reports. --- URL: https://docs.trust.rebellion.systems/security # Security and verification Build around device signatures, independent pins, verified state, and explicit evidence boundaries. TrustGraph combines device-signed operations, issuer-signed statements, and locally verified path proofs. Your application chooses what to accept and keeps the keys and state needed to make that choice. ## Keep signing authority on the device Generate a dedicated P-256 key for each device. Browser helpers create a non-extractable private `CryptoKey`; persist it with IndexedDB structured cloning. Native integrations inject a signing adapter backed by their key store. Persist a pending bind before opening AuthenSee. It contains an initiation capability. The callback carries a separate completion capability; finalize requires both and the matching local key. Remove the callback fragment after safely retaining it for completion, and exclude both capabilities from analytics, URLs you share, and logs. **Never** Never replace device-signed read authorization with an application bearer token. The signer must belong to the source persona and satisfy current lifecycle and device-authority checks. ## Sign the exact operation Use the SDK's canonical signing helpers. Sign all query members exactly as submitted, before defaults. Each target and each retry needs a fresh signed read and nonce. Writes and reads use distinct signing domains. Read authorization has a five-minute freshness window. Read replay tracking is process-local; mutation replay handling is separate. Application retry logic must create fresh read authorization instead of replaying captured requests. Canonical JSON: trust-graph-read-v1, operation, graphKeyId, fresh nonce, signedAt, exact query. Sign with the device P-256 key. ## Hold your own verification pins Supply the expected Ed25519 attestation JWKS from trusted application configuration. Use the bundled circuit verification key with the optional `/zk` verifier, which verifies proof bytes locally. Release its WASM resources with `destroy()` when finished. **Never** Never fetch verification pins from an untrusted attestation or accept signature-only positive answers. Do not use the response's unsigned convenience fields or the legacy checksum verifier for trust decisions. ## Check the meaning of the evidence A verified positive proves that a policy-satisfying path exists in the committed graph. A negative is a signed server report. A content signature plus membership certificate proves different facts and requires separate current-status and recipient-reachability checks. Use [what a proof tells you](/concepts/what-a-proof-tells-you) when designing labels and authorization decisions. **Does not establish** The proof does not establish that the service published the complete graph. The service sees the graph and queries. A path proof hides intermediate nodes and edges from the verifier; it does not prove graph completeness, latest state, human identity, message signing time, or live presence. ## Verify and retain device history A parsed device directory is not yet verified authority. Verify records, signatures, head hashes and continuity against retained pins. Continue pagination to the advertised head and persist the resulting verified state atomically. A bind can return a quarantined device; use the current verified state when deciding which actions to expose. See [identity](/concepts/identity) and [devices API](/api/devices) for approval, recovery, revocation and reset behavior. ## Keep observability separate Avoid recording device secrets, bind capabilities, and plaintext signed messages. Show failures with stable API codes where available, and keep rejected verification evidence out of a successful-connection UI. See the [API overview](/api) for errors and rate limits. For integration or security questions, [contact the team](mailto:hello@rebellion.systems?subject=TrustGraph%20integration). Positive answers use the UltraHonk `ultrahonk-wot-path-v2` proof scheme; negative answers remain signed reports. ## Omnibus app layer (not core TrustGraph) These are Omnibus’s own services, built on top of TrustGraph. They are not part of the core TrustGraph API. If you want to build on them, reach out: hello@rebellion.systems. --- URL: https://docs.trust.rebellion.systems/ecosystem/omnibus/app-layer # Omnibus app-layer services Omnibus’s own services, separate from the core TrustGraph API. Reach out to discuss building on them. **Note** **Omnibus app layer.** These are Omnibus’s own services, built on top of TrustGraph. They are not part of the core TrustGraph API. If you want to build on them, [reach out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration). Omnibus is [an app built on TrustGraph](/ecosystem/omnibus). This page collects its existing encryption-key and profile contracts, separately from core identity, device approval, vouches and reachability. Discuss access and configuration with the team before integrating. ## Feed encryption-key directory Omnibus uses device encryption-key advertisements for encrypted delivery. Core device approval and quarantine are described in [device authority](/api/devices). `POST /v1/devices/log` uses signed read `device-log-read` over `{graphNodeId, afterSequence?, limit?}`. The `keys` response field contains at most the current and previous encryption-key advertisement per device. Core authority records and signing keys are described in the linked device reference. Parsing a directory does not verify its signatures or continuity; use the SDK verification helpers with retained trusted pins. ### Advertise an encryption key `POST /v1/devices/key/advertise` takes `{action, signature}`. The signed `device-key-advertise` action contains `graphKeyId`, `ad`, `adSignature`, `nonce` and `signedAt`. Sign the advertisement with `v: "omnibus-feed-key-v1"`, then sign the outer action in the normal action domain. Signatures are canonical base64, low-S P-256 DER. | Advertisement field | Rule | | --- | --- | | `graphNodeId`, `epoch`, `deviceGraphKeyId` | Approved device in the current node epoch | | `keySequence` | Increasing positive integer | | `encryptionPublicKey` | Canonical base64 X25519 public key, 32 bytes | | `encryptionKeyId` | SHA-256 of the protocol’s length-framed domain and public key | | `validFrom`, `validUntil` | UTC times exactly eight days apart | | `issuedAt` | UTC timestamp | Use the SDK helpers for key IDs and record signatures. Success returns `{adHash, keySequence}`. Equal sequence and equal unsigned hash is idempotent; conflicting or lower sequence returns `FEED_KEY_CONFLICT` (409). The daily rolling limit is two advertisements per device. Responses use `Cache-Control: no-store`. Common errors include `FEED_FORBIDDEN` (403), `FEED_UNAUTHORIZED` (401), `FEED_NONCE_REPLAY` (409), `FEED_RATE_LIMITED` (429), `FEED_SERVER_UNAVAILABLE` (503) and `DEVICE_LOG_NOT_APPROVED` (403). ## Profile wrap secret Omnibus’s profile vault uses an owner-authorized wrap-secret read. This service does not upload a display name or a profile. Client applications are responsible for local encryption, key lifetime, sign-out cleanup and backup behavior. ### Capability and authorization Read `GET /readyz` and check `profileVault: {enabled: true, wrapSecretFormat: "profile-wrap-secret-v1"}`. A missing capability on an older server means unsupported. Send `POST /v1/profile/wrap-secret/read` with `{graphNodeId, authorization}`. Use the signed-read envelope to sign operation `read-profile-wrap-secret` over the exact query `{graphNodeId}`. The signing graph key must belong to the node and, where device authority exists, be approved. Content-signing, expired, revoked and quarantined keys cannot authorize this read; bearer credentials alone do not authorize it. Every retry needs fresh authorization and a nonce. ### Response ```json role="response" { "graphNodeId": "", "wrapSecret": "", "format": "profile-wrap-secret-v1" } ``` Validate response ownership and format. `wrapSecret` is 32 bytes encoded as 43 canonical unpadded base64url characters. Honor `Cache-Control: no-store`. **Never** Never log or persist the wrapping contribution, or retain it across sign-out. Use it only in memory as a contribution to a locally derived key, not directly as the encryption key. **Does not establish** The wrapping contribution is readable by the service. The service does not enforce a client’s encryption, sign-out cleanup or backup policy. ### SDK helpers `graph.profileVaultCapabilities()` reads readiness without bearer credentials and returns `null` for an absent or explicitly disabled capability. Malformed capabilities, HTTP errors and transport failures remain errors. `graph.readProfileWrapSecret({graphNodeId, authorization})` sends the signed request without an implicit capability probe. The caller supplies authorization, and custom HTTP adapters must honor `no-store`. ## Nearby discovery Nearby uses mutual relationships and each participant’s signed audience to decide eligibility. It is not a precise-location directory. This summary describes an Omnibus service; use core [reachability](/api/query) for a TrustGraph connection check. ## Other app experiences - **Feeds and posts:** encrypted sharing through Omnibus’s Feed experience. - **Files:** encrypted attachments managed by the app layer. - **Audience policy:** the app layer controls who can receive shared content. - **Messaging:** app conversations, separate from [portable signed messages](/build/signed-messages) composed from core primitives. Availability depends on the app release and service configuration. To discuss an integration, [reach out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration). --- URL: https://docs.trust.rebellion.systems/ecosystem/omnibus # Omnibus, built on TrustGraph One app using core TrustGraph, with its own separate app layer. **Note** **Omnibus app layer.** These are Omnibus’s own services, built on top of TrustGraph. They are not part of the core TrustGraph API. If you want to build on them, [reach out](mailto:hello@rebellion.systems?subject=TrustGraph%20integration). ## What Omnibus uses from TrustGraph Omnibus binds a device key to a persona, lets people declare vouches, and uses reachability to check connections through the graph. These are core TrustGraph capabilities that other applications can integrate directly. - [Bind a device](/api/bind) to authorize actions for a persona. - [Declare a vouch](/concepts/vouches) to express a directed relationship. - [Query reachability](/concepts/reachability-and-proofs) and verify the evidence locally. ## Omnibus’s own services Omnibus adds Feed, encrypted posts and files, Nearby, profile vault and messaging alongside its use of the core platform. See [Omnibus app-layer services](/ecosystem/omnibus/app-layer) for the separately scoped contracts and how to discuss access. ## Choices made by the app Omnibus adds its own interface and local storage behavior. Its reminders and “something new” indicator are app experiences, not platform guarantees. Availability depends on the app release. Other clients define their own interface, local storage, sign-out and backup behavior. To build your own integration with the core platform, start with [early access](/early-access) and the [quickstart](/quickstart).