Content provenance (was this AI-generated?)
What you will achieve
Section titled “What you will achieve”Check a file for provider provenance signals and read a structured verdict: what was detected, whether it validated, and which model and issuer the manifest names.
When and why you need this
Section titled “When and why you need this”Moderation asks is this content harmful. Provenance asks a different question: does this file carry a signal saying a model made it. Use it when accepting user uploads, labelling media, or building an audit trail.
import { checkProvenance } from '@combycode/llm-sdk';
const res = await checkProvenance({ file: bytes, filename: 'upload.png', mimeType: 'image/png',});
for (const s of res.signals) { console.log(s.kind, s.detected, s.validationState, s.issuer, s.model);}Two signal kinds are normalised into one list: C2PA (a signed Content Credentials manifest carrying issuer / model / timestamp) and SynthID (Google’s watermark; for audio it is the only one available).
Read the result carefully
Section titled “Read the result carefully”detected and trusted are different questions, and the difference is the whole point. A C2PA
manifest is metadata: anyone can attach one, and it can be stripped, forged, or invalidated by a
re-encode. detected means a manifest was found. trusted means its signature validated against a
known issuer.
valid is not trusted, and you meet this immediately. An image generated by gpt-image-1 and
checked minutes later returns detected: true, validationState: 'valid',
issuer: "OpenAI OpCo, LLC", model: 'gpt-image' — and trusted: false, because the issuer is
not on the checker’s trust list. For most uses the signal you actually want is
detected && validationState === 'valid', not trusted alone.
Nothing detected says nothing at all. Signals are routinely lost to a screenshot, a crop, or a re-encode. Absence is absence of evidence, not evidence of human authorship — no policy should be built as though it were.
Your options
Section titled “Your options”| Option | Type | Required | Description |
|---|---|---|---|
file | Uint8Array | Yes | The bytes to check. |
filename | string | Yes | With an extension — the API uses it to pick a decoder. |
mimeType | string | Yes | e.g. image/png, audio/wav. |
apiKey | string | No | Falls back to engine.apiKeys.openai. |
engine | EngineHandle | No | Share an existing engine instance. |
ProvenanceCheckResult carries detected, trusted, and signals[]
(kind, detected, validationState, issuer, model, generatedAt).
Compare the SDKs
Section titled “Compare the SDKs”OpenAI is the only tracked SDK that ships a provenance endpoint at all; Anthropic, Google, xAI and
OpenRouter have none. The call itself is a multipart upload to /v1/content_provenance_checks, and
the raw response splits results by type — checkProvenance() normalises them into one signals
list and separates detected from trusted so the weaker claim cannot be mistaken for the stronger.
Gotchas and next steps
Section titled “Gotchas and next steps”The call is free, and still recorded. No tokens are billed, but an onCostEntry is emitted with
an honest zero so the ledger can distinguish “free” from “never happened”.
Non-OpenAI providers throw immediately, naming the provider — there is no silent fallback, because a provenance check that quietly did nothing is worse than an error.
Next steps:
- Moderation — classifying content as harmful, a different question
- Image generation — producing the media whose provenance this reads