All validation functions in @svta/cml-c2pa return structured results with an isValid boolean and an errorCodes array. This guide explains the results, the error codes, and the manifest data.
Every result follows the same pattern:
if (!result.isValid) {
for (const code of result.errorCodes) {
console.error(`Validation failure: ${code}`)
}
}
The errorCodes array may contain several codes when several checks fail. When isValid is true, the array is empty.
isValid is true when the manifest is well formed and the claim signature verifies. Every store assertion must be referenced by the claim and match its hash. The manifest must have at most one hard binding. The signature is verified with the end-entity certificate that the manifest carries in its x5chain header. The library does not check that certificate against a trust list. Any party with a certificate can produce a manifest that validates.
Before you present content as authentic, compare the signer with your own trust anchors. InitSegmentValidation.certificate and ManifestBoxValidationResult.certificate hold the DER-encoded end-entity certificate from the claim signature. The value is null when the signature is absent or carries no certificate.
A manifest without a c2pa.signature box fails with C2paStatusCode.CLAIM_SIGNATURE_MISSING. A signature that carries no certificate, or that does not verify over the claim, fails with C2paStatusCode.CLAIM_SIGNATURE_MISMATCH.
A claim box whose CBOR does not decode fails with C2paStatusCode.CLAIM_CBOR_INVALID. A claim box whose CBOR is not a map, for example a map inside a CBOR tag, fails with C2paStatusCode.CLAIM_MALFORMED. The library does not check the fields of the claim.
The LiveVideoStatusCode constants are the live video validation failures that C2PA specification section 19.7 defines.
import { LiveVideoStatusCode } from '@svta/cml-c2pa'
| Constant | Value | Meaning |
|---|---|---|
INIT_INVALID |
livevideo.init.invalid |
Init segment contains an mdat box or BMFF hash mismatch |
MANIFEST_INVALID |
livevideo.manifest.invalid |
C2PA Manifest Box failed standard validation |
SEGMENT_INVALID |
livevideo.segment.invalid |
Crypto failure (signature, hash, or key mismatch), a session key outside its validity period, a broken c2pa.manifestId chain, or a failed custom continuity validator |
ASSERTION_INVALID |
livevideo.assertion.invalid |
sequenceNumber or streamId mismatch |
CONTINUITY_METHOD_INVALID |
livevideo.continuityMethod.invalid |
continuityMethod absent, unsupported, or required companion fields missing |
CONTINUITY_METHOD_UNSUPPORTED |
livevideo.continuityMethod.unsupported |
Custom continuity method with no registered validator (always alongside continuityMethod.invalid) |
SESSIONKEY_INVALID |
livevideo.sessionkey.invalid |
A session key in the init segment is invalid, or the init segment has no valid session key (the keys are absent or expired) |
Versions 1.3.0 and earlier report SESSIONKEY_INVALID from validateC2paSegment for a session key that expired after init segment validation. That code does not match C2PA section 19.7.3. To check the validity period of a session key, see Session Key Lifecycle.
Example of handling specific error codes:
import { LiveVideoStatusCode } from '@svta/cml-c2pa'
for (const code of result.errorCodes) {
switch (code) {
case LiveVideoStatusCode.SEGMENT_INVALID:
// Signature, hash, or session key check failed
break
case LiveVideoStatusCode.SESSIONKEY_INVALID:
// Init segment has an invalid session key or no valid session key
break
case LiveVideoStatusCode.ASSERTION_INVALID:
// Sequence number or stream ID problem
break
case LiveVideoStatusCode.CONTINUITY_METHOD_INVALID:
// Manifest ID chain is broken
break
case LiveVideoStatusCode.INIT_INVALID:
// Init segment structure is invalid
break
case LiveVideoStatusCode.MANIFEST_INVALID:
// Embedded manifest failed validation
break
}
}
validateC2paInitSegment reports SESSIONKEY_INVALID if it finds an invalid session key in the c2pa.session-keys assertion (C2PA section 19.7.3). A session key is invalid if it does not conform to section 18.25.2, or if its signer binding fails verification. The library does not check every rule of section 18.25.2. sessionKeys contains only the session keys that pass these checks.
The COSE key must include a kid (label 2). minSequenceNumber and validityPeriod must be unsigned integers up to Number.MAX_SAFE_INTEGER (2^53 - 1). createdAt must be CBOR tag 0 with an RFC 3339 date-time string (RFC 8949 section 3.4.1). The letters T and Z must be uppercase. The date must exist, so 2025-02-30T00:00:00Z is invalid. A leap second (second 60) is also invalid, because a JavaScript Date cannot represent it.
An expired session key does not count as invalid. sessionKeys excludes the expired key, and the result has no error code for that key.
The assertion must contain at least one session key (section 18.25.2). validateC2paInitSegment reports SESSIONKEY_INVALID if the assertion has no session key, or if its CBOR does not decode. This rule also applies in VOD Merkle mode.
Versions 1.3.0 and earlier report no error code for an invalid session key if another session key is valid. These versions also accept a kid outside the COSE key. They throw an error if the library cannot verify a session key, for example a key type that it does not support. They also throw an error if the CBOR of the c2pa.session-keys assertion does not decode. If your code catches these errors, check errorCodes instead.
The C2paStatusCode constants represent manifest integrity failures defined in C2PA specification sections 15 and 18.
import { C2paStatusCode } from '@svta/cml-c2pa'
| Constant | Value | Meaning |
|---|---|---|
ASSERTION_HASHEDURI_MISMATCH |
assertion.hashedURI.mismatch |
Assertion hash does not match the claim reference |
ASSERTION_MISSING |
assertion.missing |
The store has no box for a claim reference, or more than one box for its label |
ASSERTION_MULTIPLE_HARD_BINDINGS |
assertion.multipleHardBindings |
The manifest has more than one hard binding assertion |
ASSERTION_UNDECLARED |
assertion.undeclared |
The assertion store holds an assertion that the claim does not reference |
ASSERTION_ACTION_INGREDIENT_MISMATCH |
assertion.action.ingredientMismatch |
Action requires an ingredient reference but none is present |
CLAIM_SIGNATURE_MISMATCH |
claimSignature.mismatch |
Claim signature verification failed, or the signature carries no certificate |
CLAIM_SIGNATURE_MISSING |
claimSignature.missing |
The manifest has no c2pa.signature box |
CLAIM_MISSING |
claim.missing |
The manifest has no claim box |
CLAIM_CBOR_INVALID |
claim.cbor.invalid |
The claim box holds CBOR that does not decode |
CLAIM_MALFORMED |
claim.malformed |
The claim box holds CBOR that is not a map |
CLAIM_MULTIPLE |
claim.multiple |
The manifest has more than one claim box |
ASSERTION_BMFFHASH_MALFORMED |
assertion.bmffHash.malformed |
BMFF hash assertion or Merkle structure is malformed |
ASSERTION_BMFFHASH_MISMATCH |
assertion.bmffHash.mismatch |
BMFF content hash does not match the committed value |
Versions 1.2.0 and earlier report claim.signature.mismatch for CLAIM_SIGNATURE_MISMATCH. That value does not match the C2PA specification. Compare codes with the C2paStatusCode constants, not with string literals.
C2paStatusCode values appear in InitSegmentValidation.errorCodes and ManifestBoxValidationResult.errorCodes, which check manifest integrity. They do not appear in SegmentValidationResult.errorCodes: the VSI/EMSG segment validation (Verifiable Segment Info, in event message boxes) uses only LiveVideoStatusCode.
For VOD Merkle streams, InitSegmentValidation extracts merkleMaps from the c2pa.hash.bmff.v3 assertion and validates the initHash binding of each entry. VOD Merkle segments do not use session keys. When merkleMaps is not empty, the result does not report LiveVideoStatusCode.SESSIONKEY_INVALID for missing or expired session keys. An invalid session key still causes SESSIONKEY_INVALID. See Invalid Session Keys.
The C2paManifest type represents a parsed C2PA manifest. It is available through InitSegmentValidation.manifest (VSI method) and ManifestBoxValidationResult.manifest (Manifest Box method).
| Field | Type | Description |
|---|---|---|
label |
string |
Manifest label (identifier within the manifest store) |
instanceId |
string | null |
Unique instance ID |
claimGenerator |
string | null |
Tool that generated the claim |
signatureInfo |
C2paSignatureInfo |
Signature metadata |
assertions |
readonly C2paAssertion[] |
All assertions in the assertion store. Read them only when isValid is true. |
The signatureInfo object contains:
| Field | Type | Description |
|---|---|---|
issuer |
string | null |
Certificate issuer (Common Name or Organization) |
certNotBefore |
string | null |
Certificate validity start (ISO 8601) |
const { isValid, manifest } = init // or result for the Manifest Box method
// The assertions list is the raw assertion store, so read it only when isValid is true.
if (isValid && manifest) {
console.log('Label:', manifest.label)
console.log('Instance ID:', manifest.instanceId)
console.log('Claim generator:', manifest.claimGenerator)
console.log('Issuer:', manifest.signatureInfo.issuer)
console.log('Cert not before:', manifest.signatureInfo.certNotBefore)
for (const assertion of manifest.assertions) {
console.log(`Assertion [${assertion.label}]:`, assertion.data)
}
}
data is the decoded content of the assertion. For a CBOR assertion, a map is a plain object with string keys, a byte string is a Uint8Array, and an unsigned integer up to Number.MAX_SAFE_INTEGER (2^53 - 1) is a number. A larger integer is a BigInt. CBOR tag 0 and tag 1 are a Date. Every other tag is a { tag, value } object. For a JSON assertion, data is the parsed JSON. For other content, or for CBOR that does not decode, data is the raw bytes of the content box.
With the VSI/EMSG method, each SegmentValidationResult includes a sequenceResult field: a discriminated union on reason with SequenceValidationReason constants:
import { SequenceValidationReason } from '@svta/cml-c2pa'
| Constant | Value | Valid | Additional Fields | Description |
|---|---|---|---|---|
VALID |
valid |
true |
none | Sequence number is the next expected value |
DUPLICATE |
duplicate |
false |
none | Sequence number was already seen |
GAP_DETECTED |
gap_detected |
false |
missingFrom, missingTo |
One or more sequence numbers were skipped |
OUT_OF_ORDER |
out_of_order |
false |
none | Sequence number is less than the last seen |
SEQUENCE_NUMBER_BELOW_MINIMUM |
sequence_number_below_minimum |
false |
none | Below the session key's minSequenceNumber |
Narrow the type to access the GAP_DETECTED fields:
import { SequenceValidationReason } from '@svta/cml-c2pa'
if (result.sequenceResult.reason === SequenceValidationReason.GAP_DETECTED) {
console.warn(
`Missing segments ${result.sequenceResult.missingFrom}` +
` through ${result.sequenceResult.missingTo}`,
)
}
The two validation methods serve different use cases:
| Aspect | VSI/EMSG (section 19.4) | Manifest Box (section 19.3) |
|---|---|---|
| Init segment required | Yes | No |
| Per-segment overhead | Low (EMSG box only) | High (full manifest per segment) |
| Session keys | Yes | No |
| Manifest access | Init segment only | Every segment |
| Continuity mechanism | Sequence numbers | Manifest ID chaining |
| Functions | validateC2paInitSegment + validateC2paSegment |
validateC2paManifestBoxSegment |
The VSI/EMSG method suits low-overhead real-time streaming where bandwidth matters. The Manifest Box method gives self-contained segments that are verifiable without earlier context.