Law Book Creation API
This document describes the API endpoint for programmatically creating new law books.
Overview
The Law Book Creation API allows authenticated users with write permissions to create new law books. The API handles:
Review workflow: Books submitted via an API token are created with
review_status="pending"and become publicly visible only once approvedApproval-driven revision management: The
latestflag tracks the newest accepted revision and is (re)assigned when a revision is approved — a pending submission never demotes the currently-published revisionDuplicate prevention: Law books with the same slug and revision date are rejected
API token tracking: The token used for creation is recorded for audit purposes
Endpoint
POST /api/law_books/
Authentication
Requires a valid API token with lawbooks:write permission.
Authorization: Token YOUR_API_TOKEN
Request Format
Headers
Content-Type: application/json
Authorization: Token YOUR_API_TOKEN
Body
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string (max 100) |
Yes |
Book code (e.g., “BGB”, “StGB”) |
|
string (max 250) |
Yes |
Full title of the book |
|
string |
Yes |
Date of this revision in YYYY-MM-DD format |
|
integer |
No |
Display order / importance (default: 0) |
|
string |
No |
Changelog as JSON array |
|
string |
No |
Footnotes as JSON array |
|
string |
No |
Sections as JSON object |
Example Request
curl -X POST "https://de.openlegaldata.io/api/law_books/" \
-H "Authorization: Token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "BGB",
"title": "Bürgerliches Gesetzbuch",
"revision_date": "2024-01-01",
"order": 1
}'
Response Format
Success Response (201 Created)
{
"id": 42,
"slug": "bgb",
"latest": false,
"review_status": "pending"
}
Field |
Type |
Description |
|---|---|---|
|
integer |
Unique law book ID |
|
string |
URL-friendly identifier (generated from code via |
|
boolean |
Whether this revision is the currently-published latest. Submissions via an API token are |
|
string |
|
Error Responses
400 Bad Request - Validation Error
{
"code": ["This field is required."],
"title": ["Book title cannot be empty."]
}
403 Forbidden
{
"detail": "You do not have permission to perform this action."
}
409 Conflict - Duplicate Law Book
{
"detail": "A law book with this code and revision date already exists."
}
Revision Management
The latest flag identifies the newest accepted revision of a book code — the one served on the public site. It is reassigned whenever a revision’s review_status changes (i.e. on approval), not when a revision is submitted. The invariant is “at most one revision per code has latest=True, and it is the newest accepted one”.
Because API submissions are created pending, this means:
Submitting a revision via the API creates it
latest=False,review_status="pending". The currently-published revision keepslatest=True— an unapproved submission never demotes it, so the public page never goes blank while a newer revision waits for review.Approving a revision (e.g. via the admin) makes it
latest=Trueif it is now the newest accepted revision, demoting the previous latest. Approving an older revision leaves the newer accepted one as latest.Rejecting a revision leaves the published latest unchanged.
A directly-created accepted revision (server-side, no API token) is promoted immediately if it is the newest — preserving the previous non-API behaviour.
If a book ever drifts out of this invariant (e.g. data from before this behaviour), run
manage.py backfill_latest_booksto repair it; the public views also tolerate a missing flag by falling back to the newest accepted revision.
Example: Revision Timeline
POST {"code": "BGB", "revision_date": "2023-01-01", ...}
→ Created latest=false, review_status=pending
→ (approve) → latest=true (first accepted revision)
POST {"code": "BGB", "revision_date": "2024-01-01", ...}
→ Created latest=false, review_status=pending
→ 2023-01-01 stays latest=true until approval
→ (approve 2024-01-01) → latest=true; 2023-01-01 → latest=false
POST {"code": "BGB", "revision_date": "2022-06-01", ...}
→ Created latest=false, review_status=pending
→ (approve) → stays latest=false (older than 2024-01-01)
Duplicate Prevention
Duplicates are detected based on the combination of slug (generated from code) and revision_date. If a law book with the same slug and revision date already exists, a 409 Conflict error is returned.
API Token Tracking
The API token used for law book creation is recorded on the law book for audit purposes. This allows:
Tracking which application/user created each law book
Identifying law books created via API vs. other methods
Revoking access and identifying affected law books
Typical Workflow
Law books and laws are created in a two-step process:
Create the law book via
POST /api/law_books/Create laws within the book via
POST /api/laws/using the book’scodeasbook_code
POST /api/law_books/ → {"code": "BGB", "title": "Bürgerliches Gesetzbuch", "revision_date": "2024-01-01"}
POST /api/laws/ → {"book_code": "BGB", "section": "§ 1", "title": "Beginn der Rechtsfähigkeit", "content": "..."}
POST /api/laws/ → {"book_code": "BGB", "section": "§ 2", "title": "Eintritt der Volljährigkeit", "content": "..."}
Examples
Python Example
import requests
API_TOKEN = "your_api_token_here"
BASE_URL = "https://de.openlegaldata.io/api"
headers = {
"Authorization": f"Token {API_TOKEN}",
"Content-Type": "application/json",
}
lawbook_data = {
"code": "BGB",
"title": "Bürgerliches Gesetzbuch",
"revision_date": "2024-01-01",
"order": 1,
}
response = requests.post(f"{BASE_URL}/law_books/", json=lawbook_data, headers=headers)
if response.status_code == 201:
result = response.json()
print(f"Law book created: ID={result['id']}, Slug={result['slug']}, Latest={result['latest']}")
elif response.status_code == 409:
print("Error: Law book already exists")
elif response.status_code == 400:
print(f"Validation error: {response.json()}")
else:
print(f"Error: {response.status_code} - {response.text}")
Full Import Example (Book + Laws)
import requests
API_TOKEN = "your_api_token_here"
BASE_URL = "https://de.openlegaldata.io/api"
headers = {
"Authorization": f"Token {API_TOKEN}",
"Content-Type": "application/json",
}
# Step 1: Create the law book
lawbook_data = {
"code": "BGB",
"title": "Bürgerliches Gesetzbuch",
"revision_date": "2024-01-01",
}
response = requests.post(f"{BASE_URL}/law_books/", json=lawbook_data, headers=headers)
if response.status_code == 201:
print(f"Law book created: {response.json()}")
elif response.status_code == 409:
print("Law book already exists, proceeding with law creation...")
else:
print(f"Error creating law book: {response.status_code} - {response.text}")
exit(1)
# Step 2: Create laws within the book
laws = [
{
"book_code": "BGB",
"section": "§ 1",
"title": "Beginn der Rechtsfähigkeit",
"content": "<p>Die Rechtsfähigkeit des Menschen beginnt mit der Vollendung der Geburt.</p>",
"order": 1,
},
{
"book_code": "BGB",
"section": "§ 2",
"title": "Eintritt der Volljährigkeit",
"content": "<p>Die Volljährigkeit tritt mit der Vollendung des 18. Lebensjahres ein.</p>",
"order": 2,
},
]
results = {"created": 0, "duplicates": 0, "errors": 0}
for law_data in laws:
response = requests.post(f"{BASE_URL}/laws/", json=law_data, headers=headers)
if response.status_code == 201:
results["created"] += 1
elif response.status_code == 409:
results["duplicates"] += 1
else:
results["errors"] += 1
print(f"Error importing {law_data['section']}: {response.text}")
print(f"Import complete: {results}")
Best Practices
Create books before laws: Law creation requires an existing law book to reference
Handle duplicates gracefully: 409 responses indicate the law book already exists
Use consistent revision dates: Group all laws for a book revision under the same
revision_dateTrack revisions explicitly: Provide meaningful
revision_datevalues to maintain a clear version historyBatch with care: Implement rate limiting and error handling for bulk imports