C2PA Tool, invoked as c2patool, is the open-source command-line utility published by the Content Authenticity Initiative for working with C2PA manifests. It does three things: prints a summary of the manifests in a file as JSON, prints a lower-level report of the manifest data, and adds a new signed manifest to a file. It is built on the CAI's Rust SDK, the same library behind the Verify website, so when a web tool gives a result you want to examine closely, c2patool shows you the underlying data.
Every command on this page was checked against the tool's official documentation in the contentauth/c2patool repository and run against version 0.28.0, released on 22 September 2026. The tool is still pre-1.0 and its options change between releases; when in doubt, c2patool -h is authoritative for the version you have installed.
Where c2patool lives now
The tool's home has moved more than once, which confuses search results and older tutorials. It started as a standalone repository, was later folded into the c2pa-rs monorepo alongside the Rust library, and now lives in its own repository again at github.com/contentauth/c2patool. Older releases are tagged c2patool-vX.Y.Z on the c2pa-rs releases page; newer ones are published on the c2patool repository. Some of the official examples still show file paths from the monorepo period, such as c2pa-rs/cli/sample.
The names are easy to mix up:
c2patoolis the command-line tool and the name of its crate on crates.io.c2pais the Rust library (the "CAI Rust library" or Rust SDK), published as thec2pacrate. It is not a command-line program.- Language bindings built on the same core exist for other languages; the CAI's open-source documentation site lists them.
Installing
On macOS, Homebrew packages the tool, though the formula can trail the latest release by a few versions:
brew install c2patool
On any platform, download the archive for your operating system from the releases page (prebuilt binaries are provided for macOS, Windows, and x86-64 Linux), extract it, and put the c2patool executable on your PATH. On macOS you may need to clear Gatekeeper's quarantine flag before an unnotarized binary will run. With a Rust toolchain installed, you can build from source instead:
cargo install c2patool
Confirm the install and check the version:
c2patool -V
c2patool -h
Reading a manifest
The basic syntax is c2patool <ASSET_PATH> [OPTIONS] [SUBCOMMAND]. With no options, the tool reads the file and prints the manifest store as JSON:
c2patool photo.jpg
The output, abbreviated here from one of the tool's own sample images, has a stable top-level shape:
{
"active_manifest": "contentauth:urn:uuid:5a08e472-...",
"manifests": {
"contentauth:urn:uuid:5a08e472-...": {
"claim_generator": "make_test_images/0.12.0 c2pa-rs/0.12.0",
"title": "C.jpg",
"format": "image/jpeg",
"assertions": [
{ "label": "stds.schema-org.CreativeWork", "data": { ... } },
{ "label": "c2pa.actions.v2",
"data": { "actions": [ { "action": "c2pa.created" },
{ "action": "c2pa.drawing", ... } ] } }
],
"signature_info": {
"alg": "Ps256",
"issuer": "C2PA Test Signing Cert",
"time": "2022-08-19T19:03:41+00:00"
}
}
},
"validation_status": [
{ "code": "signingCredential.untrusted", ... }
],
"validation_results": { "activeManifest": { "success": [...],
"informational": [...], "failure": [...] } },
"validation_state": "Valid"
}
The fields to look at first:
active_manifestnames the most recent manifest, the one that describes the file as it is now. Earlier manifests appear inmanifestsbecause they belong to ingredients.assertionshold the substance: actions taken, digital source type, creative-work metadata, and so on. The assertions and claims page explains the common labels.signature_infogives the algorithm, the certificate issuer, and the signing time if a timestamp was obtained.validation_stateisInvalid,Valid, orTrusted. The detailed status codes behind it appear undervalidation_results, grouped into success, informational, and failure lists.
Other read modes
| Command | What it shows |
|---|---|
c2patool photo.jpg --info | A short report: provenance URI, manifest store size, number of manifests, and any validation issues |
c2patool photo.jpg -d | The detailed report (--detailed): the manifest in its internal C2PA form, including the claim, assertion hashes, and hashed URIs |
c2patool photo.jpg --tree | A text tree of the manifest store: assertions and nested ingredients |
c2patool photo.jpg --certs | The PEM certificate chain from the active manifest's signature |
c2patool photo.jpg -o ./report | Writes the manifest data, including thumbnails, into a directory |
The detailed report is the one to use alongside the manifest structure page: it shows what was actually signed rather than the SDK's friendlier summary. Piping --certs into openssl x509 -text -noout prints the signing certificate's subject, issuer, and validity dates.
Trust: why everything says "untrusted"
Out of the box, c2patool checks structure and cryptography but has no trust list configured, so it reports signingCredential.untrusted for every signer while still giving a validation_state of Valid when the cryptography checks out. To evaluate trust the way consumer tools do, point it at a trust list with the trust subcommand. For the official C2PA trust list:
c2patool photo.jpg trust \
--trust_anchors 'https://raw.githubusercontent.com/c2pa-org/conformance-public/refs/heads/main/trust-list/C2PA-TRUST-LIST.pem'
The subcommand also accepts --allowed_list (specific end-entity certificates), --trust_config (allowed extended key usages), and --trust_list_uri; each has an environment-variable equivalent such as C2PATOOL_TRUST_ANCHORS. Rather than repeating these on every run, c2patool init trust downloads the official list once and caches it next to the settings file (by default under ~/.config/c2pa/), after which trust checks are applied automatically. Adding --legacy also fetches the frozen interim trust list that the Verify website still uses. The trust list page covers the difference between the two lists.
Adding a manifest
To sign, you describe the manifest in a JSON manifest definition and pass it with -m, naming the output with -o. A minimal definition that records two edits:
{
"claim_generator_info": [
{ "name": "imgkey-example", "version": "1.0" }
],
"title": "harbor.jpg",
"assertions": [
{
"label": "c2pa.actions.v2",
"data": {
"actions": [
{ "action": "c2pa.color_adjustments" },
{ "action": "c2pa.cropped" }
]
}
}
]
}
c2patool harbor.jpg -m manifest.json -o harbor-signed.jpg
With no key or certificate specified, the tool signs with a built-in ES256 test certificate and prints a note that this is only valid for development. The result validates cryptographically but will never be trusted by any consumer tool, which is exactly what you want for testing. The JSON format is documented in the manifest definition docs and the CAI's JSON manifest reference.
Manifest intent
By default the tool applies the edit intent: it adds the source file as a parent ingredient and inserts a c2pa.opened action ahead of the actions you listed. Two flags change this:
--create <source_type>declares a new original, with an IPTC digital source type such asdigitalCaptureortrainedAlgorithmicMedia. The tool adds ac2pa.createdaction. It cannot be combined with--updateor--parent.--updatedeclares non-editorial changes, such as re-encoding, to a file that already carries a manifest.
A manifest definition can also be passed inline with -c, which is convenient for the create case:
c2patool capture.jpg -c '{"assertions":[]}' --create digitalCapture -o capture-signed.jpg
Ingredients
Ingredients are the earlier assets a new file was made from, and their own manifests are carried inside the new one. There are two ways to add them. -p (--parent) names a parent file that differs from the source:
c2patool edited.jpg -m manifest.json -p original.jpg -o edited-signed.jpg
Additional component ingredients can be listed in the manifest definition with the tool-specific ingredient_paths field, for example "ingredient_paths": ["logo.png"]. Running --tree on the result shows each ingredient and, where it had one, its own manifest nested beneath it. The --ingredient option (c2patool file.jpg --ingredient --output ./ingredient) writes an ingredient definition and a binary .c2pa manifest store for a file, useful for inspecting or reusing its provenance.
Real keys
For anything beyond testing, supply a certificate and key. The tool-specific fields alg, private_key, sign_cert, and ta_url (a timestamp authority) can go in the manifest definition, or the equivalents can go in a settings file or the C2PA_SIGN_CERT and C2PA_PRIVATE_KEY environment variables. The documentation is explicit that keys in files are for development only. In production, use --signer-path, which delegates signing to an external program (a wrapper around a KMS or HSM, for instance) so the private key never passes through c2patool, or configure a remote signing service. The signing documentation specifies the protocol. A certificate that will be trusted by consumer tools has to chain to the C2PA trust list; see the trust list page.
Other outputs
-s (--sidecar) writes the manifest to a separate .c2pa file instead of embedding it, and -r <url> (--remote) embeds a reference to a manifest hosted at that URL. --external-manifest <file.c2pa> validates an asset against a sidecar. The fragment subcommand handles fragmented MP4 (BMFF) video.
Common errors
| Message | Cause |
|---|---|
Error: No claim found | The file has no embedded C2PA manifest. As with every tool, this says nothing about whether the image is authentic. |
Error: Output already exists; use -f/force to force write | The -o path exists. Add -f to overwrite. If output and source are the same path, the source is overwritten. |
Error: Output type must match source type | The output extension differs from the input. c2patool does not convert formats. |
Error: Unsupported file type or type is unsupported | The format is not one the SDK can embed into, or a path points at something other than a supported asset. Check the supported formats list. |
signingCredential.untrusted | Not an error in itself. The signer is not on a configured trust list; with no trust list configured, every signer is reported this way. |
assertion.dataHash.mismatch | The file's bytes changed after signing, so the hard binding fails and validation_state becomes Invalid. Editing EXIF or XMP with another tool after signing is enough to cause it. |