Trust & Provenance
·IdenticAPI

How to Verify Content Credentials Programmatically

Verify Content Credentials programmatically — manifest extraction, signature validation, and trust interpretation using current C2PA tooling and specifications.

Platforms that display or moderate media need server-side Content Credentials verification — extracting C2PA manifest stores, validating signatures, and mapping results to product policy. This guide shows how to integrate official C2PA tooling in Node.js using @contentauth/c2pa-node from the c2pa-js monorepo.

There is no IdenticAPI-hosted C2PA verification endpoint. Use the open-source SDKs maintained by the Content Authenticity Initiative ecosystem, or embed c2pa-rs directly in Rust services. IdenticAPI's AI Security & Trust hub addresses complementary guardrails for generative applications — provenance verification remains your media pipeline's responsibility.

Prerequisites

  • Node.js >= 22 (required by current @contentauth/c2pa-node releases)
  • Linux x86_64/aarch64, macOS, or Windows build targets with precompiled binaries
  • Trust anchor configuration (PEM certificates for partners you accept)

Install:

npm install @contentauth/c2pa-node

For Rust services, add c2pa crate from c2pa-rs instead — API shapes differ but validation semantics align with the specification.

Architecture overview

Client upload → API gateway → Object storage
                    ↓
            Verification worker (Node)
                    ↓
         @contentauth/c2pa-node Reader
                    ↓
         Postgres / cache: validation JSON
                    ↓
         Moderation UI + Content Credentials panel

Never trust browser-side verification alone. Clients may lie about manifest contents.

Reading and validating an asset

The Reader class extracts manifest stores and optionally validates on read:

import { Reader } from '@contentauth/c2pa-node';
import fs from 'node:fs/promises';

const settings = {
  verify: {
    verify_after_reading: true,
    verify_trust: true,
    ocsp_fetch: true,
    remote_manifest_fetch: false, // enable only with URL allowlists
  },
};

export async function verifyContentCredentials(filePath) {
  const reader = await Reader.fromAsset(filePath, settings);

  const manifestStore = reader.json();
  const active = reader.getActive();
  const embedded = reader.isEmbedded();
  const remoteUrl = reader.remoteUrl();

  return {
    manifestStore,
    activeManifestLabel: active?.label ?? null,
    embedded,
    remoteUrl: remoteUrl ?? null,
  };
}

verify_after_reading: true runs signature, hash, and trust checks during fromAsset. Consult the Rust SDK settings for the authoritative list — Node bindings pass them through.

Handling absent manifests

If the file format lacks a manifest store, manifestStore may be empty and getActive() returns null. Map this to provenance: "absent"not synthetic or authentic.

Interpreting manifest JSON

reader.json() returns the manifest store structure defined by the C2PA specification — manifests keyed by label, assertions embedded in JUMBF references.

Extract policy-relevant fields in application code:

function extractProvenanceSummary(manifestStore, activeLabel) {
  if (!activeLabel || !manifestStore?.manifests?.[activeLabel]) {
    return { status: 'absent', assertions: [] };
  }

  const manifest = manifestStore.manifests[activeLabel];
  const assertionLabels = manifest.assertions?.map((a) => a.label) ?? [];

  const actions = manifest.assertions?.find(
    (a) => a.label === 'c2pa.actions'
  )?.data;

  return {
    status: 'present',
    assertionLabels,
    actions: actions?.actions ?? [],
    claimGenerator: manifest.claim_generator ?? null,
  };
}

Exact JSON shapes depend on SDK version — treat examples as illustrative; inspect live output from your pinned release.

Trust lists

With verify_trust: true, validation fails for signers outside your configured trust anchors. Load CA certificates at startup:

// Pseudocode — consult c2pa-rs trust handler docs for your version
import { createC2pa } from '@contentauth/c2pa-node';

const c2pa = createC2pa({
  trust: {
    trust_anchors: ['./certs/partner-ca.pem'],
  },
});

Trust configuration APIs evolve between releases. Read the version-matched documentation at contentauth.github.io/c2pa-js when wiring production trust stores.

Untrusted but structurally valid signatures should surface differently from tampered files — see manifest validation status mapping.

Express upload handler pattern

import express from 'express';
import fs from 'node:fs/promises';
import { verifyContentCredentials } from './verify.js';
import { extractProvenanceSummary } from './summarize.js';
import { unlink } from 'node:fs/promises';

const app = express();

app.post('/media/verify', express.raw({ type: '*/*', limit: '50mb' }), async (req, res) => {
  const tmpPath = `/tmp/upload-${Date.now()}`;
  try {
    await fs.writeFile(tmpPath, req.body);
    const result = await verifyContentCredentials(tmpPath);
    const summary = extractProvenanceSummary(
      result.manifestStore,
      result.activeManifestLabel
    );
    res.json({ ok: true, summary, embedded: result.embedded });
  } catch (err) {
    res.status(422).json({ ok: false, error: 'validation_failed', detail: String(err) });
  } finally {
    await unlink(tmpPath).catch(() => {});
  }
});

Adjust limits, virus scanning, and authentication for production.

Mapping to policy enums

Consolidate SDK output for downstream services:

Internal enumCondition
PROVENANCE_VALIDValidation success + trusted signer
PROVENANCE_UNTRUSTEDValid signature, signer not allowlisted
PROVENANCE_INVALIDHash or signature failure
PROVENANCE_ABSENTNo manifest store

Drive UI labels from enums — never expose raw COSE to browsers.

Example policy:

  • PROVENANCE_VALID + trainedAlgorithmicMedia → show "Signed AI-generated"
  • PROVENANCE_ABSENT → no badge; optional AI detector queue
  • PROVENANCE_INVALID → "Provenance could not be verified"

Remote manifests and SSRF

If you enable remote_manifest_fetch, attackers may supply URLs targeting internal networks. Mitigations:

  • Disable remote fetch by default
  • Allowlist hostnames
  • Fetch from isolated worker with no VPC metadata access

Prefer embedded manifests for user uploads.

Caching and idempotency

Cache validation results keyed by sha256(file) + manifest_label. Invalidate when users replace files.

Store compact JSON summaries, not full binaries, in your database.

WASM and browser verification

@contentauth/c2pa-wasm enables client-side inspection for preview UX — still re-verify server-side before moderation decisions.

Rust alternative

High-throughput pipelines may embed c2pa from c2pa-rs directly:

use c2pa::Reader;

let reader = Reader::from_file("asset.jpg")?;
let manifest_store = reader.manifest_store();

Use the same trust and validation settings concepts described in How C2PA Verification Works.

Testing

  1. Download official sample assets from CAI open-source repositories
  2. Assert PROVENANCE_VALID on known-good files
  3. Flip one byte → expect PROVENANCE_INVALID
  4. Export stripped copy → expect PROVENANCE_ABSENT

Pin @contentauth/c2pa-node version in package-lock.json; validation status enums change across releases.

Observability

Log:

  • request_id, file hash, activeManifestLabel
  • Signer certificate subject (not private keys)
  • Validation status code
  • SDK version

Do not log full audio/video payloads.

What programmatic verification does not provide

  • Proof of factual truth in assertions
  • Speaker identity or deepfake detection
  • Guaranteed manifest survival across third-party transcoding

Combine verification with editorial policy, human review, and optional detection for unsigned media.

Official tooling keeps your verification semantics aligned with the specification — while your product copy stays honest about what signatures actually mean.

Frequently asked questions

How do you verify Content Credentials programmatically?

Use official C2PA SDKs such as @contentauth/c2pa-node (Node.js) or c2pa-rs (Rust). The Reader class extracts manifest stores from assets and validates signatures when verify settings are enabled. There is no substitute for reading validation status from the SDK.

Is there an IdenticAPI C2PA verification endpoint?

No. Integrate @contentauth/c2pa-node or c2pa-rs directly in your media pipeline. IdenticAPI AI Security & Trust covers complementary guardrails for generative applications, not hosted manifest verification.

What Node.js version does @contentauth/c2pa-node require?

Current releases require Node.js 22 or newer. The package downloads precompiled binaries for common Linux, macOS, and Windows targets during installation.

What verify settings matter in production?

Typically enable verify_after_reading, verify_trust with configured trust anchors, and ocsp_fetch for revocation checks. Disable remote_manifest_fetch unless you implement URL allowlists and SSRF protections.

How should APIs map validation results?

Return stable enums such as valid, untrusted, invalid, and absent with signer metadata and assertion summaries. Never map absent manifests to fake or authentic — unknown provenance is a first-class outcome.

Related reading