New: Fetch Articles of Association via API – and Analyze Them with LLMs

12분 분량

The new articles_of_association document type lets you retrieve German company charters and shareholder agreements via API. Learn how to build an LLM pipeline that extracts structured data from registry PDFs – for due diligence, compliance, and LegalTech.

이 글은 영어로 제공돼요.

Articles of association and shareholder agreements are the documents that legal, M&A, and compliance teams actually get stuck on: they define how a company operates, not just that it exists. And these documents have been the biggest gap in most automation projects. You click through the registry portal, download PDFs by hand, save them somewhere, and from that point on, "automation" is effectively over.

That changes now. The fetch-document endpoint of the handelsregister.ai API supports a new document_type: articles_of_association. With a single API call, you can programmatically retrieve the articles of association (AG/Aktiengesellschaft) or the shareholder agreement (GmbH/UG) as an original PDF from the German commercial register (Handelsregister).

A quick note on terminology, since this tends to cause confusion: there is only one new parameter value, articles_of_association, and it covers both. The Handelsregister treats Satzung (articles of association for AGs) and Gesellschaftervertrag (shareholder agreement for GmbHs/UGs) as the same document type.

In this article, we'll walk through how to use the feature, what kind of information lives in these documents, and most importantly: how to build a robust LLM-powered analysis pipeline that turns PDFs into real, structured data.

Why articles of association have been an automation bottleneck

Legally, the situation is straightforward: access to the German commercial register and its filed documents is publicly available for informational purposes. In practice, however, this access runs through web portals that serve PDFs, not through a stable, developer-friendly API.

The problem gets worse as you scale. A single deal is manageable by hand. But when you need to review 50 targets, 300 suppliers, or an entire VC portfolio, "clicking through PDFs" isn't a strategy. You need a pipeline that's reproducible, fault-tolerant, and versionable.

On top of that, many registry documents aren't "LLM-ready" out of the box. Some are text-based PDFs, others are scans or mixed formats where no usable text can be extracted without OCR. The fact that scanned PDF/A documents, despite being widely used in notarial workflows, aren't automatically machine-readable is a well-known issue in registry digitization.

What's typically inside articles of association

Before we get into code, a brief legal primer so you know what to look for when you point an LLM at these documents.

For a GmbH (German limited liability company), the GmbHG (§§ 2, 3) mandates minimum contents for the shareholder agreement: company name and registered office, business purpose, share capital, and the shares each shareholder subscribes to. The agreement must be notarized. For standard cases, there's a simplified procedure using the Musterprotokoll (template protocol), which is practically relevant because these documents tend to be highly standardized and easy to parse automatically.

For an AG (stock corporation), the articles of association are similarly form-bound (§ 23 AktG), with comparable mandatory disclosures about founders, capital, and business purpose.

In practice, however, the actual content goes far beyond the legal minimums, and that's what makes these documents so interesting for due diligence and automation. The following clause clusters appear regularly and lend themselves well to automated screening:

  • Management & representation: sole or joint representation, approval catalogs, references to internal rules of procedure.
  • Profit distribution: standard pro-rata distribution or custom rules.
  • Share transfer restrictions (Vinkulierung): consent requirements for share transfers, often used to protect against unwanted shareholders.
  • Pre-emption rights: existing shareholders get first refusal before shares go to third parties.
  • Drag-/tag-along: exit mechanisms that facilitate transactions or protect (or obligate) minority shareholders.

Drag-/tag-along clauses are a good example of why standard registry extracts (AD/CD) often aren't enough: the existence of the company is clearly documented there, but exit-readiness lives in the contractual details.

Fetching articles of association: document_type=articles_of_association

The new feature is intentionally simple. You use the existing fetch-document endpoint, just with an additional document_type. The currently supported values are: AD, CD, shareholders_list, and now articles_of_association.

The call expects a company_id (the entity ID from search results) and the document_type. On success, you get back a PDF directly.

Minimal example with curl

# Fetch articles of association / shareholder agreement
curl 'https://handelsregister.ai/api/v1/fetch-document?api_key=YOUR_API_KEY&company_id=COMPANY_ID&document_type=articles_of_association' \
  -o articles_of_association.pdf

Minimal example with Python requests

import requests

url = "https://handelsregister.ai/api/v1/fetch-document"
params = {
    "api_key": "YOUR_API_KEY",
    "company_id": "COMPANY_ID",
    "document_type": "articles_of_association"
}
response = requests.get(url, params=params)

with open("articles_of_association.pdf", "wb") as f:
    f.write(response.content)

Python with the official SDK

If you prefer not to assemble requests yourself: the official Python SDK (pip install handelsregister) offers a fetch_document method. You can pass the new type as a string directly.

from handelsregister import Handelsregister

client = Handelsregister(api_key="YOUR_API_KEY")

# 1) Get the company ID via search
company = client.fetch_organization(q="Konux GmbH München")
company_id = company["entity_id"]

# 2) Fetch articles of association as PDF
client.fetch_document(
    company_id=company_id,
    document_type="articles_of_association",
    output_file="konux_articles_of_association.pdf",
)

Practical details that will save you time later

Rate limits. Document downloads are limited to 5 requests per minute (general requests: 60/min). For batch jobs, that means you need to plan for queuing, backoff, and resumability.

Availability. Not every company has these documents available digitally. That's not a bug, it's data reality: availability depends on whether and when the respective registry court has digitized and filed the documents.

Error handling. On success you get a PDF, on error you get JSON with details. So always check both HTTP status and Content-Type before writing bytes to a .pdf file:

import requests

url = "https://handelsregister.ai/api/v1/fetch-document"
params = {
    "api_key": "YOUR_API_KEY",
    "company_id": "COMPANY_ID",
    "document_type": "articles_of_association",
}

resp = requests.get(url, params=params, timeout=60)
content_type = resp.headers.get("content-type", "")

if resp.status_code == 200 and "application/pdf" in content_type:
    with open("articles_of_association.pdf", "wb") as f:
        f.write(resp.content)
else:
    try:
        err = resp.json()
    except Exception:
        err = {"raw": resp.text[:500]}
    raise RuntimeError(f"fetch-document failed: {resp.status_code} {err}")

Technical deep dive: analyzing articles of association with LLMs

Downloading the PDF is the easy part. The real question is: how do you turn it into data? Data that you can process automatically and that holds up when someone audits your work.

Here's a pipeline that has proven reliable for registry PDFs: download → text/OCR → structuring → LLM extraction → JSON → persistence and QA.

PDF → Text: measure first, then OCR

Step 1: Check whether the PDF has a text layer. PyMuPDF (pymupdf/fitz) is fast and flexible for this. Using page.get_text("text", sort=True) gives you plain text in natural reading order.

Step 2: If there's no text, OCR. OCRmyPDF is the pragmatic standard: it adds an OCR text layer to scanned PDFs using the Tesseract engine. In the registry and notarial domain, where PDF/A is prevalent, you'll need this more often than you might expect.

A minimal snippet that combines both:

import subprocess
import pymupdf

def extract_text_with_fallback(pdf_path: str) -> str:
    doc = pymupdf.open(pdf_path)
    chunks = []
    for page in doc:
        chunks.append(page.get_text("text", sort=True))
    text = "\n".join(chunks).strip()

    # Heuristic: if very little text, generate an OCR version
    if len(text) < 1000:
        ocr_path = pdf_path.replace(".pdf", ".ocr.pdf")
        subprocess.run(
            ["ocrmypdf", "-l", "deu", "--deskew", pdf_path, ocr_path],
            check=True,
        )
        doc = pymupdf.open(ocr_path)
        chunks = [p.get_text("text", sort=True) for p in doc]
        text = "\n".join(chunks).strip()

    return text

Text → Structure: paragraphs, not token soup

LLMs are good, but they're not magic. Feed them 40 pages of unstructured OCR text and you'll often get an equally unstructured answer. Two simple rules help:

  1. Split by typical markers like "§", "Abs.", section numbers, and headings (e.g., "Gegenstand", "Stammkapital", "Geschäftsführung"). This maps well to the structure of German legal documents and makes downstream chunking much easier.
  2. Preserve page references. When you flag red flags automatically, you always want to be able to jump back: which page says that? This traceability is also the most important lever against hallucinations in RAG setups.

Prompt engineering for legal PDFs

When extracting from articles of association, prompts work best when they do four things at once:

First: Clearly define what to extract. Open-ended questions like "Summarize the agreement" produce unusable results. Better: a closed list of specific fields (share capital, representation rules, transfer restrictions, etc.).

Second: Have a null strategy. If a clause doesn't exist in the document, the model should return null instead of guessing. Especially for drag-along or Vinkulierung, "not present" is valid and important information.

Third: Require evidence. For each extracted field, a short supporting quote (max 25 words) and the page number. This lets you run automated QA without reading every document manually.

Fourth: Enforce structured output. Free-text responses are readable for humans, but poison for pipelines. You want JSON that flows directly into a database or comparison report.

An example prompt, vendor-agnostic and tailored to German shareholder agreements:

System instruction: You are a legal analyst for German shareholder agreements (Gesellschafterverträge). Use only the provided text. If a piece of information is not clearly stated in the text, set the field to null and do not speculate.

Task: Extract (1) share capital, (2) representation rules, (3) share transfer restrictions (consent/pre-emption/Vinkulierung), (4) drag-along/tag-along (if present).

Output: Return only JSON following the schema below. For each populated field, include at least one short supporting quote (max 25 words) and the page number.

The "only from the text, no external sources" principle is critical because hallucinations in legal contexts are especially costly.

Structured output: JSON you can actually write to a database

"LLM returns JSON" sounds trivial, but in practice it isn't. Pipelines often break on small things: missing keys, strings where you expected numbers, free text in enum fields. Use schema-constrained output (JSON Schema, tools, or structured outputs) so the response is bound to a defined schema.

A practical schema for articles-of-association screening:

{
  "capital": {
    "amount_eur": null,
    "type": null
  },
  "representation": {
    "rule": null,
    "evidence": []
  },
  "transfer_restrictions": {
    "requires_consent": null,
    "pre_emption_right": null,
    "vinkulierung": null,
    "evidence": []
  },
  "exit_clauses": {
    "drag_along": { "present": null, "threshold": null, "evidence": [] },
    "tag_along": { "present": null, "threshold": null, "evidence": [] }
  }
}

Why threshold? Because drag-/tag-along clauses in practice often hinge on specific triggers (majority thresholds, minimum offer prices, share classes). That's exactly what an investor needs to know.

Batch analysis: portfolio scan instead of one-offs

Once you can fetch articles_of_association as PDFs, the next logical step is batch screening. A typical VC scenario: "Analyze the articles of association for all portfolio companies, check for consistent transfer and exit clauses, and flag deviations."

The pipeline:

  1. Load list of company_ids from CRM or portfolio database.
  2. For each ID: fetch-document with document_type=articles_of_association.
  3. PDF → text/OCR with quality checks (page count, text length).
  4. LLM extraction → JSON (schema-constrained).
  5. Persist, run QA (evidence, pages, versioning, timestamp).

Keep the document rate limit (5/min) in mind and build for idempotency: if the job crashes, it shouldn't restart from scratch.

RAG approach: articles of association as a knowledge base

If you want to go beyond fact extraction and actually answer questions ("Can shareholder X sell without consent?"), RAG is often the better approach. Instead of just storing a JSON, you index the text (chunked) along with metadata like company_id, retrieval date, document type, and page numbers.

This metadata discipline and pipeline hygiene (PDF not empty? Parser returns text?) are what separate a demo chatbot from a system you can stand behind internally.

If you want to go deeper on the RAG side: the article Handelsregisterauszüge in RAG-Projekten shows a practical approach for registry PDFs. And if you prefer working in an "LLM-native" style, take a look at the MCP Server from handelsregister.ai.

Use cases: where articles_of_association delivers immediate value

VC due diligence. Early in the review process, teams check whether special rights, pre-emption rights, or exit clauses exist and whether they could block the next round or an exit. With articles_of_association, you can automate this screening and escalate only the flagged cases to legal.

Compliance and KYB. For KYC/onboarding workflows, registry data is the primary source for existence, legal form, and representation rules. The articles of association can contain additional rules that go beyond what's in the standard registry extract (AD), such as special consent requirements.

M&A. Drag-/tag-along, Vinkulierung, and pre-emption rights directly impact transaction mechanics and closing risks. When you want to compare 20 targets, you need structured data, not 20 open PDF tabs.

LegalTech products. If you're building tools (deal desk, investor relations, governance dashboards), you can use articles_of_association as a data source for features like a clause finder, deviation scanner, or investor-readiness score. Always with a link back to the original PDF and supporting evidence, because "evidence first" isn't a nice-to-have here, it's a product requirement.

Conclusion: less PDF hell, more automation

With document_type=articles_of_association, you can now programmatically retrieve articles of association and shareholder agreements as original PDFs from the German commercial register, directly through the existing fetch-document endpoint. The rest is engineering: check PDF quality, add OCR where needed, structure the text, extract with LLMs in a schema-compliant way, and back everything up with evidence so results can be verified and fed into downstream workflows.

For context on the other document types: the article Handelsregister-Dokumente per API abrufen explains the general document retrieval (AD, CD, shareholders_list) and how to build reproducible processes around it. For getting started with structured company data, IDs, and Python examples, Firmendaten per API is the right starting point.

Get started: Register for an API key, call fetch-document, download your first set of articles, and build your LLM pipeline. Whether you start with a single document or run a batch scan across your entire portfolio is up to you. The API is ready, the tools are open source, and the documents are waiting to be read.