# AI content labeling in Russia from March 2027: a practical checklist

> AI Release · @ai_release1 · https://ai-release.org/guides/markirovka-ii-kontenta-2027-cheklist_en.html

## TL;DR
- **Time to complete:** 90–120 minutes for the first asset; about 10 minutes per additional asset once the pipeline exists.
- **Difficulty:** Intermediate
- **You will need:** Git, Python 3 with the `jsonschema` package, Node.js LTS with `ajv-cli` (run via `npx`), ExifTool, FFmpeg, write access to the repository that builds your website or app, and the current text of the AI content labeling rules that take effect on 1 March 2027.

## Requirements and setup
![Illustration: Requirements and setup](https://ai-release.org/guides/img/markirovka-ii-kontenta-2027-cheklist-en-1.jpg?v=1793342112)


- **OS:** Linux, macOS, or Windows 10+ with WSL2. On PowerShell 7 run `$PSVersionTable.PSVersion` to record your shell version.
- **Python:** confirm `python3 --version` and install the validator with `pip install jsonschema`.
- **Node.js:** confirm `node -v` and `npx --version`. No local install of `ajv-cli` is needed — `npx` fetches it.
- **ExifTool:** confirm `exiftool -ver`. This is the tool that writes metadata into image files.
- **FFmpeg:** confirm `ffmpeg -version | head -n 1`. This writes metadata into video containers.
- **Rights:** write access to the labeling repository and permission to run its CI pipeline.
- **Legal scope sign-off:** which of your outputs are in scope, which exemptions apply, and the exact wording of the disclosure must be confirmed against the current regulation text with your legal team. This guide covers only the engineering side.

Create the working layout. Every artifact gets one manifest file and one asset file with the same ID — that naming rule is what makes the rest of the guide scriptable.

```bash
mkdir -p ai-labeling/{schema,manifests,assets,scripts}
cd ai-labeling
git init
```

```text
ai-labeling/
├── schema/label.schema.json
├── manifests/img-2027-0001.json
├── assets/img-2027-0001.jpg
└── scripts/verify_labels.sh
```

**Example:** after cloning your empty repo, `ls` prints `assets  manifests  schema  scripts` — you are ready for Step 1.

## Step 1. Define the label record and validate it

**What to do:** create one JSON manifest per asset that was generated or edited by AI, describe its fields in a JSON Schema, and validate every manifest against that schema before it reaches production. A manifest is a plain-text record of provenance — what made the asset, when, and whether a human reviewed it.

**Why:** the manifest is the single source of truth. The file metadata, the page label, and the API header are all generated from it, so they cannot drift apart. Writing the record first also forces you to capture the model identifier at generation time, before it is lost.

**Example:** `manifests/img-2027-0001.json`:

```json
{
  "asset_id": "img-2027-0001",
  "asset_sha256": "<output-of-sha256sum>",
  "created_at": "2027-02-14T10:22:00Z",
  "origin": "ai-generated",
  "model_provider": "<provider-name-from-your-contract>",
  "model_id": "<exact-model-id-returned-by-the-api>",
  "ai_generated_share": 1.0,
  "human_reviewed": true,
  "reviewer": "editor@example.com",
  "reviewed_at": "2027-02-14T11:05:00Z",
  "disclosure": { "visible_label": "AI-generated image", "machine_readable": true }
}
```

Replace `<exact-model-id-returned-by-the-api>` with the string your provider returns in the response body — do not type a model name from memory. For AI-written text assets use the same schema and set `origin` to `ai-assisted` where an editor rewrote the draft; see [AI for text work: translation, summarization, rewriting](https://ai-release.org/guides/nejroseti-dlya-teksta.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) for the workflows that produce those drafts.

Add `schema/label.schema.json` with `"required": ["asset_id", "asset_sha256", "created_at", "origin", "model_id", "ai_generated_share", "human_reviewed", "disclosure"]`, then hash the asset and validate:

```bash
sha256sum assets/img-2027-0001.jpg > checksums.txt
python3 -c "import json,jsonschema;jsonschema.validate(json.load(open('manifests/img-2027-0001.json')),json.load(open('schema/label.schema.json')));print('manifest valid')"
```

**How to check:** the terminal prints exactly `manifest valid`. Then delete `"model_id"` from a copy of the manifest, validate again, and confirm you get a `ValidationError` naming that field — the schema is actually enforcing something.

## Step 2. Write the label into the asset, the page, and the API response
![Illustration: Step 2. Write the label into the asset, the page, and the API response](https://ai-release.org/guides/img/markirovka-ii-kontenta-2027-cheklist-en-2.jpg?v=1793342120)


**What to do:** propagate the manifest to three surfaces: (1) machine-readable metadata inside the file, (2) a visible label plus a machine-readable tag on the web page, (3) a header on any API that returns AI-generated content. Each surface is read by a different audience — file metadata survives download and re-upload, the page label is what a human sees, and the header is what an automated client sees.

**Why:** a single surface fails the "transparent to both humans and machines" test. A visible caption disappears when the image is embedded elsewhere; file metadata is invisible to a reader. You need both.

**Example — image metadata.** The `-overwrite_original` flag stops ExifTool from leaving `.jpg_original` backup files in your build output:

```bash
exiftool -overwrite_original \
  -XMP-dc:Description="AI-generated image. Model: <model-id>. Provenance: /provenance/img-2027-0001.json" \
  -XMP-xmpMM:DocumentID="img-2027-0001" \
  assets/img-2027-0001.jpg
```

**Example — video metadata.** `-codec copy` remuxes without re-encoding, so quality is untouched and the job takes seconds:

```bash
ffmpeg -i assets/clip-2027-0002.mp4 -codec copy \
  -metadata comment="AI-generated video; provenance clip-2027-0002" \
  assets/clip-2027-0002.labeled.mp4
```

**Example — page markup.** One meta tag for machines, one paragraph for readers:

```html
<meta name="ai-generated" content="true">
<p class="ai-disclosure">AI-generated image. <a href="/provenance/img-2027-0001.json">Provenance record</a></p>
```

**Example — API response.** Add these to the response builder for any endpoint that returns generated text, image, or audio:

```text
X-AI-Generated: true
X-AI-Provenance: /provenance/img-2027-0001.json
```

For providers that support embedded provenance manifests, sign the file with `c2patool` instead of plain XMP — flag names and the signing-key setup change between releases, so **check the current syntax in the tool's documentation** and record the version you pinned.

**How to check:** run `exiftool -s3 -XMP-dc:Description assets/img-2027-0001.jpg` and confirm the description line appears. Run `curl -sI https://<your-domain>/api/render | grep -i x-ai-generated` and confirm `x-ai-generated: true`; replace `<your-domain>` with the hostname of your staging or production environment.

## Step 3. Verify end-to-end and enforce in CI
![Illustration: Step 3. Verify end-to-end and enforce in CI](https://ai-release.org/guides/img/markirovka-ii-kontenta-2027-cheklist-en-3.jpg?v=1793342127)


**What to do:** turn the manual checks into one script and run it on every commit, so an asset that lost its label during a re-export fails the build instead of shipping.

**Why:** labels disappear silently. Design tools, image optimizers, and CDN pipelines routinely strip XMP blocks, and nobody notices until a complaint arrives. A failing build is the only reminder that survives a team change.

**Example — `scripts/verify_labels.sh`:**

```bash
#!/usr/bin/env bash
set -euo pipefail
npx ajv-cli validate -s schema/label.schema.json -d "manifests/*.json" --errors=text
sha256sum -c checksums.txt
for f in assets/*.jpg; do
  exiftool -s3 -XMP-dc:Description "$f" | grep -q "AI-generated" \
    || { echo "MISSING LABEL: $f"; exit 1; }
done
echo "all labels verified"
```

Wire it into CI as a single step running `bash scripts/verify_labels.sh`, and make that step required for merges. The same pattern — a script plus a scheduled run — extends to bulk re-labeling of large archives; see [AI agents for task automation: a guide](https://ai-release.org/guides/ii-agenty-dlya-avtomatizacii.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) for how to wrap it as a repeatable job, and [AI for programmers: coding assistants in 2026](https://ai-release.org/guides/ii-dlya-koda.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) if you want help generating the CI config.

**How to check:** run the script and confirm three lines appear in order — `manifests/img-2027-0001.json valid`, `assets/img-2027-0001.jpg: OK`, `all labels verified`. Then copy an asset, strip its metadata with `exiftool -all= assets/test-unlabeled.jpg`, re-run, and confirm you get `MISSING LABEL: assets/test-unlabeled.jpg` followed by `echo $?` printing `1`.

Final checklist before you consider the rollout done:

- [ ] Every AI-touched asset has a manifest with `model_id` filled in from the API response, not from memory.
- [ ] `assets/*` are labeled at the file level and re-checked after every optimizer pass.
- [ ] Web pages carry both the visible disclosure and the `ai-generated` meta tag.
- [ ] API endpoints return `X-AI-Generated` and a link to the provenance record.
- [ ] The verification script runs in CI and blocks merges on failure.

## Troubleshooting

**Problem. The validator rejects manifests that were fine yesterday.**
- Ошибка: `jsonschema.exceptions.ValidationError: 'ai_generated_share' is a required property`
- Причина: someone tightened `required` in `schema/label.schema.json` before the older manifests were backfilled.
- Решение: backfill the field in every manifest, then re-run. Inspect one file first with `python3 -m json.tool manifests/img-2027-0001.json | grep ai_generated_share`; if it is missing, add `"ai_generated_share": 1.0` for fully generated assets, or `0.0` for human-only, and re-run `bash scripts/verify_labels.sh`.

**Problem. The schema validates with Python but the CI step fails.**
- Ошибка: `Error: can't resolve reference #/definitions/provenance from id #`
- Причина: `ajv-cli` resolves `$ref` differently from Python's `jsonschema`; the referenced definition has no `$id`, so ajv cannot find it when loading a single schema file.
- Решение: inline the definition into `schema/label.schema.json`, or add an `$id` to the referenced block. To unblock the build while you fix it, run `npx ajv-cli validate -s schema/label.schema.json -d "manifests/*.json" --strict=false` and confirm the error disappears.

**Problem. Re-wrapping the video produces an empty output file.**
- Ошибка: `Output file #0 does not contain any stream`
- Причина: an earlier `-map` flag in the command selected a stream index the input does not have, so FFmpeg has nothing to copy.
- Решение: drop the `-map` flag, or set `-map 0` to take all streams from the first input. Re-run and confirm the file exists with `ffprobe -v error -show_entries format_tags=comment -of default=nw=1 assets/clip-2027-0002.labeled.mp4`, which should print `TAG:comment=AI-generated video; provenance clip-2027-0002`.

## Summary and next steps

Build the manifest first, propagate it to file metadata, page markup, and API headers, then lock all three behind a CI check that fails on unlabeled assets. Next, extend the same schema to your existing asset archive, starting with the highest-traffic pages.

## FAQ

**Does a metadata field inside the file satisfy the disclosure requirement?**
Machine-readable metadata alone is normally not enough — it is invisible to a reader, so pair it with a visible label. The exact required wording and any exemptions must be confirmed against the current regulation text.

**What happens to the label when a user downloads and re-uploads the file?**
XMP metadata often survives a download but is stripped by editors and social platforms. Treat the manifest plus your published provenance URL as the durable record, and re-apply labels whenever an asset re-enters your pipeline.

**Do we have to label content that is only partly AI-generated?**
Record the proportion in `ai_generated_share` in the manifest and label according to the scope rules that apply to you. The threshold values and category rules are **check with the documentation** — confirm them with legal counsel rather than guessing.

**Can we retro-fit labels to content published before March 2027?**
Yes. Generate manifests from your existing asset records, run the same ExifTool and page-tagging steps in bulk, and add the verification script to the cron job. Start with assets that are still live and high-traffic.
