Provenance (C2PA + PROV-O)
The Provenance API is two surfaces sharing one mount.
C2PA Content Credentials lets the deployer issue, verify, and
revoke signed manifests against AI-generated outputs —
binding each output to its originating trace, the model that
produced it, and the operator's signing key. PROV-O export
returns the W3C provenance ontology serialisation for any trace,
suitable for handing to an evidence pipeline expecting RDF rather
than custom JSON. Tested against API version v1. Mounted at
/api/v1/provenance. JWT auth on every endpoint; C2PA writes
gated by c2paContentCredentials (Enterprise+); PROV-O reads
gated by provOExport (Governance+).
Why two surfaces
C2PA and PROV-O answer different questions:
| Question | Standard | Adjudon endpoint |
|---|---|---|
| "Was this image / document / AI output produced under a verifiable provenance chain?" | C2PA (Coalition for Content Provenance and Authenticity) | /c2pa/manifests/* |
| "What does this trace's provenance look like in the W3C-standard PROV-O graph format?" | PROV-O (W3C Provenance Ontology) | /provo/* |
C2PA targets media-asset signing — the manifest binds to a specific content hash and travels with the asset. PROV-O targets graph-format export for evidence pipelines that already consume RDF/Turtle. The two are complementary, not redundant.
Endpoints
GET /api/v1/provenance/settings
PUT /api/v1/provenance/settings
POST /api/v1/provenance/c2pa/manifests
GET /api/v1/provenance/c2pa/manifests
GET /api/v1/provenance/c2pa/manifests/:id
POST /api/v1/provenance/c2pa/manifests/:id/revoke
GET /api/v1/provenance/c2pa/manifests/:id/verify
GET /api/v1/provenance/provo/trace/:traceId
GET /api/v1/provenance/provo/recent
The C2PAManifest object
| Field | Type | Description |
|---|---|---|
organizationId | string | Owning org |
traceId | string | The DecisionTrace this manifest provenances |
label | string | Org/trace-unique manifest label |
contentHash | string | SHA-256 hex of the asset / content this manifest covers |
signatureAlg | enum | ecdsa-p256-jws (default; RFC 7515 JWS Compact, ES256); hmac-sha256 legacy verify-only |
signature | string | JWS compact (3 dot-separated base64url segments) for ECDSA; hex MAC for legacy |
signerKid | string | null | Key ID at issue time (SHA-256 of SubjectPublicKeyInfo) |
signerMode | enum | null | prod-cert, prod-jwk, ephemeral-jwk, legacy-hmac |
issuer | string | Org slug or DID identifying the signer |
assertions | object | { actions[], ingredients[], creativeWork, aiTrainingMining } per C2PA spec |
issuedAt | string (ISO 8601) | Signing timestamp |
revokedAt | string (ISO 8601) | Set on revoke; reads include the field but the verify endpoint refuses revoked manifests |
revocationReason | string | null | Curated vocabulary; never raw error text per Cardinal Rule #4 |
Adjudon does not embed binary JUMBF (JPEG Universal Metadata Box Format) inside this record. The schema records the signed claim only; the SDK packages a C2PA-compliant container at output emission time using the manifest record as the truth source. Full JUMBF embedding remains on the roadmap.
Provenance settings
GET /api/v1/provenance/settings
PUT /api/v1/provenance/settings
Per-org provenance configuration. The settings document
controls how manifests are signed (which signing mode is
active, which key bundle is in rotation) and what assertion
shape the org wants on default issue (e.g. always-attach
aiTrainingMining opt-out flags for orgs that publish a public
no-train policy).
Read access is open to any authenticated user so the dashboard
can render the configuration page; write access requires the
c2paContentCredentials gate. The settings include the active
signerMode (one of prod-cert, prod-jwk, ephemeral-jwk),
the public key descriptor for verifiers, and the default
assertion templates the org applies on issue.
curl https://api.adjudon.com/api/v1/provenance/settings \
-H "Authorization: Bearer $ADJUDON_JWT"
Issue a manifest
POST /api/v1/provenance/c2pa/manifests
| Body field | Required | Description |
|---|---|---|
traceId | yes | The trace this manifest provenances |
label | yes | Globally unique within (org, trace) |
contentHash | yes | SHA-256 hex of the content the manifest binds to |
assertions | no | C2PA assertion bundle (actions / ingredients / creativeWork / aiTrainingMining) |
curl -X POST https://api.adjudon.com/api/v1/provenance/c2pa/manifests \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{
"traceId": "65b1f2c4...",
"label": "marketing-banner-2026-05",
"contentHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"assertions": {
"actions": [{ "action": "c2pa.created", "when": "2026-05-06T14:02:11Z" }],
"creativeWork": { "author": "AI System v3.2 (org slug)" }
}
}'
The handler signs with the org's active signing key (rotated via
the C2PA KMS API), writes the
manifest, and returns it. Errors: 400 VALIDATION_ERROR
(missing fields), 401, 403 UPGRADE_REQUIRED (plan), 500.
Verify a manifest
GET /api/v1/provenance/c2pa/manifests/:id/verify
Returns one of seven curated reason codes:
| Reason | Meaning |
|---|---|
verified | Signature valid; manifest active; issuer key resolvable |
revoked | Manifest exists but was revoked |
signature-invalid | Cryptographic verification failed |
key-not-found | Issuer key unresolvable (rotated and not retained) |
algorithm-unsupported | Signature algorithm outside the accepted set |
content-hash-mismatch | Caller passed a hash that does not match contentHash |
manifest-malformed | Schema-invalid manifest (defensive case; should not occur for in-platform writes) |
The reason vocabulary is bounded — raw OpenSSL or library
error messages never flow back to callers. This is a Cardinal Rule
#4 enforcement specific to the verify path; the underlying error
is logged via safeLog for operator triage but stripped from the
response.
A typical verify flow on the consumer side:
- Receive an asset (image, document, AI-generated text) with an embedded C2PA manifest reference.
- Extract the manifest
idand the asset'scontentHash. GET /c2pa/manifests/:id/verify?contentHash=<hex>— the endpoint checks signature validity, key resolvability, manifest active-state, and the supplied content-hash against the stored one in a single call.- Branch on the curated
reasonfield; the consumer never needs to re-implement signature verification.
This pattern is what makes the verify endpoint useful to downstream tooling (CMS systems, social-media compliance filters, archival pipelines) that does not want to ship a full C2PA verifier.
Revoke a manifest
POST /api/v1/provenance/c2pa/manifests/:id/revoke
Body: { reason }. Stamps revokedAt = now and persists the
curated reason. The verify endpoint thereafter returns
revoked for this manifest. Revocation is not reversible
— a revoked manifest cannot be un-revoked; if the asset's
provenance still holds, issue a new manifest.
PROV-O export
GET /api/v1/provenance/provo/trace/:traceId
GET /api/v1/provenance/provo/recent
Returns the W3C PROV-O graph for one trace or the most recent
batch. The serialisation is JSON-LD by default with PROV-O
namespace (http://www.w3.org/ns/prov#); Accept: text/turtle
returns Turtle, Accept: application/n-triples returns N-Triples
when requested. Trace payloads are PII-scrubbed before
serialisation, same as every other read.
The graph nodes correspond to the standard PROV triple of
prov:Entity, prov:Activity, prov:Agent: the trace is the
Entity, the agent's invocation is the Activity, the agent
identity is the Agent. Cross-links to upstream traces (when
available via inputContext.parentTraceId) populate
prov:wasDerivedFrom edges.
Common gotchas
- Two plan gates on one resource. C2PA writes require
c2paContentCredentials(Enterprise+); PROV-O reads requireprovOExport(Governance+). The/settingsGET is open to any authenticated user;/settingsPUT requires the C2PA gate. - No JUMBF embedding today. The schema records the signed claim; binary JUMBF embedding is on the roadmap. The Adjudon SDK packages the manifest into a C2PA-compliant container at output time using the record as truth.
hmac-sha256is verify-only legacy. The handler refuses to issue HMAC manifests; onlyecdsa-p256-jwsis accepted on the issue path. HMAC manifests from prior installations remain verifiable but cannot be re-issued.- Revocation is one-way. A revoked manifest cannot be un-revoked; issue a new manifest if the original provenance still holds.
- PROV-O serialisation negotiates by
Acceptheader. JSON-LD is default; Turtle and N-Triples available on request. - Idempotency.
Idempotency-Keymiddleware is wired only onPOST /traces; the(organizationId, label, traceId)uniqueness on manifest creation defends against duplicate POST. PROV-O endpoints are reads; nothing to replay.
Why this matters for AI Act readiness
EU AI Act Article 50 imposes transparency obligations on AI-generated synthetic content — deployers must label output as AI-generated in a "machine-readable" format. C2PA Content Credentials are the format the European Commission has explicitly cited as a candidate for satisfying that obligation. Adjudon's manifest issuance binds the trace, the signing key, and the content hash so a regulator-side verifier can resolve "this AI output was produced by which agent under which provenance chain" in one call. PROV-O export is the same information shaped for evidence pipelines that already speak RDF rather than C2PA's binary container format.
See also
- C2PA KMS API — the signing-key rotation surface this resource consumes
- Hash Chain — the tamper-evident anchor for trace records that get C2PA-signed
- Traces API — the source-of-truth
for the
DecisionTracerecords every manifest binds to - Plans & Features —
the
c2paContentCredentialsandprovOExportfeature gates - Error Codes — the broader error taxonomy