Case Statistics API

This document describes the API endpoints for retrieving aggregated case statistics, designed for plotting and data analysis.

Overview

The statistics API provides aggregated case counts with date-bucketed time series. A root endpoint returns the overall time series, and four sub-endpoints break down counts by a specific dimension (country, state, court, or source), where each result item includes its own total and date buckets.

All endpoints default to the last year with monthly buckets.

Endpoints

Endpoint

Description

GET /api/cases/stats/

Overall total and date-bucketed time series

GET /api/cases/stats/by_country/

Grouped by country

GET /api/cases/stats/by_state/

Grouped by state

GET /api/cases/stats/by_court/

Grouped by court (requires court__state filter)

GET /api/cases/stats/by_source/

Grouped by data source

Authentication

All endpoints are publicly readable (anonymous access returns statistics for accepted cases only). Authenticated requests follow the same visibility rules as the cases API.

Query Parameters

Root endpoint (/api/cases/stats/)

Parameter

Type

Default

Description

date_after

string (YYYY-MM-DD)

One year ago

Filter cases with date >= value

date_before

string (YYYY-MM-DD)

Today

Filter cases with date <= value

bucket

string

month

Time bucket size: year, month, or day

review_status

string

Staff only. Filter by review status: pending, accepted, or rejected

Sub-endpoints (by_country, by_state, by_court, by_source)

All parameters from the root endpoint, plus:

Parameter

Type

Required

Description

court

integer

No

Filter by court ID

court_slug

string

No

Filter by court slug (alternative to court)

court__state

integer

Yes for by_court (unless state_slug is provided), No for others

Filter by state ID (via court relation)

state_slug

string

Yes for by_court (unless court__state is provided), No for others

Filter by state slug (alternative to court__state)

source

integer

No

Filter by source ID

Visibility Rules

  • Anonymous / non-staff users: Statistics reflect only cases with review_status="accepted"

  • Staff users: Statistics include all cases (accepted, pending, rejected). Use the review_status parameter to filter by a specific status.

Response Format

Root endpoint

Returns an overall total and date-bucketed time series.

{
  "filters": {
    "date_after": "2025-03-23",
    "date_before": "2026-03-23",
    "bucket": "month"
  },
  "total": 48250,
  "buckets": [
    {"date": "2025-04", "count": 3820},
    {"date": "2025-05", "count": 4105},
    {"date": "2025-06", "count": 3950},
    {"date": "2025-07", "count": 4200},
    {"date": "2025-08", "count": 3780},
    {"date": "2025-09", "count": 4050},
    {"date": "2025-10", "count": 4310},
    {"date": "2025-11", "count": 3900},
    {"date": "2025-12", "count": 4180},
    {"date": "2026-01", "count": 4120},
    {"date": "2026-02", "count": 3835},
    {"date": "2026-03", "count": 4000}
  ]
}

Sub-endpoints

Each result item includes its own total and buckets, allowing per-item time series plots.

/api/cases/stats/by_country/

{
  "filters": {
    "date_after": "2025-03-23",
    "date_before": "2026-03-23",
    "bucket": "month"
  },
  "total": 48250,
  "results": [
    {
      "id": 1,
      "code": "DE",
      "name": "Germany",
      "total": 47800,
      "buckets": [
        {"date": "2025-04", "count": 3790},
        {"date": "2025-05", "count": 4070},
        {"date": "2025-06", "count": 3920}
      ]
    },
    {
      "id": 2,
      "code": "AT",
      "name": "Austria",
      "total": 450,
      "buckets": [
        {"date": "2025-04", "count": 30},
        {"date": "2025-05", "count": 35},
        {"date": "2025-06", "count": 30}
      ]
    }
  ]
}

/api/cases/stats/by_state/

{
  "filters": {"date_after": "2025-03-23", "date_before": "2026-03-23", "bucket": "month"},
  "total": 48250,
  "results": [
    {
      "id": 1,
      "name": "Nordrhein-Westfalen",
      "total": 8500,
      "buckets": [
        {"date": "2025-04", "count": 680},
        {"date": "2025-05", "count": 720}
      ]
    },
    {
      "id": 2,
      "name": "Bayern",
      "total": 7200,
      "buckets": [
        {"date": "2025-04", "count": 590},
        {"date": "2025-05", "count": 610}
      ]
    }
  ]
}

/api/cases/stats/by_court/?court__state=1

The court__state filter is required for this endpoint to limit the number of results.

{
  "filters": {"date_after": "2025-03-23", "date_before": "2026-03-23", "bucket": "month"},
  "total": 8500,
  "results": [
    {
      "id": 10,
      "name": "Oberlandesgericht Düsseldorf",
      "total": 1250,
      "buckets": [
        {"date": "2025-04", "count": 98},
        {"date": "2025-05", "count": 110}
      ]
    },
    {
      "id": 25,
      "name": "Landgericht Köln",
      "total": 980,
      "buckets": [
        {"date": "2025-04", "count": 75},
        {"date": "2025-05", "count": 82}
      ]
    }
  ]
}

/api/cases/stats/by_source/

{
  "filters": {"date_after": "2025-03-23", "date_before": "2026-03-23", "bucket": "month"},
  "total": 48250,
  "results": [
    {
      "id": 1,
      "name": "openjur",
      "total": 32000,
      "buckets": [
        {"date": "2025-04", "count": 2550},
        {"date": "2025-05", "count": 2700}
      ]
    },
    {
      "id": 2,
      "name": "gesetze-im-internet",
      "total": 16250,
      "buckets": [
        {"date": "2025-04", "count": 1270},
        {"date": "2025-05", "count": 1405}
      ]
    }
  ]
}

Examples

Get overall monthly statistics for the last year (default)

curl -X GET "https://de.openlegaldata.io/api/cases/stats/" \
  -H "Accept: application/json"

Get yearly statistics over a longer period

curl -X GET "https://de.openlegaldata.io/api/cases/stats/?bucket=year&date_after=2015-01-01&date_before=2026-12-31" \
  -H "Accept: application/json"

Get court breakdown for a specific state (by ID)

curl -X GET "https://de.openlegaldata.io/api/cases/stats/by_court/?court__state=1" \
  -H "Accept: application/json"

Get court breakdown for a specific state (by slug)

curl -X GET "https://de.openlegaldata.io/api/cases/stats/by_court/?state_slug=nordrhein-westfalen" \
  -H "Accept: application/json"

Get source breakdown with daily buckets for the last month

curl -X GET "https://de.openlegaldata.io/api/cases/stats/by_source/?bucket=day&date_after=2026-02-23&date_before=2026-03-23" \
  -H "Accept: application/json"

Staff: statistics for pending cases only

curl -X GET "https://de.openlegaldata.io/api/cases/stats/by_source/?review_status=pending" \
  -H "Authorization: Token YOUR_STAFF_API_TOKEN" \
  -H "Accept: application/json"

Python: plot cases per source over time

import requests
import matplotlib.pyplot as plt

response = requests.get("https://de.openlegaldata.io/api/cases/stats/by_source/")
data = response.json()

plt.figure(figsize=(12, 5))
for source in data["results"]:
    dates = [b["date"] for b in source["buckets"]]
    counts = [b["count"] for b in source["buckets"]]
    plt.plot(dates, counts, label=f"{source['name']} ({source['total']})")

plt.xlabel("Month")
plt.ylabel("Cases")
plt.title(f"Cases per source (total: {data['total']})")
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Error Responses

400 Bad Request - Missing required filter

{
  "detail": "The 'court__state' or 'state_slug' filter is required for this endpoint."
}

400 Bad Request - Invalid bucket

{
  "detail": "Invalid bucket 'weekly'. Must be one of: year, month, day."
}

400 Bad Request - Invalid ID value

{
  "detail": "Invalid value 'de' for 'court__state'. Expected a numeric ID."
}

400 Bad Request - Invalid date

{
  "detail": "Invalid date format for 'date_after': 'not-a-date'. Use YYYY-MM-DD."
}