Skip to main content

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:

QuestionStandardAdjudon 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

FieldTypeDescription
organizationIdstringOwning org
traceIdstringThe DecisionTrace this manifest provenances
labelstringOrg/trace-unique manifest label
contentHashstringSHA-256 hex of the asset / content this manifest covers
signatureAlgenumecdsa-p256-jws (default; RFC 7515 JWS Compact, ES256); hmac-sha256 legacy verify-only
signaturestringJWS compact (3 dot-separated base64url segments) for ECDSA; hex MAC for legacy
signerKidstring | nullKey ID at issue time (SHA-256 of SubjectPublicKeyInfo)
signerModeenum | nullprod-cert, prod-jwk, ephemeral-jwk, legacy-hmac
issuerstringOrg slug or DID identifying the signer
assertionsobject{ actions[], ingredients[], creativeWork, aiTrainingMining } per C2PA spec
issuedAtstring (ISO 8601)Signing timestamp
revokedAtstring (ISO 8601)Set on revoke; reads include the field but the verify endpoint refuses revoked manifests
revocationReasonstring | nullCurated 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 — read settings
curl https://api.adjudon.com/api/v1/provenance/settings \
-H "Authorization: Bearer $ADJUDON_JWT"

Issue a manifest

POST /api/v1/provenance/c2pa/manifests
Body fieldRequiredDescription
traceIdyesThe trace this manifest provenances
labelyesGlobally unique within (org, trace)
contentHashyesSHA-256 hex of the content the manifest binds to
assertionsnoC2PA assertion bundle (actions / ingredients / creativeWork / aiTrainingMining)
curl — issue a manifest for a generated image
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:

ReasonMeaning
verifiedSignature valid; manifest active; issuer key resolvable
revokedManifest exists but was revoked
signature-invalidCryptographic verification failed
key-not-foundIssuer key unresolvable (rotated and not retained)
algorithm-unsupportedSignature algorithm outside the accepted set
content-hash-mismatchCaller passed a hash that does not match contentHash
manifest-malformedSchema-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:

  1. Receive an asset (image, document, AI-generated text) with an embedded C2PA manifest reference.
  2. Extract the manifest id and the asset's contentHash.
  3. 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.
  4. Branch on the curated reason field; 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 require provOExport (Governance+). The /settings GET is open to any authenticated user; /settings PUT 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-sha256 is verify-only legacy. The handler refuses to issue HMAC manifests; only ecdsa-p256-jws is 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 Accept header. JSON-LD is default; Turtle and N-Triples available on request.
  • Idempotency. Idempotency-Key middleware is wired only on POST /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 DecisionTrace records every manifest binds to
  • Plans & Features — the c2paContentCredentials and provOExport feature gates
  • Error Codes — the broader error taxonomy