Back to use cases
builderHalf day

Cross-Border Case Law Comparison

Use semantic search to find functionally equivalent case law across multiple European jurisdictions for comparative legal analysis.

APISemantic SearchComparative Law

What you'll build

Comparative law research is one of the most time-consuming tasks in cross-border legal practice. A question like "How do different EU member states handle the right to be forgotten?" requires searching multiple national court databases, each with its own language, citation format, and search interface. The Moonlit API's semantic search makes this dramatically easier. Because it searches by meaning rather than keywords, you can use a single natural-language query to find functionally equivalent rulings across jurisdictions -- even when the legal terminology differs between Dutch, German, French, and English-language courts. This tutorial builds a Python tool that takes a legal concept, searches across multiple jurisdictions simultaneously, groups results by country, and produces a structured comparison table showing how each jurisdiction approaches the same legal issue.

Architecture


                    ┌────────────────────┐
                    │  Legal Concept    │
                    │  (natural lang.)  │
                    └─────────┬──────────┘
                              │
           ┌──────────┼──────────┐
           ▼          ▼          ▼
  ┌─────────┐ ┌─────────┐ ┌─────────┐
  │  NL     │ │  EU     │ │  DE     │
  │ semantic│ │ semantic│ │ semantic│
  │ search  │ │ search  │ │ search  │
  └────┬────┘ └────┬────┘ └────┬────┘
       │          │          │
       └──────────┼──────────┘
                  ▼
  ┌──────────────────────────┐
  │  Comparison Table       │
  │  grouped by jurisdiction│
  └──────────────────────────┘

Prerequisites

  • A Moonlit API key
  • Python 3.10+
  • pip install requests tabulate

Step-by-step

1

Define the comparison query

Write a natural-language description of the legal concept you want to compare. Semantic search works best with complete questions or descriptions rather than keyword fragments.

import requests
from collections import defaultdict

MOONLIT_KEY = "your-api-key"
BASE_URL = "https://api.moonlit.ai/v1.1"

QUERY = "What standard of proof is required for interim injunctions in intellectual property disputes?"
JURISDICTIONS = ["Netherlands", "Germany", "European Union", "Belgium"]
2

Search each jurisdiction

Run a semantic reranked search for each jurisdiction separately. This ensures you get the top results from each country rather than having one jurisdiction dominate the combined results.

def search_jurisdiction(query: str, jurisdiction: str) -> list[dict]:
    response = requests.post(
        f"{BASE_URL}/search/semantic_search",
        headers={
            "Ocp-Apim-Subscription-Key": MOONLIT_KEY,
            "Content-Type": "application/json",
        },
        json={
            "query": query,
            "jurisdictions": [jurisdiction],
            "documentTypes": ["case_law"],
            "num_results": 5,
        },
    )
    response.raise_for_status()
    return response.json()["result"]["results"]

# Collect results by jurisdiction
results_by_jurisdiction = {}
for jur in JURISDICTIONS:
    results = search_jurisdiction(QUERY, jur)
    results_by_jurisdiction[jur] = results
    print(f"{jur}: {len(results)} results found")
3

Build the comparison table

Structure the results into a comparison format showing jurisdiction, court level, key ruling, and ECLI identifier side by side.

from tabulate import tabulate

rows = []
for jur, results in results_by_jurisdiction.items():
    for r in results[:3]:  # Top 3 per jurisdiction
        rows.append([
            jur,
            r.get("source", "N/A"),
            r["date"],
            r["identifier"],
            (r.get("summary", "")[:120] + "...") if r.get("summary") else "N/A",
        ])

print(tabulate(
    rows,
    headers=["Jurisdiction", "Court", "Date", "ECLI", "Key holding"],
    tablefmt="grid",
))
4

Retrieve full texts for deep comparison

For the most relevant result from each jurisdiction, retrieve the full document text. These can then be passed to an LLM to generate a structured comparative analysis.

def get_top_documents(results_by_jur: dict) -> dict:
    """Retrieve the top result's full text from each jurisdiction."""
    docs = {}
    for jur, results in results_by_jur.items():
        if results:
            top = results[0]
            response = requests.get(
                f"{BASE_URL}/document/retrieve_document",
                headers={"Ocp-Apim-Subscription-Key": MOONLIT_KEY},
                params={"DocumentIdentifier": top["identifier"]},
            )
            if response.ok:
                docs[jur] = response.json()
    return docs

top_docs = get_top_documents(results_by_jurisdiction)
for jur, doc in top_docs.items():
    print(f"\n{'='*60}")
    print(f"{jur}: {doc['title']}")
    print(f"Court: {doc.get('source', 'N/A')}")
    print(f"Summary: {doc.get('summary', 'N/A')[:300]}")

Complete Code

#!/usr/bin/env python3
"""Cross-border case law comparison using Moonlit semantic search."""

import os
import requests
from tabulate import tabulate

MOONLIT_KEY = os.environ["MOONLIT_API_KEY"]
BASE_URL = "https://api.moonlit.ai/v1.1"
HEADERS = {
    "Ocp-Apim-Subscription-Key": MOONLIT_KEY,
    "Content-Type": "application/json",
}


def search_jurisdiction(query: str, jurisdiction: str, n: int = 5) -> list[dict]:
    response = requests.post(
        f"{BASE_URL}/search/semantic_search",
        headers=HEADERS,
        json={
            "query": query,
            "jurisdictions": [jurisdiction],
            "documentTypes": ["case_law"],
            "num_results": n,
        },
    )
    response.raise_for_status()
    return response.json()["result"]["results"]


def retrieve_document(doc_id: str) -> dict:
    response = requests.get(
        f"{BASE_URL}/document/retrieve_document",
        headers={"Ocp-Apim-Subscription-Key": MOONLIT_KEY},
        params={"DocumentIdentifier": doc_id},
    )
    response.raise_for_status()
    return response.json()


def compare(query: str, jurisdictions: list[str]):
    print(f"Query: {query}")
    print(f"Jurisdictions: {', '.join(jurisdictions)}\n")

    all_results = {}
    for jur in jurisdictions:
        results = search_jurisdiction(query, jur)
        all_results[jur] = results
        print(f"  {jur}: {len(results)} results")

    rows = []
    for jur, results in all_results.items():
        for r in results[:3]:
            rows.append([
                jur,
                r.get("source", "N/A"),
                r["date"],
                r["identifier"],
                (r.get("summary", "")[:100] + "...") if r.get("summary") else "N/A",
            ])

    print("\n" + tabulate(rows, headers=["Jur.", "Court", "Date", "ECLI", "Key holding"], tablefmt="grid"))

    print("\n\nDetailed comparison of top results:")
    for jur, results in all_results.items():
        if results:
            doc = retrieve_document(results[0]["identifier"])
            print(f"\n{'='*60}")
            print(f"[{jur}] {doc['title']}")
            print(f"Court: {doc.get('source', 'N/A')} | Date: {doc.get('date', 'N/A')}")
            print(f"Summary: {doc.get('summary', 'N/A')[:400]}")


if __name__ == "__main__":
    compare(
        query="What standard of proof is required for interim injunctions in intellectual property disputes?",
        jurisdictions=["Netherlands", "Germany", "European Union", "Belgium"],
    )

What's next