If you build anything with German law, such as a legal research tool, a RAG assistant for a law firm or a monitor for new rulings, the best free source is the federal government's open-data portal rechtsprechung-im-internet.de. It lists decisions of all federal courts: Bundesgerichtshof (BGH), Bundesverfassungsgericht (BVerfG), Bundesverwaltungsgericht, Bundesfinanzhof, Bundesarbeitsgericht (BAG), Bundessozialgericht and Bundespatentgericht.
The catch is the format. There is no search API. You get a table of contents as one XML file (about 23.5 MB, 84,835 decisions when I fetched it on 5 November 2026) and, for each decision, a ZIP file containing an XML document in the court's own schema.
This guide shows how to turn that into clean JSON you can filter, store and embed.
Disclosure: I built the tool used below, German Federal Court Decisions on Apify. The approach works without it too; the last section shows what it saves you.
Yes. Court decisions are official works under § 5 of the German Copyright Act (UrhG) and are not protected by copyright. The decisions on the portal are already anonymised by the courts. That makes them one of the few large, clean, legally unproblematic text corpora for German-language legal AI.
Each decision XML has structured sections. The ones that matter most:
| Field | German term | What it is |
|---|---|---|
leitsatz |
Leitsatz | The headnote: the legal rule the court establishes. The most valuable text for retrieval. |
tenor |
Tenor | The operative part: what was decided. |
tatbestand |
Tatbestand | The facts of the case. |
entscheidungsgruende /gruende |
Entscheidungsgründe / Gründe | The reasoning. Usually the longest part. |
normen |
Normen | The statutes the decision relies on (e.g. "§ 280 BGB"). |
fileNumber |
Aktenzeichen | The case number, e.g. III ZR 3/26 . |
ecli |
ECLI | The European Case Law Identifier. |
Not every decision has every section. Short procedural decisions (Beschlüsse) often have only a Tenor and Gründe and no Leitsatz.
With the actor, one HTTP call returns the decisions as JSON:
curl -X POST "https://api.apify.com/v2/acts/fast_data_asap~german-federal-court-decisions/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"courts": ["BGH", "BAG", "BVerfG"],
"lastDays": 14,
"maxDecisions": 6,
"includeFullText": true,
"outputFormat": "markdown"
}'
I ran exactly this on 6 November 2026. It took 45 seconds, most of it spent the 23 MB index, and returned three decisions published in that window:
| Court | Date | Aktenzeichen | Type | Words |
|---|---|---|---|---|
| BGH, 7. Zivilsenat | 2026-10-28 | VII ZB 47/25 | Beschluss | 1,872 |
| BGH, 1. Zivilsenat | 2026-10-28 | I ZR 91/25 | Beschluss | 357 |
| BVerfG, 2. Senat | 2026-10-23 | 2 BvR 1421/26 | rejected urgent application | 1,056 |
Two practical notes:
ecli.
Useful filters: dateFrom / dateTo, documentType (Urteil or Beschluss), fileNumber (e.g. "ZR" for civil senates) and keywords, which search the title and headnote, or with keywordsInFullText: true the full text.
Legal text chunks best along its own structure. Don't cut a Leitsatz in half, and keep the case number and court on every chunk so the assistant can cite properly:
import json
def chunks_for(decision, max_words=350):
meta = {
"court": decision["court"],
"date": decision["decisionDate"],
"aktenzeichen": decision["fileNumber"],
"ecli": decision.get("ecli"),
"normen": decision.get("normen") or [],
}
sections = [
("Leitsatz", decision.get("leitsatz")),
("Tenor", decision.get("tenor")),
("Tatbestand", decision.get("tatbestand")),
("Gründe", decision.get("entscheidungsgruende") or decision.get("gruende")),
]
for name, text in sections:
if not text:
continue
words = text.split()
for start in range(0, len(words), max_words):
part = " ".join(words[start:start + max_words])
yield {**meta, "section": name, "text": part}
decisions = json.load(open("decisions.json"))
chunks = [c for d in decisions for c in chunks_for(d)]
print(len(chunks), "chunks")
Two tips that make a measurable difference in retrieval:
normen as metadata.
A daily schedule with lastDays: 7 and the courts you care about, plus deduplication on ecli in your vector store, keeps the corpus current. For topic alerts, add keywords (for example ["DSGVO"] or ["Kündigung"]) and send new hits to Slack or e-mail with n8n or Make.
Doing this by hand means down and parsing the 23.5 MB index, mapping court names and senates, fetching and unzipping one archive per decision, parsing the court XML schema, converting its formatting to Markdown, and handling the portal's occasional stalls. The actor does all of that, retries stalled downloads, and only charges for decisions it actually returns: $0.001 per decision. Failed downloads are free.
The README has the full field list and more examples: German Federal Court Decisions on Apify.
Data: rechtsprechung-im-internet.de (Federal Ministry of Justice), fetched 5–6 November 2026. This article is about data access, not legal advice.