{"id":"provenance-and-compliance","title":"Provenance and compliance","description":"Signed Merkle-chained transcripts of every evidence operation. Fetch as JSON or PROV-O JSON-LD, verify server-side or offline in the SDK, and present as a chain of custody for legal proceedings. HMAC-SHA256 default with ML-DSA-65 (post-quantum) opt-in.","lastUpdated":"2026-05-08","sections":[{"id":"evidence-provenance-get","title":"GET /v1/evidence/{evidence_id}/provenance · Get evidence provenance transcript","content":"Route: GET /v1/evidence/{evidence_id}/provenance\nHost: https://api.knogin.com\nAuth: Bearer token\nAudience: External integrators\nStability: Stable\n\nReturns the full Merkle-chained provenance transcript for an evidence record as JSON (default) or PROV-O JSON-LD. HMAC-SHA256 by default; ML-DSA-65 (post-quantum) opt-in via ?algorithm=ml-dsa-65.\n\nIntegration notes:\n- Requires the `argus:provenance:read` scope on a bearer token.\n- Query parameter `format` accepts `json` (default) or `jsonld`. The `jsonld` variant emits a PROV-O document with `@context: https://www.w3.org/ns/prov` and standard term mapping (`prov:Activity`, `prov:wasGeneratedBy`, `prov:wasDerivedFrom`, `prov:Entity`, `prov:Agent`).\n- Query parameter `algorithm` accepts `hmac-sha256` (default, tenant-scoped HKDF-derived secret) or `ml-dsa-65` (post-quantum, via the existing pqc_adapter). The chosen algorithm is signed into both the per-record signatures and the Merkle root signature.\n- Each record carries `parent_id` and `parent_hash` so the transcript forms an append-only chain per `evidence_id`. The Merkle root binds the full ordered set of records and is signed independently.\n- Standard G2 trace headers are emitted; `trace_id` for each record is also captured inside the transcript for end-to-end correlation across the audit log.\n- Tenant scoped: cross-tenant lookups return 404 (never 403). SecrecyLevel of the underlying evidence applies: operators below the evidence's secrecy level cannot read its provenance.","codeExamples":[{"language":"bash","code":"curl \"https://api.knogin.com/v1/evidence/ev_abc123/provenance?format=jsonld&algorithm=ml-dsa-65\" \\\n  -H \"Authorization: Bearer $TOKEN\"","description":"Request example"},{"language":"json","code":"{\n  \"evidence_id\": \"ev_abc123\",\n  \"tenant_id\": \"tnt_123\",\n  \"records\": [\n    {\n      \"id\": \"01HFXY...\",\n      \"operation\": \"evidence.create\",\n      \"actor_id\": \"user_42\",\n      \"actor_kind\": \"user\",\n      \"content_hash\": \"sha256:b94d...\",\n      \"parent_hash\": null,\n      \"signature\": \"<hex>\",\n      \"signature_alg\": \"hmac-sha256\",\n      \"signed_at\": \"2026-05-08T10:00:00Z\",\n      \"trace_id\": \"0af7651916cd43dd8448eb211c80319c\",\n      \"job_id\": null\n    }\n  ],\n  \"merkle_root\": \"sha256:...\",\n  \"root_signature\": \"<hex>\",\n  \"root_signature_alg\": \"hmac-sha256\",\n  \"format\": \"json\",\n  \"version\": \"2026.4\"\n}","description":"Response example"}]},{"id":"evidence-provenance-verify","title":"POST /v1/evidence/{evidence_id}/provenance/verify · Verify a provenance transcript","content":"Route: POST /v1/evidence/{evidence_id}/provenance/verify\nHost: https://api.knogin.com\nAuth: Bearer token\nAudience: External integrators\nStability: Stable\n\nServer-side verification of a provenance transcript: recomputes per-record content hashes, verifies signatures, and confirms the Merkle root. Returns broken_links if any record fails the signature or content-hash check.\n\nIntegration notes:\n- Requires the `argus:provenance:read` scope on a bearer token.\n- Request body: `{ \"transcript\": <transcript object> }` where the transcript matches the shape returned by `GET /v1/evidence/{evidence_id}/provenance`.\n- Server-side verification is the canonical path. SDKs additionally ship an offline verify helper that needs no network round-trip: `evidence.verifyProvenanceLocal(transcript)` (TypeScript) and `evidence.verify_provenance_local(transcript)` (Python). The offline helper requires the published verify keys from `GET /v1/.well-known/provenance-keys/{algorithm}` for `ml-dsa-65`; HMAC verification stays server-side because the secret is tenant-scoped.\n- Returns 200 with `{ valid: true, checked_records, merkle_root_verified: true, broken_links: [] }` when the transcript is intact, or 200 with `valid: false`, `merkle_root_verified: false`, and a populated `broken_links` array when any record fails. The endpoint never returns 4xx for a tampered-but-well-formed transcript: 400 is reserved for malformed input.\n- Tenant scoped: cross-tenant verify returns 404. The audit entry includes `metadata = { record_count, format, algorithm, valid }`.","codeExamples":[{"language":"bash","code":"curl -X POST https://api.knogin.com/v1/evidence/ev_abc123/provenance/verify \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"transcript\": { \"...\": \"<full transcript object from GET /provenance>\" }\n  }'","description":"Request example"},{"language":"json","code":"{\n  \"valid\": true,\n  \"evidence_id\": \"ev_abc123\",\n  \"broken_links\": [],\n  \"checked_records\": 5,\n  \"merkle_root_verified\": true\n}","description":"Response example"}]},{"id":"provenance-verify-keys","title":"GET /.well-known/provenance-keys/{algorithm} · Fetch a provenance verify key","content":"Route: GET /.well-known/provenance-keys/{algorithm}\nHost: https://api.knogin.com\nAuth: Public endpoint\nAudience: External integrators\nStability: Stable\n\nReturns the public verify key for the given signature algorithm so that integrators can verify provenance transcripts offline. HMAC-SHA256 returns 404 because its secret is tenant-scoped; ML-DSA-65 returns the public verify key.\n\nIntegration notes:\n- Public endpoint: no bearer token required. The endpoint exposes only public key material; no tenant or signer identity is leaked.\n- Path parameter `algorithm` accepts `hmac-sha256` (always returns 404 because HMAC keys are tenant-scoped HKDF-derived secrets and cannot be exposed publicly) or `ml-dsa-65` (returns the public verify key that pairs with the platform-level ML-DSA-65 signer).\n- The response shape mirrors the well-known JWKS pattern: a `keys` array with `{ kid, alg, public_key }` entries. Cache responses with the same policy as JWKS and refresh on unknown `kid` values.\n- Use this endpoint only for offline verification. Server-side `POST /v1/evidence/{evidence_id}/provenance/verify` remains the canonical authoritative check.","codeExamples":[{"language":"bash","code":"curl https://api.knogin.com/v1/.well-known/provenance-keys/ml-dsa-65","description":"Request example"},{"language":"json","code":"{\n  \"keys\": [\n    {\n      \"kid\": \"prov-2026-05-mldsa\",\n      \"alg\": \"ml-dsa-65\",\n      \"public_key\": \"<base64 ML-DSA-65 public key>\"\n    }\n  ]\n}","description":"Response example"}]}],"relatedTopics":["identity-app-registration","oauth-service-tokens","jwks-token-verification","event-delivery-webhooks","graphql-transport-contract","observability-and-tracing","async-jobs","sandbox-environment","discovery-and-governance"],"markdown":"# Provenance and compliance\n\nSigned Merkle-chained transcripts of every evidence operation. Fetch as JSON or PROV-O JSON-LD, verify server-side or offline in the SDK, and present as a chain of custody for legal proceedings. HMAC-SHA256 default with ML-DSA-65 (post-quantum) opt-in.\n\n## GET /v1/evidence/{evidence_id}/provenance · Get evidence provenance transcript\n\nRoute: GET /v1/evidence/{evidence_id}/provenance\nHost: https://api.knogin.com\nAuth: Bearer token\nAudience: External integrators\nStability: Stable\n\nReturns the full Merkle-chained provenance transcript for an evidence record as JSON (default) or PROV-O JSON-LD. HMAC-SHA256 by default; ML-DSA-65 (post-quantum) opt-in via ?algorithm=ml-dsa-65.\n\nIntegration notes:\n- Requires the `argus:provenance:read` scope on a bearer token.\n- Query parameter `format` accepts `json` (default) or `jsonld`. The `jsonld` variant emits a PROV-O document with `@context: https://www.w3.org/ns/prov` and standard term mapping (`prov:Activity`, `prov:wasGeneratedBy`, `prov:wasDerivedFrom`, `prov:Entity`, `prov:Agent`).\n- Query parameter `algorithm` accepts `hmac-sha256` (default, tenant-scoped HKDF-derived secret) or `ml-dsa-65` (post-quantum, via the existing pqc_adapter). The chosen algorithm is signed into both the per-record signatures and the Merkle root signature.\n- Each record carries `parent_id` and `parent_hash` so the transcript forms an append-only chain per `evidence_id`. The Merkle root binds the full ordered set of records and is signed independently.\n- Standard G2 trace headers are emitted; `trace_id` for each record is also captured inside the transcript for end-to-end correlation across the audit log.\n- Tenant scoped: cross-tenant lookups return 404 (never 403). SecrecyLevel of the underlying evidence applies: operators below the evidence's secrecy level cannot read its provenance.\n\nRequest example\n\n```bash\ncurl \"https://api.knogin.com/v1/evidence/ev_abc123/provenance?format=jsonld&algorithm=ml-dsa-65\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\n\nResponse example\n\n```json\n{\n  \"evidence_id\": \"ev_abc123\",\n  \"tenant_id\": \"tnt_123\",\n  \"records\": [\n    {\n      \"id\": \"01HFXY...\",\n      \"operation\": \"evidence.create\",\n      \"actor_id\": \"user_42\",\n      \"actor_kind\": \"user\",\n      \"content_hash\": \"sha256:b94d...\",\n      \"parent_hash\": null,\n      \"signature\": \"<hex>\",\n      \"signature_alg\": \"hmac-sha256\",\n      \"signed_at\": \"2026-05-08T10:00:00Z\",\n      \"trace_id\": \"0af7651916cd43dd8448eb211c80319c\",\n      \"job_id\": null\n    }\n  ],\n  \"merkle_root\": \"sha256:...\",\n  \"root_signature\": \"<hex>\",\n  \"root_signature_alg\": \"hmac-sha256\",\n  \"format\": \"json\",\n  \"version\": \"2026.4\"\n}\n```\n\n## POST /v1/evidence/{evidence_id}/provenance/verify · Verify a provenance transcript\n\nRoute: POST /v1/evidence/{evidence_id}/provenance/verify\nHost: https://api.knogin.com\nAuth: Bearer token\nAudience: External integrators\nStability: Stable\n\nServer-side verification of a provenance transcript: recomputes per-record content hashes, verifies signatures, and confirms the Merkle root. Returns broken_links if any record fails the signature or content-hash check.\n\nIntegration notes:\n- Requires the `argus:provenance:read` scope on a bearer token.\n- Request body: `{ \"transcript\": <transcript object> }` where the transcript matches the shape returned by `GET /v1/evidence/{evidence_id}/provenance`.\n- Server-side verification is the canonical path. SDKs additionally ship an offline verify helper that needs no network round-trip: `evidence.verifyProvenanceLocal(transcript)` (TypeScript) and `evidence.verify_provenance_local(transcript)` (Python). The offline helper requires the published verify keys from `GET /v1/.well-known/provenance-keys/{algorithm}` for `ml-dsa-65`; HMAC verification stays server-side because the secret is tenant-scoped.\n- Returns 200 with `{ valid: true, checked_records, merkle_root_verified: true, broken_links: [] }` when the transcript is intact, or 200 with `valid: false`, `merkle_root_verified: false`, and a populated `broken_links` array when any record fails. The endpoint never returns 4xx for a tampered-but-well-formed transcript: 400 is reserved for malformed input.\n- Tenant scoped: cross-tenant verify returns 404. The audit entry includes `metadata = { record_count, format, algorithm, valid }`.\n\nRequest example\n\n```bash\ncurl -X POST https://api.knogin.com/v1/evidence/ev_abc123/provenance/verify \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"transcript\": { \"...\": \"<full transcript object from GET /provenance>\" }\n  }'\n```\n\nResponse example\n\n```json\n{\n  \"valid\": true,\n  \"evidence_id\": \"ev_abc123\",\n  \"broken_links\": [],\n  \"checked_records\": 5,\n  \"merkle_root_verified\": true\n}\n```\n\n## GET /.well-known/provenance-keys/{algorithm} · Fetch a provenance verify key\n\nRoute: GET /.well-known/provenance-keys/{algorithm}\nHost: https://api.knogin.com\nAuth: Public endpoint\nAudience: External integrators\nStability: Stable\n\nReturns the public verify key for the given signature algorithm so that integrators can verify provenance transcripts offline. HMAC-SHA256 returns 404 because its secret is tenant-scoped; ML-DSA-65 returns the public verify key.\n\nIntegration notes:\n- Public endpoint: no bearer token required. The endpoint exposes only public key material; no tenant or signer identity is leaked.\n- Path parameter `algorithm` accepts `hmac-sha256` (always returns 404 because HMAC keys are tenant-scoped HKDF-derived secrets and cannot be exposed publicly) or `ml-dsa-65` (returns the public verify key that pairs with the platform-level ML-DSA-65 signer).\n- The response shape mirrors the well-known JWKS pattern: a `keys` array with `{ kid, alg, public_key }` entries. Cache responses with the same policy as JWKS and refresh on unknown `kid` values.\n- Use this endpoint only for offline verification. Server-side `POST /v1/evidence/{evidence_id}/provenance/verify` remains the canonical authoritative check.\n\nRequest example\n\n```bash\ncurl https://api.knogin.com/v1/.well-known/provenance-keys/ml-dsa-65\n```\n\nResponse example\n\n```json\n{\n  \"keys\": [\n    {\n      \"kid\": \"prov-2026-05-mldsa\",\n      \"alg\": \"ml-dsa-65\",\n      \"public_key\": \"<base64 ML-DSA-65 public key>\"\n    }\n  ]\n}\n```\n\n## Related Topics\n\n- [Identity and app registration](https://knogin.com/api/docs/identity-app-registration)\n- [OAuth and service tokens](https://knogin.com/api/docs/oauth-service-tokens)\n- [JWKS and token verification](https://knogin.com/api/docs/jwks-token-verification)\n- [Event delivery and webhooks](https://knogin.com/api/docs/event-delivery-webhooks)\n- [GraphQL transport contract](https://knogin.com/api/docs/graphql-transport-contract)\n- [Observability and tracing](https://knogin.com/api/docs/observability-and-tracing)\n- [Async jobs](https://knogin.com/api/docs/async-jobs)\n- [Sandbox environment](https://knogin.com/api/docs/sandbox-environment)\n- [Discovery and governance](https://knogin.com/api/docs/discovery-and-governance)","metadata":{"readingTime":"6 min","difficulty":"intermediate","tags":["integrations","api","oauth","webhooks","graphql"]}}