# API The OLDP API is based on [Django REST Framework](http://www.django-rest-framework.org/) and provides programmatic access to legal data including cases, laws, courts, and law books. ## Base URL All API requests should be made to: ``` https://de.openlegaldata.io/api/ ``` ## Authentication The API supports two authentication methods: ### 1. API Tokens (Recommended) API tokens provide secure, fine-grained access control to API resources. You can create and manage multiple tokens with different permission levels in your account settings. **How to get your API token:** 1. Log in to your account 2. Navigate to your profile settings 3. Go to the "API Access" section 4. Create a new token with a descriptive name 5. Copy the token (it will only be shown once!) **Using your token in requests:** Add the token to the `Authorization` header using the `Token` prefix: ```bash curl -X GET "https://de.openlegaldata.io/api/cases/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ### 2. Session Authentication For web-based clients, you can use standard Django session authentication with your username and password. ## Permission System API tokens use a fine-grained permission system that controls access to specific resources and actions. ### Permission Levels Permissions are defined by **resource** and **action**: - **Resources**: `cases`, `laws`, `courts`, `lawbooks`, `references`, `annotations` - **Actions**: `read`, `write`, `delete` ### Default Permissions By default, new API tokens are assigned to the **"default" permission group** which provides: - ✅ **cases:read** - Read access to cases - ✅ **laws:read** - Read access to laws - ✅ **courts:read** - Read access to courts - ✅ **lawbooks:read** - Read access to law books This ensures secure, read-only access by default. Write and delete permissions must be explicitly granted by administrators. ### Permission Groups Administrators can create custom permission groups with specific combinations of permissions. Tokens are assigned to permission groups, making it easy to manage access for different use cases: - **default**: Read-only access to core resources - **read_write**: Read and write access to specific resources - **full_access**: Complete access including delete operations Contact the administrators if you need elevated permissions for your API token. ## Throttle Rates To ensure fair usage and maintain service quality, the API implements rate limiting: - **Anonymous users**: 100 requests per day - **Authenticated users**: 5,000 requests per hour (10,000 after completing your profile) The authenticated budget is per user and shared with the [MCP server](../mcp.md): requests to `/api/` and authenticated requests to `/mcp` count against the same quota. Your current usage is shown on your account dashboard. If you need higher limits, please contact us or consider using our data dumps for bulk access. ## API Endpoints ### Cases **List all cases:** ```bash curl -X GET "https://de.openlegaldata.io/api/cases/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Filter cases by court:** ```bash curl -X GET "https://de.openlegaldata.io/api/cases/?court_id=3" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Get a specific case:** ```bash curl -X GET "https://de.openlegaldata.io/api/cases/12345/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Filter by date range:** ```bash curl -X GET "https://de.openlegaldata.io/api/cases/?date_after=2020-01-01&date_before=2023-12-31" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ### Laws > **Field availability:** the list endpoint (`/api/laws/`) returns > summary records **without the `content` field**, mirroring how `/api/cases/` > behaves. Use the detail endpoint (`/api/laws//`) to retrieve a law > section's full HTML body. This keeps bulk pagination cheap on bandwidth > and origin CPU; for whole-dataset access prefer the > [data dumps](../data-dumps.md). **List all laws (no `content`):** ```bash curl -X GET "https://de.openlegaldata.io/api/laws/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Filter laws by book:** ```bash curl -X GET "https://de.openlegaldata.io/api/laws/?book_id=5" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Get a specific law (includes `content`):** ```bash curl -X GET "https://de.openlegaldata.io/api/laws/123/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ### Law Books **List all law books:** ```bash curl -X GET "https://de.openlegaldata.io/api/lawbooks/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Get a specific law book:** ```bash curl -X GET "https://de.openlegaldata.io/api/lawbooks/bgb/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Filter by code:** ```bash curl -X GET "https://de.openlegaldata.io/api/lawbooks/?code=BGB" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ### Courts **List all courts:** ```bash curl -X GET "https://de.openlegaldata.io/api/courts/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Filter courts by type:** ```bash curl -X GET "https://de.openlegaldata.io/api/courts/?court_type=AG" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Get a specific court:** ```bash curl -X GET "https://de.openlegaldata.io/api/courts/ag-berlin/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ## Pagination API responses are paginated. Use the `limit` and `offset` parameters to navigate through results: ```bash # Get first 50 results curl -X GET "https://de.openlegaldata.io/api/cases/?limit=50&offset=0" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" # Get next 50 results curl -X GET "https://de.openlegaldata.io/api/cases/?limit=50&offset=50" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` The response includes pagination metadata: ```json { "count": 1234, "next": "https://de.openlegaldata.io/api/cases/?limit=50&offset=50", "previous": null, "results": [...] } ``` ## Search **Search cases by text:** ```bash curl -X GET "https://de.openlegaldata.io/api/cases/search/?text=urheberrecht" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **Search laws:** ```bash curl -X GET "https://de.openlegaldata.io/api/laws/search/?text=vertragsrecht" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` Search supports Lucene syntax for complex queries: ```bash curl -X GET "https://de.openlegaldata.io/api/cases/search/?text=urheberrecht+AND+software" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` `text` is required. Optional filters that compose with the keyword query (logical AND): | Param | Notes | |-------|-------| | `start_date`, `end_date` | `YYYY-MM-DD`, inclusive | | `cited_law_book` + `cited_law_section` | Restrict to cases citing a specific section (case search only — silently ignored on `/api/laws/search/`) | | `cited_case` | Restrict to cases citing the case with this id | | `return_text=1` | Include the full `text` alongside `snippets` | ```bash # Cases citing § 823 BGB that mention "Mietrecht" curl -G "https://de.openlegaldata.io/api/cases/search/" \ --data-urlencode 'text=Mietrecht' \ --data-urlencode 'cited_law_book=bgb' \ --data-urlencode 'cited_law_section=823' \ -H "Authorization: Token $OLDP_API_TOKEN" ``` See [Search](../searching.md) for the full filter matrix across the web, REST, and MCP surfaces. ## Citations & Cross-References The citation graph is queryable in three complementary ways. The same data is also available via the [MCP server](../mcp.md) — the REST nested actions and the MCP tools share a single service layer, so payload shapes match across the two **agent-facing** surfaces. The **human-facing** web search at `/search/?cited_law_book=…&cited_law_section=…` (or `?cited_case=`) renders the same Elasticsearch-backed result set with facets and pagination. ### Nested actions on cases & laws Map 1:1 to the natural questions about a single case or law section. | Endpoint | Returns | Backend | |----------|---------|---------| | `GET /api/cases//references/` | Forward refs emitted by this case (laws + cases it cites) | SQL | | `GET /api/cases//citing_cases/` | Cases whose body cites this case | Elasticsearch | | `GET /api/cases//citing_laws/` | Laws whose body cites this case | SQL | | `GET /api/laws//references/` | Forward refs emitted by this law | SQL | | `GET /api/laws//citing_cases/` | Cases whose body cites this law section | Elasticsearch | | `GET /api/laws//citing_laws/` | Laws whose body cites this law section | SQL | The `references/` endpoints return a single dict (`total_law_references`, `total_case_references`, `law_references[]`, `case_references[]`, `references_extracted_at`). The `citing_*` endpoints return a paginated list of summary records — **`content` is omitted** on these list-style responses for both cases and laws; fetch the detail endpoint when the body HTML is actually needed. For laws, the citing-cases lookup is keyed by `(book_slug, section_slug)` against the `CaseIndex.cited_laws` field. The slug pair is stable across book revisions, so older citation rows pinned to non-latest revisions still surface — no `(book_code, section)` sibling expansion needed at query time. ### Elasticsearch dependency on citing-cases endpoints `/api/cases//citing_cases/` and `/api/laws//citing_cases/` read from Elasticsearch (`CaseIndex.cited_cases` and `CaseIndex.cited_laws` respectively). When ES is unavailable these endpoints return **503** with a structured body so clients can differentiate transient warm-up from a hard outage: ```json // 503 — transient timeout, agent should retry { "detail": "Search timed out while warming caches. Retry the same query in a few seconds.", "code": "search_backend_timeout", "retryable": true, "hint": "First-touch queries on large result sets read ES segments from disk; the same query is sub-100ms on the next attempt." } // 503 — hard outage { "detail": "Search backend is currently unavailable. Please try again later.", "code": "search_backend_unavailable" } ``` See [docs/elasticsearch.md](../elasticsearch.md#index-fields-driving-citation-lookups) for the underlying index fields and the reindex command an operator must run after upgrading a release that changes either field's shape. ```bash # What does case 12345 cite? curl -X GET "https://de.openlegaldata.io/api/cases/12345/references/" \ -H "Authorization: Token $OLDP_API_TOKEN" # Which cases cite § 823 BGB? curl -X GET "https://de.openlegaldata.io/api/laws//citing_cases/" \ -H "Authorization: Token $OLDP_API_TOKEN" ``` ### Flat `/api/references/` For cross-cutting queries the nested actions can't express. Filter by either numeric IDs or slugs (no id round-trip required): | Filter | Field | |--------|-------| | `cited_by_case=` | the source case whose body emitted the cite | | `cited_by_case__slug=` | same, by slug | | `cited_by_law=` | source law (id) | | `cited_by_law__slug=` | source law section slug | | `cited_by_law__book__slug=` | source law book slug | | `cites_case=` | target case (id) | | `cites_case__slug=` | target case (slug) | | `cites_law=` | target law (id) | | `cites_law__slug=` | target law section slug | | `cites_law__book__slug=` | target law book slug | | `assigned=true|false` | drop unresolved refs (no `case`/`law` FK) | Filters compose. Examples: ```bash # Every reference involving § 823 BGB as the target, by slug curl -X GET "https://de.openlegaldata.io/api/references/?cites_law__book__slug=bgb&cites_law__slug=823" \ -H "Authorization: Token $OLDP_API_TOKEN" # Refs emitted by laws within the BGB book pointing at any law curl -X GET "https://de.openlegaldata.io/api/references/?cited_by_law__book__slug=bgb&cites_law__isnull=False" \ -H "Authorization: Token $OLDP_API_TOKEN" # Refs from a specific case (slug) to assigned targets only curl -X GET "https://de.openlegaldata.io/api/references/?cited_by_case__slug=bgh-vi-zr-123-22&assigned=true" \ -H "Authorization: Token $OLDP_API_TOKEN" ``` Each row carries the source (`cited_by`), the target (`case` or `law`), the marker text (the literal citation as it appeared in the source), and the unresolved free-form `to` field used during extraction. ### Citation validation `GET /api/citations/validate/?citation=...&type=...` checks whether a free-form German legal citation (Aktenzeichen, ECLI, or paragraph reference) exists in the local DB. ```bash curl -G "https://de.openlegaldata.io/api/citations/validate/" \ --data-urlencode 'citation=§ 823 BGB' \ -H "Authorization: Token $OLDP_API_TOKEN" # {"found": true, "type": "law", "matches": [...]} curl -G "https://de.openlegaldata.io/api/citations/validate/" \ --data-urlencode 'citation=VI ZR 123/22' \ -H "Authorization: Token $OLDP_API_TOKEN" # {"found": true, "type": "case", "matches": [...]} ``` `type` defaults to `auto` (sniff from the input shape). Force a specific parse with `type=file_number`, `type=ecli`, or `type=law_reference`. ## My Resources (/me/) The `/me/` endpoints let you view resources you have created with your API token. This is useful for tracking submissions and checking their review status. For detailed documentation, see [My Resources API](me-endpoints.md). **List your cases:** ```bash curl -X GET "https://de.openlegaldata.io/api/me/cases/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` **List your law books:** ```bash curl -X GET "https://de.openlegaldata.io/api/me/law_books/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" ``` **User profile and token info:** ```bash curl -X GET "https://de.openlegaldata.io/api/me/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" ``` ## Creating and Updating Resources **Note:** Write operations require a token with appropriate `write` permissions. ### Case Creation API For detailed documentation on creating cases programmatically, including automatic court resolution, duplicate handling, and reference extraction, see the [Case Creation API Documentation](case-creation.md). **Create a new case (requires cases:write permission):** ```bash curl -X POST "https://de.openlegaldata.io/api/cases/?extract_refs=true" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "court_name": "Bundesgerichtshof", "file_number": "I ZR 123/21", "date": "2021-05-15", "content": "

Full case content in HTML...

", "type": "Urteil" }' ``` The API automatically resolves the court from the `court_name` field. Use `?extract_refs=true` (default) to extract legal references from the content, or `?extract_refs=false` to disable. **Update an existing case (requires cases:write permission):** ```bash curl -X PATCH "https://de.openlegaldata.io/api/cases/12345/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "title": "Updated Case Title" }' ``` ### Court Creation API For detailed documentation on creating courts programmatically, including automatic state/city resolution, duplicate handling, and review workflow, see the [Court Creation API Documentation](court-creation.md). **Create a new court (requires courts:write permission):** ```bash curl -X POST "https://de.openlegaldata.io/api/courts/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "name": "Amtsgericht Berlin-Mitte", "code": "AGBERLINMITTE", "state_name": "Berlin", "court_type": "AG", "city_name": "Berlin" }' ``` The API automatically resolves the state and city from their names. Courts created via API are set to `review_status="pending"` until approved by an administrator. **Delete a resource (requires appropriate delete permission):** ```bash curl -X DELETE "https://de.openlegaldata.io/api/cases/12345/" \ -H "Authorization: Token YOUR_API_TOKEN_HERE" \ -H "Accept: application/json" ``` ## Error Handling The API uses standard HTTP status codes: - **200 OK**: Request successful - **201 Created**: Resource created successfully - **400 Bad Request**: Invalid request parameters - **401 Unauthorized**: Missing or invalid authentication - **403 Forbidden**: Insufficient permissions - **404 Not Found**: Resource not found - **429 Too Many Requests**: Rate limit exceeded - **500 Internal Server Error**: Server error **Example error response:** ```json { "detail": "Authentication credentials were not provided." } ``` **Permission denied response:** ```json { "detail": "You do not have permission to perform this action." } ``` ## Response Formats The API supports multiple response formats via the `Accept` header: - **JSON** (default): `Accept: application/json` - **XML**: `Accept: application/xml` - **Browsable API**: `Accept: text/html` (for web browsers) ## Best Practices 1. **Use HTTPS**: Always use HTTPS to protect your API token 2. **Store tokens securely**: Never commit tokens to version control 3. **Use environment variables**: Store your token in environment variables: ```bash export OLDP_API_TOKEN="your_token_here" curl -H "Authorization: Token $OLDP_API_TOKEN" ... ``` 4. **Handle rate limits**: Implement exponential backoff when you receive 429 responses 5. **Use pagination**: Don't fetch all results at once; use pagination for large datasets 6. **Monitor token usage**: Check your token's last used timestamp in your account settings 7. **Rotate tokens regularly**: Create new tokens periodically and revoke old ones 8. **Use specific permissions**: Request only the permissions you need for your use case ## Examples ### Complete Example: Fetching Cases from a Specific Court ```bash #!/bin/bash # Set your API token export OLDP_API_TOKEN="your_token_here" export BASE_URL="https://de.openlegaldata.io/api" # 1. Find the court ID echo "Finding court..." COURT_ID=$(curl -s -X GET "$BASE_URL/courts/?code=BGH" \ -H "Authorization: Token $OLDP_API_TOKEN" \ -H "Accept: application/json" | jq -r '.results[0].id') echo "Court ID: $COURT_ID" # 2. Fetch cases from this court echo "Fetching cases..." curl -s -X GET "$BASE_URL/cases/?court_id=$COURT_ID&limit=10" \ -H "Authorization: Token $OLDP_API_TOKEN" \ -H "Accept: application/json" | jq '.results[] | {title, date, file_number}' ``` ### Example: Exporting Data to CSV ```bash #!/bin/bash export OLDP_API_TOKEN="your_token_here" export BASE_URL="https://de.openlegaldata.io/api" # Fetch and convert to CSV curl -s -X GET "$BASE_URL/cases/?limit=100" \ -H "Authorization: Token $OLDP_API_TOKEN" \ -H "Accept: application/json" | \ jq -r '.results[] | [.id, .title, .date, .court.name] | @csv' > cases.csv echo "Exported to cases.csv" ``` ## Data Dumps and Bulk Downloads For bulk access, prefer the `dump_api_data` management command over making thousands of API requests. It produces gzipped JSONL files plus a snapshot manifest. See [Data Dumps & Bulk Downloads](../data-dumps.md) for full details.