As of This Month, EU Law Says Your AI Output Has to Mark Itself
Article 50 of the EU AI Act took effect on August 2, and it says any image, audio, video, or text your system generates has to carry a machine-readable mark that it was made by AI. A checkbox in your UI does not satisfy that. The mark has to live inside the file, survive a re-upload, and be verifiable. Here is how to build that into your generation pipeline as a gate, not a bolt-on.
On August 2, a quiet clause in the EU AI Act became enforceable, and most engineering teams shipping generative features have not touched their pipeline in response. Article 50 says that if your system produces synthetic images, audio, video, or text, that output has to be marked in a machine-readable format so it is detectable as artificially generated. Not labeled in your UI. Marked, in the file, in a way a machine can verify after the file has left your control.
The gap between what teams think this requires and what it actually requires is where the risk sits. Most people read "transparency" and add a caption that says Generated by AI under the image. That caption is a string in your React tree. It does not travel with the JPEG when a user right-clicks and saves it, it does not exist when your API returns the raw bytes to another service, and it is invisible to any automated system trying to figure out whether a piece of media was synthesized. Article 50 is asking for the second thing, the mark inside the bytes, and the penalty for getting it wrong reaches fifteen million euros or three percent of worldwide turnover, whichever is larger.
The good news is that the technical answer is settled, the tooling is open source, and the whole thing fits into your generation path as one gate. The work is small. The reason it is not done is that nobody framed it as an engineering task with a clear finish line. So let me do that.
Two obligations people keep collapsing into one
Article 50 actually contains two separate requirements, and conflating them is the first mistake.
The first is a human-facing disclosure. If a person is interacting with an AI system, such as a chatbot, they have to be told. If content is a deepfake, a human looking at it has to be informed it is artificial. This one is a product and UX problem. You add a notice to the chat, you add a visible label to the media, and you are done with this half.
The second is machine-readable marking. Providers of generative systems have to ensure the outputs are marked in a format that a machine can detect as artificially generated. This one is not a UX problem, it is a pipeline problem, and it is the half almost everyone skips because it is invisible in the product and only shows up when a regulator, a platform, or a downstream verifier inspects the file.
The de facto standard for the machine-readable half is C2PA, branded as Content Credentials, now standardized as ISO/IEC 21694 and backed by Adobe, Microsoft, Google, the BBC, and thousands of others. The European Commission's draft transparency code names C2PA by example. So this is not a bet on a format. It is the format.
What a Content Credential actually is
A C2PA manifest is a small, cryptographically signed record that gets embedded into the asset. Think of it as a signed nutrition label bound to the file. For AI-generated content the load-bearing part is an assertion that the content was created by a trained algorithmic model, plus a claim generator string that identifies your product.
Here is a minimal manifest for an AI-generated image. This is the definition you feed the signing step, not something you hand-write per file, but it is worth seeing what the claim actually asserts.
{
"claim_generator": "acme-image-api/2.4.0",
"title": "Generated image",
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
"softwareAgent": "acme-diffusion-3"
}
]
}
}
]
}
The one field that carries the legal weight is digitalSourceType set to trainedAlgorithmicMedia. That is the IPTC code that means "made by a generative model," and it is what a verifier keys on to conclude the content is synthetic. Everything else, the timestamps, later edits, the model version, is useful provenance but the created-by-a-model assertion is the part Article 50 is asking for.
Make it a gate, not a bolt-on
The failure mode I see coming is teams treating this as a post-processing script somebody runs on a folder of images once a quarter. That is how you end up with unmarked output in production, because the one code path that skipped the script is the one that shipped to a European user.
The right shape is a gate. Nothing leaves your generation service without a credential, the same way nothing should leave without passing your output guardrails. You put the signing step at the egress boundary, after generation and before the bytes are returned or stored, and you make it structurally impossible to return an asset that did not go through it.
Here is that gate in Node using the open-source c2pa-node library. The generation function produces raw bytes, and the gate signs them before anything downstream can touch them.
import { createC2pa, createTestSigner, ManifestBuilder } from "c2pa-node";
// In production the signer wraps a real certificate held in a KMS or HSM.
// createTestSigner is for local development only and its cert is not trusted.
const signer = await createTestSigner();
const c2pa = createC2pa({ signer });
const MANIFEST = {
claim_generator: "acme-image-api/2.4.0",
format: "image/png",
title: "Generated image",
assertions: [
{
label: "c2pa.actions",
data: {
actions: [
{
action: "c2pa.created",
digitalSourceType:
"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
softwareAgent: "acme-diffusion-3",
},
],
},
},
],
};
// The egress gate: raw generated bytes go in, a signed asset comes out,
// and this is the only exit from the service.
export async function markAndRelease(pngBytes: Buffer): Promise<Buffer> {
const asset = { buffer: pngBytes, mimeType: "image/png" };
const manifest = new ManifestBuilder(MANIFEST);
const { signedAsset } = await c2pa.sign({ asset, manifest });
return signedAsset.buffer;
}
The important discipline is not the library call, it is that markAndRelease is the single return path. If a developer can call the generator and get bytes without going through this function, the gate is theater. Wire it so the raw generation function is private to the module and the only exported way to obtain output is the signed one. That is the difference between a control and a suggestion, and it is the same reasoning behind pinning and signing anything you run from outside, which I covered in the skill supply-chain post.
If you would rather not run the library in-process, the same thing works as a shell step with c2patool, which is convenient for batch jobs and non-Node stacks:
# Sign a generated file against a manifest definition and signing config.
# The signing config points at your cert, key, and timestamp authority.
c2patool generated.png \
--manifest manifest.json \
--config signing.json \
--output signed.png
Either path gets you the same result: a file that now carries a signed, machine-readable statement that it was produced by a model.
Verify what you shipped
A gate you cannot check is a gate you do not trust. You want a read path, both to assert in tests that everything leaving the service is marked and to give downstream consumers a way to confirm provenance. Reading is cheaper than signing and needs no certificate.
import { createC2pa } from "c2pa-node";
const reader = createC2pa();
// Returns the manifest store if the asset is marked, or null if it is bare.
export async function isMarkedAsAI(bytes: Buffer, mimeType: string) {
const result = await reader.read({ buffer: bytes, mimeType });
if (!result) return { marked: false as const };
const active = result.manifests[result.activeManifest];
const actions = active?.assertions?.find((a) => a.label === "c2pa.actions");
const created = actions?.data?.actions?.some(
(x: { digitalSourceType?: string }) =>
x.digitalSourceType?.endsWith("trainedAlgorithmicMedia"),
);
return { marked: true as const, declaresAI: Boolean(created) };
}
Put a call to this in an integration test that runs a real generation through the gate and asserts declaresAI is true. That single test is what keeps the obligation from silently regressing the next time someone refactors the response path. It is the same instinct as testing that your egress filter actually fired, not just that it exists.
The parts that will bite you
Metadata gets stripped. An embedded manifest is metadata, and a screenshot or an aggressive re-encode drops it. This is the honest ceiling of embedded-only marking. The standard answer is to pair the manifest with a durable content fingerprint stored on your side, a soft binding, so a stripped file can be matched back to the credential you kept. For images and audio, an invisible watermark is the increasingly common third layer. Your legal duty is to apply the mark, not to make it physically indestructible, but if you want the mark to survive the real internet, plan for the fingerprint from the start rather than discovering the gap after launch.
The test signer is not a real signer. createTestSigner exists so you can develop without a certificate, and its signature verifies against nothing anyone trusts. Production needs a real certificate from a recognized authority, and the private key belongs in a KMS or HSM, not in an environment variable and definitely not in the repo. Treat the signing key with the same care as any other production signing secret, because a leaked one lets anyone forge your provenance.
Text is the hard case, so do not pretend otherwise. You can sign a text file the same way you sign an image, and if you deliver generated text as a downloadable artifact or an API payload, do that. But text pasted into a chat box has no file to carry metadata, and there is no invisible text watermark that reliably survives a user editing or paraphrasing it. For that surface, lean on the human-facing disclosure and mark the downloadable forms, and be precise in your documentation about what is and is not covered. Overclaiming an invisible text mark you cannot back up is worse than admitting the limit.
Do not forget the other half. Signing the file satisfies the machine-readable obligation and nothing else. The chatbot disclosure and the visible deepfake label are separate requirements under the same article. The gate handles the bytes. A product task handles the human.
The takeaway
Article 50 is enforceable now, and the machine-readable marking it asks for is not a policy you write, it is a few lines of code at the edge of your generation service. Sign every generated asset with a C2PA Content Credential that declares it was made by a model, put that signing step at the egress boundary so nothing leaves unmarked, verify it in a test so the obligation cannot regress, and pair the embedded mark with a stored fingerprint if you want it to survive the wild. That is a gate, not a compliance project, and once it is in place the fifteen-million-euro version of this problem stops being your problem.
If you are shipping generative features into the EU and are not sure whether your pipeline actually marks its output or just captions it, that gap is worth closing before an auditor or a platform finds it for you. Book a consultation call and we can walk your generation path, find where unmarked output escapes, and put a provenance gate in front of it.
Viral Ruparel
Generative AI consultant helping teams ship reliable LLM and agent systems in production.
Contact Viral about your AI project →Frequently Asked Questions
Does a visible "Generated by AI" label satisfy Article 50?+
No, not on its own. Article 50 asks for two different things that people keep collapsing into one. The first is a human-facing disclosure, the label a person reads, which applies to chatbots and to deepfakes. The second is a machine-readable marking embedded in the generated file itself, which applies to synthetic images, audio, video, and text so that a downstream system can detect the content as artificially generated without a human in the loop. A caption in your UI covers the first and does nothing for the second, because the moment the file leaves your page the caption is gone and the bytes carry no signal. You need the embedded mark as well, which is what C2PA Content Credentials provide.
What actually goes into a C2PA manifest for AI content?+
A manifest is a small signed record bound to the asset. For AI output the part that matters is an actions assertion stating the content was created, with a digital source type of trainedAlgorithmicMedia, plus the claim generator string that names your product and model. You can add the creation timestamp and later human edits, but the legally load-bearing field is the one that says this came from a trained model. The whole thing is cryptographically signed with your certificate, so a verifier can check both that the mark is present and that it has not been altered since you signed it.
What happens to the mark when someone screenshots or re-encodes the image?+
An embedded C2PA manifest is metadata, so a screenshot or a lossy re-encode that strips metadata will strip it, and that is the honest limit of the approach. This is why the standard pairs embedded credentials with a durable content fingerprint, sometimes called a soft binding, and increasingly with an invisible watermark for images and audio. The fingerprint lets a verifier match a stripped file back to the manifest you stored server side. For compliance you are obligated to apply the marking, not to make it physically unremovable, but pairing the manifest with a watermark and a stored fingerprint is what turns a checkbox into something that actually survives contact with the real internet.
We are not in the EU. Does this still apply to us?+
It applies if your generated output reaches users in the EU, the same extraterritorial reach the GDPR has, so a US company serving European users is in scope. Beyond the legal question, the marking is becoming table stakes. Platforms are starting to read Content Credentials and label or downrank media that lacks provenance, and buyers in regulated industries are adding it to procurement checklists. Building the gate once covers the EU obligation and positions you for the labeling other jurisdictions and platforms are converging on, so it is worth doing even if the fine is not your immediate concern.
Can I mark generated text the same way I mark images?+
Partly. C2PA can wrap a text asset and carry the same signed assertion, so if you are shipping generated text as a file you can sign it exactly like an image. The harder case is text pasted into a box, where there is no file to carry metadata and no invisible text watermark that reliably survives editing and paraphrasing. For that path the practical answer is the human-facing disclosure plus provenance on any downloadable or API form of the output, and honesty in your documentation about what the mark does and does not cover. Do not oversell an invisible text watermark you cannot actually stand behind.
Related Articles
Your Agent Called the Same Tool Seventy Times and Billed You for It
A ReAct-style agent calls the same search tool, gets the same unhelpful result, decides another identical call will help, and does it again. Seventy times. It never crashes and never finishes, it just burns tokens going nowhere until a timeout or your bill catches it. A hard step ceiling is a backstop, not a fix. What you want is a guard that notices the agent has stopped making progress and breaks the cycle in seconds.
You Installed a Skill and Your Agent Will Run Whatever Is Inside
You pulled a skill off a registry, your agent read its instructions, and it is now ready to execute whatever code shipped in the folder. Nobody signed it, nobody diffed it, and the manifest that says "read-only" is just a text file the author wrote. Treat every third-party skill as untrusted code and put a gate in front of it: pin and hash it, check what it actually does against what it claims, and run it in a box that can only reach what it declared.
Your Agent Passes Every Eval and Still Fumbles Real Conversations
Your eval suite is green. Every case passes. Then a real user has a six-turn conversation with your agent and it forgets what they said in turn one, asks for information they already gave, and quietly breaks a policy under pressure. Single-shot evals test a single prompt. Production is a conversation. Drive your agent with a simulated user and you can test the thing users actually do.