GDPR Art. 28 Contracts with a Local AI
When a company uses an outside service that processes personal data (cloud storage, a payroll system, email), Article 28 of the GDPR requires a contract. Here we build a register of those contracts in Postgres, a draft from a template that a local model helps to fill, and reminders for expiry. The lesson is not legal advice and does not replace a lawyer. The providers in the examples are invented.
01What you'll learn
- What Article 28 of the GDPR requires — from the full official text.
- How to build a register of processors with a stored history of versions.
- How to get a draft from a template without the model inventing legal clauses.
- How to watch contract expiry and receive reminders.
- Where the European Commission's ready-made standard clauses are.
- Where the helper ends and the lawyer begins.
02Before you start
- A machine of the NVIDIA GB10 class (for example ASUS Ascent GX10 or DGX Spark) with Ollama; Postgres and n8n — see the lesson n8n on GX10; Python 3.10 or newer.
- Access to a lawyer or a data protection officer who will review every draft.
- A list of the services you use that touch personal data — including the "hidden" ones (an extension, an add-on, a bot) that nobody has written down.
- The model in the example:
qwen2.5:14b— 9.0 GB, Apache-2.0 licence (ollama.com). Its page mentions 29+ languages, Bulgarian is not listed ⚠️ — so the model fills only short descriptive fields and a person reads them.
03Steps
-
What Article 28 says
Why the law first: everything that follows is organisation around this text. In it "another processor" (paragraphs 2 and 4) is what practice calls a sub-processor. Here is the full text of Article 28 of Regulation (EU) 2016/679 — the official English version from EUR-Lex, read on 03.10.2026:
Article 28 · Processor
1. Where processing is to be carried out on behalf of a controller, the controller shall use only processors providing sufficient guarantees to implement appropriate technical and organisational measures in such a manner that processing will meet the requirements of this Regulation and ensure the protection of the rights of the data subject.
2. The processor shall not engage another processor without prior specific or general written authorisation of the controller. In the case of general written authorisation, the processor shall inform the controller of any intended changes concerning the addition or replacement of other processors, thereby giving the controller the opportunity to object to such changes.
3. Processing by a processor shall be governed by a contract or other legal act under Union or Member State law, that is binding on the processor with regard to the controller and that sets out the subject-matter and duration of the processing, the nature and purpose of the processing, the type of personal data and categories of data subjects and the obligations and rights of the controller. That contract or other legal act shall stipulate, in particular, that the processor:
(a) processes the personal data only on documented instructions from the controller, including with regard to transfers of personal data to a third country or an international organisation, unless required to do so by Union or Member State law to which the processor is subject; in such a case, the processor shall inform the controller of that legal requirement before processing, unless that law prohibits such information on important grounds of public interest;
(b) ensures that persons authorised to process the personal data have committed themselves to confidentiality or are under an appropriate statutory obligation of confidentiality;
(c) takes all measures required pursuant to Article 32;
(d) respects the conditions referred to in paragraphs 2 and 4 for engaging another processor;
(e) taking into account the nature of the processing, assists the controller by appropriate technical and organisational measures, insofar as this is possible, for the fulfilment of the controller's obligation to respond to requests for exercising the data subject's rights laid down in Chapter III;
(f) assists the controller in ensuring compliance with the obligations pursuant to Articles 32 to 36 taking into account the nature of processing and the information available to the processor;
(g) at the choice of the controller, deletes or returns all the personal data to the controller after the end of the provision of services relating to processing, and deletes existing copies unless Union or Member State law requires storage of the personal data;
(h) makes available to the controller all information necessary to demonstrate compliance with the obligations laid down in this Article and allow for and contribute to audits, including inspections, conducted by the controller or another auditor mandated by the controller.With regard to point (h) of the first subparagraph, the processor shall immediately inform the controller if, in its opinion, an instruction infringes this Regulation or other Union or Member State data protection provisions.
4. Where a processor engages another processor for carrying out specific processing activities on behalf of the controller, the same data protection obligations as set out in the contract or other legal act between the controller and the processor as referred to in paragraph 3 shall be imposed on that other processor by way of a contract or other legal act under Union or Member State law, in particular providing sufficient guarantees to implement appropriate technical and organisational measures in such a manner that the processing will meet the requirements of this Regulation. Where that other processor fails to fulfil its data protection obligations, the initial processor shall remain fully liable to the controller for the performance of that other processor's obligations.
5. Adherence of a processor to an approved code of conduct as referred to in Article 40 or an approved certification mechanism as referred to in Article 42 may be used as an element by which to demonstrate sufficient guarantees as referred to in paragraphs 1 and 4 of this Article.
6. Without prejudice to an individual contract between the controller and the processor, the contract or the other legal act referred to in paragraphs 3 and 4 of this Article may be based, in whole or in part, on standard contractual clauses referred to in paragraphs 7 and 8 of this Article, including when they are part of a certification granted to the controller or processor pursuant to Articles 42 and 43.
7. The Commission may lay down standard contractual clauses for the matters referred to in paragraph 3 and 4 of this Article and in accordance with the examination procedure referred to in Article 93(2).
8. A supervisory authority may adopt standard contractual clauses for the matters referred to in paragraph 3 and 4 of this Article and in accordance with the consistency mechanism referred to in Article 63.
9. The contract or the other legal act referred to in paragraphs 3 and 4 shall be in writing, including in electronic form.
10. Without prejudice to Articles 82, 83 and 84, if a processor infringes this Regulation by determining the purposes and means of processing, the processor shall be considered to be a controller in respect of that processing.
💡What to take from the text for the registerThe contract is written (paragraph 9). It describes subject-matter, duration, nature, purpose, type of data and categories of data subjects (paragraph 3). For every other processor there must be authorisation and the same obligations down the chain (paragraphs 2 and 4). So the register keeps, for each provider: the service, the types of data, the versions of the contract and its expiry. -
Two more quotations: fines and supply chains
Fines. Article 83(4)(a) of the same regulation, official English text from EUR-Lex:
Infringements of the following provisions shall, in accordance with paragraph 2, be subject to administrative fines up to 10 000 000 EUR, or in the case of an undertaking, up to 2 % of the total worldwide annual turnover of the preceding financial year, whichever is higher:
(a) the obligations of the controller and the processor pursuant to Articles 8, 11, 25 to 39 and 42 and 43;Article 28 falls within "25 to 39". The actual amount in each case is decided by the supervisory authority.
Supply chains (NIS2). Directive (EU) 2022/2555 is a different act and applies only to entities within its scope — whether you are one is a question for a lawyer. Article 21(2)(d) (official English text from EUR-Lex) says that the risk-management measures shall include at least:
(d) supply chain security, including security-related aspects concerning the relationships between each entity and its direct suppliers or service providers;
The register we build is useful for both — but it is an organisational tool, not evidence of compliance by itself.
-
Ready-made standard clauses
Article 28(6)–(8) allows the contract to be based on standard contractual clauses. The titles of the two Commission acts, as they are on EUR-Lex:
- Commission Implementing Decision (EU) 2021/915 of 4 June 2021 — standard contractual clauses between controllers and processors under Article 28(7) of Regulation (EU) 2016/679 and Article 29(7) of Regulation (EU) 2018/1725.
- Commission Implementing Decision (EU) 2021/914 of 4 June 2021 — standard contractual clauses for the transfer of personal data to third countries pursuant to Regulation (EU) 2016/679.
Read them before you write your own template: your lawyer may prefer them. Whether and when a separate mechanism is needed for a transfer outside the EU/EEA is a question for them — we give no "rule" here.
-
A register in Postgres
Why versions: a contract changes and is renewed; the old versions must remain as evidence. A new version is a new row, and the old one gets the status
superseded. Why the states are computed: "expired" is not a fact you record, it follows from the date. Everything below is an invented example.sql · schema.sql · not runCREATE TABLE processors ( id serial PRIMARY KEY, name text NOT NULL UNIQUE, service text NOT NULL, data_types text, outside_eea boolean NOT NULL DEFAULT false, transfer_basis text, -- filled in together with the lawyer created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE dpa_versions ( id serial PRIMARY KEY, processor_id integer NOT NULL REFERENCES processors(id), version_number integer NOT NULL, content_md text NOT NULL, status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','pending_signature','active','superseded')), signed_date date, expiry_date date, reviewed_by text, -- who did the legal review created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (processor_id, version_number) ); -- one row per provider: the latest version and a computed state CREATE VIEW dpa_dashboard AS SELECT DISTINCT ON (p.id) p.name AS processor, p.outside_eea, v.version_number, v.status, v.expiry_date, (v.expiry_date - CURRENT_DATE) AS days_left, CASE WHEN v.id IS NULL THEN 'MISSING' WHEN v.status <> 'active' THEN upper(v.status) WHEN v.expiry_date IS NULL THEN 'NO_EXPIRY' WHEN v.expiry_date < CURRENT_DATE THEN 'EXPIRED' WHEN v.expiry_date - CURRENT_DATE <= 60 THEN 'EXPIRING' ELSE 'VALID' END AS state FROM processors p LEFT JOIN dpa_versions v ON v.processor_id = p.id ORDER BY p.id, v.version_number DESC; -- versions are never deleted: take the right away from the application -- REVOKE DELETE ON dpa_versions FROM <application-role>;The number 60 is your own choice for "expires soon", not a requirement of the law. This is what the view looks like for invented providers as of 03.10.2026:
Provider Service Expires Days State Outside EEA Provider A Cloud storage 2027-03-01 149 VALID no Provider B Payroll system 2026-11-20 48 EXPIRING no Provider C Staff system 2026-08-15 −49 EXPIRED no Provider D Email 2027-01-10 99 VALID no Provider E SMS notifications — — MISSING yes Row "E" is the most important: a service that touches data with no contract in the register. Those are the cases you look for in the inventory.
-
A draft from a template — the model fills only the descriptive fields
Why this way: the mandatory obligations of the processor were written by the legislator — we do not "translate" them through a model. Copy the text of paragraph 3 verbatim (from "That contract or other legal act shall stipulate…" to the end of point (h) and the last subparagraph) from EUR-Lex into a file
art28_3.txt, in the language of the contract. The model gets only the service description written by a person and returns four short fields; each is marked, and numbers that are not in the description are flagged.python · dpa_draft.py · not run# dpa_draft.py · a draft processing agreement (invented example) import json import pathlib import re import httpx from jinja2 import Template OLLAMA = "http://localhost:11434" MODEL = "qwen2.5:14b" # The text of Art. 28(3) — copied verbatim from EUR-Lex ART28_3 = pathlib.Path("art28_3.txt").read_text(encoding="utf-8") TEMPLATE = Template("""# DATA PROCESSING AGREEMENT — DRAFT Basis: Article 28 of Regulation (EU) 2016/679. **Controller:** <controller> **Processor:** {{ processor }} ## 1. Subject-matter and duration {{ subject_and_duration }} ## 2. Nature and purpose of the processing {{ nature_and_purpose }} ## 3. Type of personal data and categories of data subjects {{ data_types }} {{ data_subjects }} ## 4. Obligations and rights of the controller [to be completed by the lawyer] ## 5. Obligations of the processor (Art. 28(3), verbatim) {{ art28_3 }} ## 6. Other processors [to be completed by the lawyer: Art. 28(2) and (4)] ## 7. Transfers outside the EU/EEA [to be completed by the lawyer: see Chapter V of the regulation and the standard clauses] Date: <date> Signatures: <controller> / {{ processor }} """) SCHEMA = { "type": "object", "properties": { "subject_and_duration": {"type": "string"}, "nature_and_purpose": {"type": "string"}, "data_types": {"type": "string"}, "data_subjects": {"type": "string"}, }, "required": ["subject_and_duration", "nature_and_purpose", "data_types", "data_subjects"], } def fill(service: str) -> dict: prompt = ( "Service description (written by a person):\n" + service + "\n\nFill four fields in English for a draft data processing agreement. " "Use ONLY what is described. If something is not described, write " "'[missing from the description]'. Do not add legal statements, periods or amounts." ) r = httpx.post( f"{OLLAMA}/api/chat", json={ "model": MODEL, "stream": False, "format": SCHEMA, "messages": [{"role": "user", "content": prompt}], "options": {"temperature": 0}, }, timeout=300, ) r.raise_for_status() return json.loads(r.json()["message"]["content"]) def check(fields: dict, service: str) -> list[str]: problems = [] allowed = set(re.findall(r"\d+", service)) for key, val in fields.items(): if not val.strip(): problems.append(f"{key}: empty") extra = set(re.findall(r"\d+", val)) - allowed if extra: problems.append(f"{key}: numbers not in the description: {sorted(extra)}") return problems def draft(processor: str, service: str) -> tuple[str, list[str]]: fields = fill(service) problems = check(fields, service) marked = {k: "[DRAFT — check] " + v for k, v in fields.items()} return TEMPLATE.render(processor=processor, art28_3=ART28_3, **marked), problems if __name__ == "__main__": text, problems = draft( "Provider A", "Cloud storage for the organisation's documents. Staff upload files that " "may contain names and email addresses. A one-year contract.", ) print(text) print("Problems:", problems or "none")Run it first with three invented providers of different kinds and read the result line by line. If a field is wrong, you fix the description, not the text of the law.
-
Reminders in n8n
n8n workflows are stored as JSON and built from nodes; for the details of each node see its page (see Sources). One workflow is enough:
Node Setting Schedule Trigger every morning Postgres query: SELECT processor, state, days_left FROM dpa_dashboard WHERE state IN ('EXPIRING','EXPIRED','MISSING')Send Email to one named responsible person ( <email>): "The contract with … needs attention: state …, days …". The mail password is kept in n8n "Credentials".The reminder is a signal. Renewal, review and signature are human.
-
Limits
- Every draft goes through a lawyer or a data protection officer before it is signed.
- The model does not write clauses and does not decide whether someone is a "processor".
- Do not put more personal data into the register than needed — contact names and email are enough.
- Decisions about transfers outside the EU/EEA and about whether NIS2 applies are legal ones.
04Check
- An inventory of providers has been made, including the "hidden" ones.
schema.sqlis applied;dpa_dashboardreturns one row per provider.- Existing contracts are entered as versions with an expiry.
art28_3.txtis copied verbatim from EUR-Lex.dpa_draft.pyhas been tried with three invented providers; every flagged number has been reviewed.- A lawyer or officer has reviewed every draft.
- Transfers outside the EU/EEA are identified and the basis is recorded with the lawyer.
- The daily reminder reaches a named person.
Quiz
1. In what form is a contract under Article 28 made (paragraph 9)?
2. A processor wants to engage another processor. What does Article 28(2) say?
3. How high can a fine under Article 83(4)(a) go?
4. What does the model fill in this lesson?
05What's next
06Sources
- EUR-Lex: Regulation (EU) 2016/679 (GDPR) — Article 28 and Article 83(4) (read on 03.10.2026).
- EUR-Lex: Directive (EU) 2022/2555 (NIS2) — Article 21(2).
- EUR-Lex: Implementing Decision (EU) 2021/915 · Implementing Decision (EU) 2021/914 — standard contractual clauses.
- Ollama: qwen2.5 🔒 local · structured outputs.
- n8n: Schedule Trigger · Postgres · Send Email.
- Jinja · HTTPX · PostgreSQL: CREATE TABLE.