Export your compliance paperwork
Read this if you run a Laidline deployment and answer for its transparency duties: EU AI Act Article 50(2), the EU Code of Practice on Transparency of AI-Generated Content, or California Business and Professions Code sections 22757.2 and 22757.3. You will set the fields the paperwork quotes, export one signed bundle, check it, and know what each duty gets from it. Skip if you only want the bundle's file format (Evidence log). Time: 20 minutes. Needs: a deployment that has marked outputs (its
laidline.yamland evidence log),pip install laidline; add thekmsextra when you sign with AWS KMS or Vault, and theevidenceextra when your evidence log is in S3 or Postgres.
What Laidline produces
One command, laidline evidence export, writes one zip, the export bundle. It holds three kinds of paperwork.
The evidence. Everything in the bundle is covered by a manifest that your signing key signs.
| In the bundle | What it is |
|---|---|
streams/ |
Your evidence log: one entry per mark attempt, hash-chained. An entry holds output hashes, a perceptual fingerprint and counts, never a prompt or media. |
checkpoints/ |
Signed, timestamped chain heads. They prove when the log reached each entry. |
certs/ |
Your signing certificates, retired keys included, and the timestamp authority roots. |
fp/ |
The fingerprint record each keyed audio mark commits to (15,469 bytes each). |
manifest.json, manifest.sig |
Every file's SHA-256, signed with your active key. |
verify.py, README.txt |
A standalone checker that runs on Python 3.10 or newer without Laidline installed. |
licence/ |
Your licence and issuer certificate, on a registered deployment. |
The statement texts. Laidline fills three texts from your configuration and your log. They follow the numbering of the final EU Code of Practice of 10 June 2026.
| Text | Where | What it states |
|---|---|---|
| Marking method | summary.md, "Marking method (Measures 1.1 and 4.1)" |
The two layers on each image, audio file and video (Content Credentials signed with your certificate and timestamped, and the watermark per medium with your issuer number); text and streamed audio carry the watermark only. It also describes the evidence log (Sub-measure 1.1.3) and robustness testing (Measure 4.2), and names your detection endpoint. |
| Detection access | summary.md, "Detection access (Measures 2.1 and 3.4)" |
Your detection endpoint, the detection software, interoperability (C2PA, the issuer number readable without a key, your certificate's SHA-256), the log streams and last checkpoint, and that the bundle is what you give a market surveillance authority on reasoned request (Measure 4.4). A coverage line gives each month's private, shared and self-test mark counts. |
| Provenance clause | compliance/licence-clause.md |
A clause for your terms of service: outputs carry machine-readable provenance as Article 50(2) requires; image, video and audio outputs carry the latent disclosure of section 22757.3(a); nobody may remove the marks except for legitimate processing (Code Measure 1.2); where your free check is. |
The marking-method and detection-access texts end with the line "A plain guide, not legal advice; you remain responsible for your own compliance." The clause opens with a note that says the same, for you to delete before you paste the clause.
The robustness figures. The marking-method text quotes measured figures that ship with the package. Which ones depends on your configuration:
| Medium and scheme | What the text quotes |
|---|---|
| Image, PixelSeal (the default) | No figures. The text says that no packaged robustness or phantom-rate result exists for PixelSeal, and that a decoded payload alone never grades above C. |
| Image, TrustMark | Exact payload recovery for variant Q on 493 of 500 COCO photos after common edits, and the phantom-decode rate on 5,000 unmarked photos (the Q/B/C chain decodes a payload from 0.16 percent of them). |
| Audio (on by default) | AudioSeal detection on 500 LibriSpeech speech clips after codecs, at your watermark.audio.detect_threshold with its false-positive rate on 530 unmarked clips; that the unkeyed streaming mark does not survive a simulated phone leg; and the real phone-call result (100 calls). |
| Video (off by default) | VideoSeal 1.0, or keyframe TrustMark, on 43 test sequences after re-encodes, crops and frame drops; 0 of 43 unmarked clips decoded to a payload. |
| Text | Nothing: the marking-method text does not cover text. |
The robustness figures above were measured before the 2026-10 null campaign (the photo, clip and 530-clip audio counts are
earlier runs). The campaign's false-positive figures are published in Benchmarks and packaged as null
data (laidline.compliance.nulls, files compliance/data/nulls_<medium>.json), which the detect caveats quote: PixelSeal accepted a payload on 0 of 38,500 unmarked photos
(images); no read showed a mark on any of 4,854 unmarked clips
(video); an unkeyed deployment showed public presence on 0.18 percent of
5,000 unmarked single utterances and 7.25 percent of 4,979 unmarked music clips
(audio); text grade B fired on 0 of 40,000 reads
(text). If you quote false-positive rates to a regulator or a customer,
quote the campaign's.
Before you start
-
Check that the deployment can read its configuration and its licence state:
laidline licence status
Expected output (an unregistered deployment under the free cap):
State: free_within_cap plan: none ... marks this month: private 0, shared 2 self-test marks: 0 ...
Under the free cap of 1,200 credits a month the full paperwork exports on any deployment. Over it, the paperwork needs a licence that includes it (Licensing).
-
Check that the signing key the export signs with has a certificate:
laidline keys list
The
activerow must show acert_sha256. In production the key is in AWS KMS or Vault and the certificate comes from a CA on the C2PA trust list, with your organisation in its subject (Certificates). -
Register the deployment if its marks must name you. On a trial issuer number every mark is a shared mark: detection grades its watermark D and it does not identify you (Licensing).
1. Set the fields the paperwork quotes
Edit these keys in laidline.yaml:
system: name: my-image-model # the AI system the texts name version: "2.1" model_id: my-image-model-2.1 # optional; shown after the version provider_legal_name: Example Org # the provider; laidline init writes --org here detect: public_url: https://detect.example.com/detect # your public /detect, quoted under Measure 2.1 and in the clause signing: tsa: mode: per_asset # every manifest timestamped (the default) required: true # no manifest without a timestamp (the default)
What just happened: the texts now name your system, your organisation and your detection endpoint. A key you
leave unset prints "not specified" (provider, model id) or "not configured (set detect.public_url)" in the texts.
The certificate fields (algorithm, subject, SHA-256) come from your signing configuration, and the export reads the
first sink under evidence.sinks, the authoritative one (Configuration).
With signing.tsa.mode: checkpoint_only, manifests carry no timestamp of their own: summary.md gains a
"Timestamps" section that says so, and the clause leaves out the time and date of creation from the latent disclosure.
2. Export the bundle
laidline evidence export --out evidence-2026-10.zip --from 2026-10-01 --to 2026-10-31
Expected output:
{
"out": "evidence-2026-10.zip",
"streams": 2,
"entries": 4,
"checkpoints": 2,
"unanchored": 0,
"signed": true,
"records": {
"included": true,
"count": 1,
"missing": 0
},
"warnings": []
}
What just happened: Laidline read October's entries from your authoritative sink, extended each stream to the
checkpoint that anchors it, filled the three texts and signed the manifest with your active key. No watermark model
loads. "signed": true and an empty warnings list are what you want. Without --from and --to the whole log is
exported; --sink N reads another sink.
3. Read the statement texts
unzip -p evidence-2026-10.zip summary.md
Expected output (trimmed):
# Laidline evidence bundle - AI system: my-image-model version 2.1 (my-image-model-2.1) - Provider: Example Org ... ## Marking method (Measures 1.1 and 4.1) ### Marking method and compliance process (EU Code of Practice on Transparency of AI-Generated Content, Measures 1.1 and 4.1) ... 1. Digitally signed metadata (Sub-measure 1.1.1): C2PA manifest recording that my-image-model created or edited the content, signed with es256 under certificate CN=my-image-model dev,O=Example Org (SHA-256 f7b9...), timestamped by http://timestamp.digicert.com (per asset). The manifest is written on image, audio and video files. Streamed audio carries the audio watermark only: the manifest is skipped for streams. ... ## Detection access (Measures 2.1 and 3.4) ... - Detection endpoint (Sub-measure 2.1.1, point (iii)): https://detect.example.com/detect (free of charge; a per-address rate limit applies; schema laidline.detect/1).
What just happened: you read the texts as an authority will. The subject above is the development certificate
that laidline init writes; in production it is your trust-list certificate. Check every name, the certificate
subject and the endpoint. A line that reads "not specified" or "not configured" sends you back to step 1; export again after the fix.
4. Put the clause in your terms
unzip -p evidence-2026-10.zip compliance/licence-clause.md
Expected output (trimmed):
_Note for the provider, delete before use: ..._ ## Provenance marking (clause for the provider's terms of service) Outputs generated by my-image-model carry machine-readable provenance information: an imperceptible watermark and, on image, audio and video files, a signed C2PA content credential, as Article 50(2) of Regulation (EU) 2024/1689 requires. ...
What just happened: you have the non-removal clause that Code Measure 1.2 asks for, with its exception for legitimate processing, filled with your system name and detection URL. Delete the note, then paste the clause into your acceptable use policy, terms or the documentation that ships with your system.
Check it worked
laidline evidence verify evidence-2026-10.zip
... checkpoint web-1-3f9a2c [1, 1]: head ok, signature ok, tsa ok ... RESULT: OK
RESULT: OK and exit code 0 mean the hashes, the chain, the checkpoint signatures and the timestamps all check. The
recipient runs the same checks with python3 verify.py evidence-2026-10.zip from inside the bundle, without
Laidline.
What each duty gets from the bundle
The duties fall into three steps: mark every output, let anyone check it, prove it on request.
| Step | Duty | What the bundle gives | What stays with you |
|---|---|---|---|
| Mark | EU AI Act Art. 50(2); Code Measure 1.1 | The marking-method text names both layers per medium; the log shows each mark attempt, hash-chained, with signed and timestamped checkpoints (Sub-measure 1.1.3). | The provider's duty itself. Sending every output through Laidline: an output that bypasses it leaves unmarked. Keeping audio and video watermarking on if you generate them (video is off by default). Showing that the marks are effective, reliable, robust and interoperable for your content. |
| Mark | Code Measure 1.2 | The provenance clause, with the legitimate-processing exception. | Publishing the clause. Processing after Laidline (resizing, a CDN that strips metadata). |
| Mark | Cal. B&P §22757.3(a), latent disclosure | The clause lists what the disclosure conveys; each manifest carries it (your certificate's subject, system name and version, timestamp, a unique identifier, created or altered); the log ties each asset to its entry. | Keeping per_asset timestamps, or the time and date are missing. Deciding with counsel whether your certificate subject counts as your name. From 1 January 2029, saying whether your system is assistive technology: Laidline carries no field for it. |
| Check | Code Measure 2.1, Sub-measure 2.1.1 | The detection-access text names your endpoint and laidline detect, both free in every licence state. |
Running /detect where users in the Union reach it and setting detect.public_url. Lifting the default rate limit of 60 requests a minute per address for authorities, researchers and the other bodies the Code lists. |
| Check | Code Measure 3.4 | The text records C2PA manifests, the issuer number readable without a key and listed in Laidline's signed directory, and your certificate's SHA-256. | Your own interoperability solution by 2 February 2027. Publishing your detection URL in the directory (laidline register --detect-url). |
| Check | Cal. B&P §22757.2, verification tool | The detection-access text records your free endpoint, which takes an upload and is an API. | Offering the tool to your users, collecting their feedback, and a clear link to it in your interface. |
| Prove | Code Measure 4.1 | The two texts, as documentation of your marking and detection. | The compliance process that covers every measure and stays up to date. |
| Prove | Code Measure 4.2 | The robustness paragraph with the packaged figures. | Testing before you place the system on the market and regularly after, on content like yours. laidline bench reruns the measurements once you install laidline-bench. |
| Prove | Code Measure 4.4 | The bundle and its verify.py: an authority checks it without Laidline. |
Answering the request and giving access to your deployment. Laidline holds none of your logs, media or keys, so it cannot answer for you. |
Limits
- Laidline does not decide whether you comply, whether you are a provider under Article 50 or a covered provider under section 22757, or which dates apply to your system (laidline.com/compliance lists the dates). The texts are a plain guide, not legal advice; take them to your counsel.
- The paperwork describes one deployment. Each deployment, with its own
laidline.yaml, exports its own bundle. - Under the default image scheme, PixelSeal, the bundle quotes no image robustness figures. PixelSeal's measured figures are in Grading and Benchmarks.
- The audio paragraph quotes AudioSeal presence detection on speech. The keyed audio payload that names you is read differently; its figures are in Grading. The unkeyed streaming mark (the speech proxy's streaming mode, Pipecat, LiveKit) was detected in 0 of 500 clips after G.711 mu-law, so it does not survive a phone leg (Robustness); a keyed stream's payload reads through simulated G.711 (Audio).
- VideoSeal recovers the exact payload after an H.264 re-encode at crf 28 in 23 of 43 clips (53.5 percent) (Robustness). No mark survives every edit, and Laidline does not claim the pair of layers is "extraordinarily difficult to remove".
- Text marks are not described in the texts, and text carries a watermark only, with no signed metadata.
- On a trial issuer number, or for shared marks over the free cap, the watermark names Laidline's shared pool, not you; the signed manifest still names your certificate.
- Over the free cap without a licence, or under a licence without the paperwork feature, the bundle still exports complete and verifiable, but the texts, the clause and the figures read "not included: licence required". Your raw log stays on your disk either way. A lawful order for your records gets a free licence for exactly the months it demands (Lawful orders).
- Results never state that content is human-made; the texts say so too.
Troubleshooting
| Message | Cause | Fix |
|---|---|---|
Error: no configuration file found; pass a path, set LAIDLINE_CONFIG, or run laidline init ... |
The command runs outside the deployment's directory. | Pass --config /path/to/laidline.yaml or set LAIDLINE_CONFIG. |
Invalid value for '--from': --from/--to must be YYYY-MM-DD or an ISO 8601 UTC timestamp |
A date that does not parse, such as month 13. | Use 2026-10-01 or 2026-10-01T00:00:00Z. |
Warning unsigned:no_signing_certificate |
signing.cert_chain_path cannot be read. The bundle is unsigned. |
Fix the path, then export again. |
Warning unsigned:signing_key_does_not_match_certificate |
The certificate was issued for another key. | Run laidline keys list and install the certificate for the active key. |
Warning unsigned:<ExceptionType>, for example a KMS or Vault error |
The signing backend could not be reached. | Check credentials and network, then export again. |
The texts read not configured (set detect.public_url) |
detect.public_url is unset. |
Set it (step 1) and export again. |
The texts read not specified |
system.provider_legal_name or system.model_id is unset. |
Set them (step 1) and export again. |
summary.md opens with UNLICENSED USE; the texts read not included: licence required |
A month in the range is over the free cap and no licence covers it. | Register or install a licence (Licensing), then export again. |
error: the tenant filter 'acme' needs the tenant_reports feature ... (exit code 3) |
--tenant needs a licence with per-tenant reports. |
Export without --tenant, or see laidline.com/licensing. |
Warning licence_check_failed:<type> |
The licence code failed. Nothing is withheld because of it. | Run laidline licence status and fix what it reports. |
Reference
- Evidence log: the bundle layout, the HTTP export route and
verify.pyin full. - Licensing: the free cap, what is locked over it and the coverage line.
- Certificates: production keys, trust-list certificates and timestamps.
- Grading: what a detection result means.
- Robustness: every measured rate behind the quoted figures.
- Configuration: every key named on this page.
- laidline.com/compliance: the duties, the dates and what Laidline emits for each.