How to download German federal court decisions (BGH, BVerfG, BAG) as JSON and Markdown for legal RAG A developer built an Apify actor that converts German federal court decisions from rechtsprechung-im-internet.de into structured JSON and Markdown for legal RAG pipelines. The tool parses the portal's 23.5 MB XML index of 84,835 decisions and per-decision ZIP/XML files into fields such as Leitsatz, Tenor, Tatbestand, Gründe, Normen, Aktenzeichen and ECLI, with filters for court, date range, document type and keywords. A test run on 6 November 2026 returned three decisions in 45 seconds, and the developer recommends chunking along section boundaries while carrying case metadata into every chunk for citation. 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 https://apify.com/fast data asap/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 loading 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: python 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 downloading 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 https://apify.com/fast data asap/german-federal-court-decisions . Data: rechtsprechung-im-internet.de Federal Ministry of Justice , fetched 5–6 November 2026. This article is about data access, not legal advice.