The legal side of this is its own article: from August 2, Article 50(2) of the EU AI Act wants your generative system's output marked in a machine-readable way, and the Commission's Code of Practice points at C2PA Content Credentials as an example that clears the bar. This is the engineering side — how to actually stamp that mark on an image, in about fifteen minutes, with the official tooling.

What you're building#

A Content Credential is a signed manifest embedded in the file. For our purpose it needs to say one thing clearly: this file was created by an algorithm. In C2PA that statement is an assertion — a c2pa.actions block with a c2pa.created action whose digitalSourceType is the IPTC URI for algorithmically generated media. Get that field right and a verifier anywhere can read "AI-generated" off the file.

1. Install c2patool#

c2patool is the official CLI from the Content Authenticity Initiative. Grab a release binary or build it with Cargo:

# option A: prebuilt binary from the releases page (contentauth/c2patool)
# option B: from source
cargo install c2patool
c2patool --version

2. Write the manifest#

Create ai-manifest.json. The one line that matters for Article 50 is the digitalSourceType:

{
  "claim_generator": "acme-image-api/1.4",
  "title": "AI-generated image",
  "assertions": [
    {
      "label": "c2pa.actions",
      "data": {
        "actions": [
          {
            "action": "c2pa.created",
            "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
          }
        ]
      }
    }
  ]
}

trainedAlgorithmicMedia is the IPTC vocabulary value for "produced by a trained algorithm" — i.e. a model. (If a human meaningfully edited a real photo with AI tooling, that's a different value; for fully generated output, this is the one.)

3. Sign it into the file#

Point the tool at your generated image and the manifest. In development, c2patool signs with built-in test credentials, so this is a single command:

c2patool generated.jpg -m ai-manifest.json -o signed.jpg

signed.jpg now carries the credential. For production, don't ship the test cert — sign with your own key and certificate so the credential is attributable to you:

export C2PA_PRIVATE_KEY=$(cat my_key.pem)
export C2PA_SIGN_CERT=$(cat my_cert.pem)
c2patool generated.jpg -m ai-manifest.json -o signed.jpg

4. Verify before you ship#

Read the manifest straight back out:

c2patool signed.jpg

You want to see the c2pa.created action and the trainedAlgorithmicMedia source type in the output, with a valid signature. As a human sanity check, drop signed.jpg into the public inspector at contentcredentials.org/verify — it renders the same claim your users' tools will read.

Two things that will bite you#

Do it at generation time. The correct place for the c2patool call — or the in-process c2pa-python / Rust binding — is the same function that returns the asset to the user. A nightly batch job that marks yesterday's images is a nightly window of unmarked output, and for a system launched after August 2 there is no grace period.

Metadata is strippable, so add a layer. A screenshot or a careless re-encode can drop a C2PA manifest, which is exactly why the law says "robust and reliable as far as technically feasible." The pattern the big labs converged on — and the one the Code of Practice describes — is provenance metadata plus a pixel-level watermark such as SynthID underneath it. C2PA is the layer you can ship this afternoon; treat the watermark as the next ticket, not a reason to delay this one.