Blog / Tooling
Tooling · 6 min read · June 28, 2026

GDPR Article 30 for AI: Which Data Went to Which Provider?

TL;DR. If your Laravel app sends personal data to an AI provider, GDPR Article 30 expects you to keep a record: what categories of data, to which recipient, transferred where, under what safeguard, kept for how long. Most teams keep this in a spreadsheet that is out of date the day it is written, and it almost never reflects the subprocessor chain behind the provider. This post shows how to generate that record automatically from your real AI traffic, recording categories and hashes only, never the data itself. The collector is free and open source. It documents and evidences; it does not make you compliant, and it is not legal advice.

The invisible third party

When you call OpenAI, you are not sending data to one company. You are sending it to OpenAI and to the subprocessors they engage to run the service, which their own documentation lists and updates, including infrastructure providers like Microsoft (OpenAI Data Processing Addendum). Anthropic, Google, and the rest each have their own chain.

Article 30 of the GDPR asks a controller to maintain records of processing activities, and those records must cover the purposes, the categories of data and of recipients, transfers to third countries and their safeguards, and retention (Art. 30 GDPR). The catch that trips up AI features specifically: the record should reflect the full recipient chain, not just the brand name on the API. If there is ever a breach or a data-subject request, "we send some data to OpenAI" is not an answer. You need to know which categories of personal data went to which provider, processed in which region, under which contract.

Why the spreadsheet fails

The standard control is a hand-maintained RoPA. It fails for AI traffic for three reasons:

  • It is written once and drifts. A developer ships a new AI feature on a Tuesday and no one updates a compliance document to match. The record and the reality diverge immediately.
  • It records intent, not fact. It says what you believe you send. It cannot say what a prompt actually contained at 14:32 last Thursday, which is the thing an auditor or a regulator asks about.
  • It stops at the provider. It rarely captures the subprocessors, the processing region, or the retention window, because a human filling a template does not have those facts at their fingertips for every call.

The fix is to stop describing processing from memory and start recording it from the traffic itself.

A record generated from real calls

laravel-ai-ledger is a processing ledger for AI traffic. It subscribes to the signal the egress guard already emits for every captured AI request, and turns each one into a category-only processing record.

composer require laravelsecurityaudit/laravel-ai-ledger
php artisan migrate

It installs alongside laravel-ai-egress-guard, which provides the capture signal, and from then on every AI call becomes a record.

What a record contains, and what it never does

For each call the ledger writes: the subprocessor and its company, the processing region, sub-subprocessors, the purpose (configured per route), the categories of personal data detected, a pseudonymous data-subject reference, the legal basis, whether it is a third-country transfer and the safeguard, which categories were redacted before send, the provider retention, and a local hash chain.

The point that makes this safe to run: it records categories and hashes only, never the data itself. A record says, in effect, "an email address and a payment card were sent to OpenAI in the EU region, the card redacted before send, under standard contractual clauses, with a 30-day retention." It never stores the email or the card. The provider facts come from a subprocessor registry you configure; the categories come from the egress guard's findings.

One command produces the document

php artisan ai-ledger:export --type=ropa --format=markdown
php artisan ai-ledger:export --type=register --format=json --output=subprocessors.json

ropa is the Article 30 record grouped by subprocessor. register is the subprocessor register itself. You walk into a review with a document generated from what your application actually did, not from what someone remembered to write down.

Tamper-evidence, and where the free line sits

Each record carries a short hash of the one before it, a chain. If an earlier record is altered, the chain no longer reconciles and the change is visible. The records are append-only by design.

Be precise about what that proves on its own. A chain your own application keeps shows you did not casually edit a record, but a strict auditor can fairly observe that you control the whole chain and could in principle rebuild it. Making the evidence independent, counter-signed by a party your app does not control, is a separate layer, and it is on the roadmap rather than in this free package. The free collector gives you the complete, automatically generated record and register with local tamper-evidence, running entirely on your infrastructure with nothing leaving your perimeter.

What this is, and is not

This is engineering that documents and evidences processing. It does not make your application compliant, and it is not legal advice. Have a DPO or privacy counsel own the legal content, the purposes, the legal bases, the safeguards, and review the output. What the ledger removes is the worst part of the job: assembling, by hand, an accurate picture of which data went where. That part a machine should do, from the traffic, every time a call is made.

A record in plain language

A single ledger entry, described in words rather than columns, reads like this: on a given date, for the purpose "summarise support ticket," your app sent the categories email address and payment card to OpenAI, company OpenAI, processed in the EU region, with the payment card redacted before send, under standard contractual clauses, the provider retaining data for 30 days, on behalf of a pseudonymous subject reference. No email, no card, no ticket text is stored. That is the level the record operates at: enough to answer the Article 30 questions, nothing that would make the ledger itself a liability.

Zero-retention, transfers, and data-subject requests

Two facts the record surfaces that spreadsheets usually miss. First, retention: providers typically hold API data for a window unless you have a zero-retention agreement, and your record should reflect which arrangement actually applies to each provider. Second, transfer: a call to a US-processed provider from an EU controller is a third-country transfer that needs a safeguard, and the record names it.

The same record pays off when a data-subject access or erasure request arrives. Because every entry carries a pseudonymous subject reference and the categories involved, you can state which providers received that subject's categories of data rather than guessing. That is the difference between a defensible answer and a scramble.

Frequently asked questions

Does the ledger store personal data?

No. It records categories and hashes only. The actual prompt content never enters the ledger.

Is this a substitute for legal advice?

No. It generates and evidences the record from real traffic. A DPO or privacy counsel must own the legal content and review the output.

What if my subprocessor facts are wrong?

You configure the registry, so verify it against each provider's current terms. Keeping that dataset current is the part a maintained feed is meant to take off your hands.

Can I export for an auditor?

Yes, as Markdown or JSON, either the Article 30 record grouped by subprocessor or the subprocessor register itself.

Generate your first record

If you already run the egress guard, the ledger is two commands away from a real RoPA:

composer require laravelsecurityaudit/laravel-ai-ledger
php artisan migrate
# run your app or your suite so calls are captured, then:
php artisan ai-ledger:export --type=ropa --format=markdown

laravel-ai-ledger is MIT licensed and completes the family: ai-lint for exposed keys, the egress guard for what leaks at runtime, and the circuit breaker for runaway spend. The first three protect the app. This one lets you prove what it did.

This is defensive tooling and documentation support, not legal advice, and not affiliated with or endorsed by Laravel, Laravel LLC, or any AI provider. Verify your subprocessor and retention facts against each provider's current terms.

Know exactly which data goes where.

A senior engineer reviews your AI integration, data flows, and compliance posture. Fixed price, every finding with a fix.