Verify an asset
Use AssetSdk to verify finished files (images, audio clips, static MP4 video, PDFs). Pass the file bytes — or a RangeSource for a large file streamed from disk — to verify; it returns every credential for the asset as an array of { manifest, binding } entries: one for an embedded manifest, possibly several when recovered from the registry, and an empty array when the asset is unsigned. Each entry's binding records how the manifest was located — null when it was embedded, { kind: 'hard', hash } when recovered by exact content hash, or { kind: 'soft', alg, value } when recovered by a durable soft binding (e.g. an ISCC code).
AssetSdk.create({ trustAnchors, registry?, trustC2paAnchors? }) takes the PEM trust anchors and an optional registry URL (e.g. https://api.trylimbo.com); omit the registry for embedded-only verification with no recovery. The official C2PA conformance trust list is trusted by default; trustC2paAnchors: false restricts trust to your anchors. verify(input, binders) returns { manifest, binding }[]: input is a Uint8Array (in-memory bytes) or a Node RangeSource (a large file streamed without loading it whole — see File streaming), and binders is the array of soft-binding strategies to try when neither an embedded manifest nor an exact-hash copy is found. Pass [] for embedded + exact-hash recovery only. Soft recovery is explicit: to recover a re-encoded image, pass [Binder.iscc()]; for a watermark or an out-of-band video ISCC, pass Binder.watermark(...) or Binder.held(...). See Recovery.
Full example
import { AssetSdk, DEV_CA } from '@limboai/verifox-sdk-verifier';
const verifier = AssetSdk.create({ trustAnchors: [DEV_CA()] });
async function verifyUrl(url: string) {
const response = await fetch(url);
const bytes = new Uint8Array(await response.arrayBuffer());
return verifier.verify(bytes, []);
}
const [result] = await verifyUrl('/photos/cover.jpg');
For the common single-credential case, destructure the array with const [result] = …; result is undefined when the asset is unsigned (empty array), otherwise it's a { manifest, binding } pair.
To verify a Blob from a file input, get the bytes with new Uint8Array(await blob.arrayBuffer()) and pass them to verifier.verify(bytes, []). The rest of the flow is unchanged.
In-memory bytes are passed as a Uint8Array — read the file with fs.readFile on Node, or fetch → arrayBuffer in the browser. A file too large to hold in memory streams instead: pass a Node RangeSource and the SDK reads only the ranges it needs, without loading the whole file. See File streaming.
The AssetSdk handle is garbage-collected when it goes out of scope; there's nothing to release manually.
Reading the result
Each entry pairs the c2pa ManifestStore (result.manifest) with the binding that located it. A viewer-facing badge typically requires three fields off result.manifest:
manifest.claim_generatoris the Signer name string.manifest.signature_info?.timeis the signing timestamp.manifest.validation_stateis'Trusted','Valid', or'Invalid'.
const [result] = await verifier.verify(bytes, []);
if (!result?.manifest.active_manifest) {
return { signed: false }; // empty array (unsigned), or no active manifest
}
const store = result.manifest;
const manifest = store.manifests![store.active_manifest];
return {
signed: true,
signer: manifest.claim_generator,
signedAt: manifest.signature_info?.time,
state: store.validation_state,
recoveredBy: result.binding, // null when embedded, else the hard/soft binding
};
When recovery may return more than one credential, map over the array (or filter by c.manifest.validation_state) instead of taking the first.
See also
- Validation states: what
Trusted/Valid/Invalid/ unsigned mean. - Recovery: verify assets with no embedded manifest.
- File streaming: stream broadcast masters without loading them whole.
- Container formats: verify an MXF master via the container plugin.