The Manifest Box method embeds a full C2PA manifest directly into each media segment, as defined in section 19.3 of the C2PA specification. Each segment is self-contained with its own COSE signature, so no init segment or session keys are needed.
Continuity between segments is verified through manifest ID chaining: each segment declares the manifest ID of the previous segment, forming a verifiable chain.
Unlike the VSI/EMSG method, the Manifest Box method does not require a separate init segment validation step. Instead, a single function — validateC2paManifestBoxSegment — handles everything: manifest parsing, signature verification, BMFF hash validation, and continuity checks.
Two pieces of state are threaded between calls:
lastManifestId — the manifest ID from the previous segment, used for continuity verificationstate — a ManifestBoxValidationState object that tracks lastStreamId and lastSequenceNumberimport { validateC2paManifestBoxSegment } from '@svta/cml-c2pa'
import type { ManifestBoxValidationState } from '@svta/cml-c2pa'
let lastManifestId: string | null = null
let state: ManifestBoxValidationState | undefined
for (const segmentUrl of segmentUrls) {
const bytes = new Uint8Array(await fetch(segmentUrl).then(r => r.arrayBuffer()))
const { result, nextManifestId, nextState } = await validateC2paManifestBoxSegment(
bytes,
lastManifestId,
state,
)
lastManifestId = nextManifestId
state = nextState
if (result.isValid) {
console.log(`Segment ${result.sequenceNumber}: valid`)
} else {
console.error(`Segment ${result.sequenceNumber}: failed`, result.errorCodes)
}
}
The function returns an object with:
result — a ManifestBoxValidationResult with the validation outcomenextManifestId — the manifest ID to pass into the next callnextState — the updated state to pass into the next call
For the first segment, pass null as lastManifestId and undefined (or omit) for state. The chain comparison against the previous segment is skipped when lastManifestId is null, but the validator still checks that the segment itself declares a valid continuityMethod and a non-empty previousManifestId.
import { validateC2paManifestBoxSegment, LiveVideoStatusCode } from '@svta/cml-c2pa'
import type { ManifestBoxValidationState } from '@svta/cml-c2pa'
async function validateStream(segmentUrls: string[]) {
let lastManifestId: string | null = null
let state: ManifestBoxValidationState | undefined
for (const segmentUrl of segmentUrls) {
const bytes = new Uint8Array(await fetch(segmentUrl).then(r => r.arrayBuffer()))
const { result, nextManifestId, nextState } = await validateC2paManifestBoxSegment(
bytes,
lastManifestId,
state,
)
lastManifestId = nextManifestId
state = nextState
if (result.isValid) {
console.log(
`Segment ${result.sequenceNumber}: valid` +
` (stream: ${result.streamId}, issuer: ${result.issuer})`,
)
} else {
for (const code of result.errorCodes) {
switch (code) {
case LiveVideoStatusCode.CONTINUITY_METHOD_INVALID:
console.error('Continuity chain broken')
break
case LiveVideoStatusCode.ASSERTION_INVALID:
console.error('Assertion field mismatch (sequenceNumber or streamId)')
break
default:
console.error('Validation failure:', code)
}
}
}
// Access the full manifest when needed
if (result.manifest) {
console.log('Claim generator:', result.manifest.claimGenerator)
console.log('Assertions:', result.manifest.assertions.length)
}
}
}
The ManifestBoxValidationState object carries inter-segment state for continuity checks:
| Field | Type | Description |
|---|---|---|
lastStreamId |
string | null |
Stream ID from the previous segment |
lastSequenceNumber |
number | null |
Sequence number from the previous segment |
The function is pure — it does not mutate any external state. The caller is responsible for persisting nextManifestId and nextState between calls.
When state is omitted or undefined, the function skips streamId consistency and sequence number monotonicity checks (suitable for the first segment).
Each segment's c2pa.livevideo.segment assertion includes a previousManifestId field that must match the manifest ID of the preceding segment. This creates a verifiable chain:
Segment 1: manifestId = "abc-123", previousManifestId = "initial-manifest"
Segment 2: manifestId = "def-456", previousManifestId = "abc-123" // must match segment 1
Segment 3: manifestId = "ghi-789", previousManifestId = "def-456" // must match segment 2
Every segment must declare a previousManifestId, including the first one. The first segment typically references the initial static manifest. When lastManifestId is null (first call), the chain comparison is skipped — the validator only checks that previousManifestId is present.
The continuityMethod field indicates how continuity is verified. The currently supported method is c2pa.manifestId.
When the chain is broken (the previousManifestId does not match the previous segment's manifest ID), the result includes LiveVideoStatusCode.CONTINUITY_METHOD_INVALID.
The ManifestBoxValidationResult contains:
| Field | Type | Description |
|---|---|---|
manifest |
C2paManifest | null |
Parsed manifest, or null on parse failure |
issuer |
string | null |
Certificate issuer from the signature |
sequenceNumber |
number | null |
From the c2pa.livevideo.segment assertion |
previousManifestId |
string | null |
From the c2pa.livevideo.segment assertion |
streamId |
string | null |
From the c2pa.livevideo.segment assertion |
continuityMethod |
string | null |
From the c2pa.livevideo.segment assertion |
bmffHashHex |
string | null |
Hex-encoded BMFF content hash |
isValid |
boolean |
All checks passed |
errorCodes |
readonly (LiveVideoStatusCode | C2paStatusCode)[] |
Failure codes (empty when valid) |
Unlike the VSI method, the Manifest Box method can produce both LiveVideoStatusCode and C2paStatusCode error codes, since each segment carries a full manifest that undergoes integrity checks (assertion hashes, claim signature verification).