Legal Lookup API
Request an API key, have a testing key emailed to you, and copy the full API guide for the Australian legal corpus search and document endpoints.
Request API access
Submit your details to request a production API key. Requests start as pending.
Generate a dev key
dev onlyIssue an active key and email it to the address you provide (proves inbox control). Disabled in production unless ALLOW_DEV_KEY_ISSUANCE=1 is set.
API guide
# Legal Lookup API Guide
A simple HTTP/JSON API over the Australian legal corpus: full-text search across
Acts, regulations, bills, and court decisions, plus per-document detail with the
full body text and source-link status.
All responses are JSON. All request and response bodies use UTF-8.
---
## 1. Base URL
| Environment | Base URL |
| ----------- | -------- |
| Local dev | `http://localhost:3000` |
| Production | `https://legallookup.com.au` |
Every path below is relative to the base URL, e.g. `GET /api/laws` →
`http://localhost:3000/api/laws`.
---
## 2. Get an API key
Reading the corpus requires an API key, sent on every request in the
`x-api-key` header.
### Step 1 — Request access
`POST /api/register` records your details. Keys are issued against an approved
registration and emailed to you (they are never returned in the browser).
For local / self-serve testing, `POST /api/dev/issue-key` emails an active key
to the address you provide (disabled in production unless
`ALLOW_DEV_KEY_ISSUANCE=1`).
```bash
curl -X POST "$BASE_URL/api/register" \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"name": "Your Name",
"organisation": "Your Org",
"intendedUse": "What you plan to build with the corpus"
}'
```
| Field | Type | Required | Notes |
| ------------- | ------ | -------- | ----- |
| `email` | string | yes | Valid email address |
| `name` | string | no | 1–160 chars |
| `organisation`| string | no | up to 200 chars |
| `intendedUse` | string | no | up to 2000 chars |
**`201 Created`**
```json
{
"data": {
"id": 1,
"email": "you@example.com",
"status": "pending",
"created_at": "2026-06-30T04:18:00.000Z"
}
}
```
A `pending` status means your request is recorded but a key has not been issued
yet. `400 Invalid registration payload.` is returned if `email` is missing or
malformed.
### Step 2 — Use your key
Once issued, send it on every authenticated request:
```bash
export BASE_URL="http://localhost:3000"
export API_KEY="your-issued-key"
curl "$BASE_URL/api/laws?q=privacy" \
-H "x-api-key: $API_KEY"
```
Missing or invalid keys return:
```json
{ "error": "Valid x-api-key header required." }
```
with HTTP `401`.
Issued keys default to **read-only** (`read` scope). Corpus and directory GET
endpoints accept any active key. Creating or updating courts and judges requires
an additionally issued `directory:write` scope (see [Directory write](#directory-write)).
---
## 3. Authentication
| Header | Value | Required on |
| ----------- | -------------------- | ----------- |
| `x-api-key` | Your issued API key | Authenticated `/api/*` routes (laws, courts, courthouses, judges, practitioners) |
`/api/register` is public and does **not** require a key. Each key has an
associated per-minute rate limit (default 60 requests/minute).
---
## 4. Endpoints
### 4.1 Search documents
```http
GET /api/laws
x-api-key: <your-key>
```
Returns a list of document summaries matching the supplied filters. When `q` is
provided, results are ranked by full-text relevance, then by document date.
**Query parameters** (all optional):
| Param | Type | Default | Description |
| ------------------ | ------ | ------- | ----------- |
| `q` | string | — | Full-text search over citation, summary, tags, and body |
| `document_type` | enum | — | See [Document types](#document-types) |
| `jurisdiction` | enum | — | See [Jurisdictions](#jurisdictions) |
| `source` | enum | — | See [Sources](#sources) |
| `effective_status` | enum | — | `in_force`, `repealed`, `unknown` |
| `tag` | string | — | Topic tag code (AI enrichment), e.g. `privacy-data` |
| `from_date` | date | — | `YYYY-MM-DD`, filters by document date ≥ |
| `to_date` | date | — | `YYYY-MM-DD`, filters by document date ≤ |
| `limit` | int | `25` | Clamped to 1–100 |
| `offset` | int | `0` | For pagination |
**Example**
```bash
curl "$BASE_URL/api/laws?q=privacy&jurisdiction=commonwealth&effective_status=in_force&limit=5" \
-H "x-api-key: $API_KEY"
```
**`200 OK`**
```json
{
"data": [
{
"version_id": "...",
"document_type": "primary_legislation",
"document_type_label": "Primary legislation",
"jurisdiction": "commonwealth",
"jurisdiction_label": "Commonwealth",
"jurisdiction_short_code": "CTH",
"source": "federal_register_of_legislation",
"source_label": "Federal Register of Legislation",
"document_date": "1988-12-14",
"commencement_date": "1989-01-01",
"effective_status": "in_force",
"citation": "Privacy Act 1988",
"source_url": "https://www.legislation.gov.au/...",
"latest_status": "ok",
"latest_http_status": 200,
"latest_checked_at": "2026-06-29T10:00:00.000Z",
"last_queried_at": "2026-06-30T04:00:00.000Z",
"enrichment_summary": "Australia's main privacy legislation for Commonwealth agencies and many organisations.",
"enrichment_tags": ["privacy-data"]
}
]
}
```
### 4.2 Get a single document
```http
GET /api/laws/:version_id
x-api-key: <your-key>
```
Returns the full record for one document, including the body text. `version_id`
should be URL-encoded if it contains special characters.
**Example**
```bash
curl "$BASE_URL/api/laws/$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' "the-version-id")" \
-H "x-api-key: $API_KEY"
```
**`200 OK`** — same fields as a search result, plus:
```json
{
"data": {
"...": "all summary fields above",
"mime": "text/plain",
"scraped_at": "2025-01-01T00:00:00.000Z",
"body": "Full document text ...",
"body_length": 84210,
"metadata": {}
}
}
```
**`404 Not Found`**
```json
{ "error": "Document not found." }
```
### 4.3 Courts, courthouses, and judges
Institutional courts, physical venues, and judicial officer memberships.
```http
GET /api/courts?jurisdiction=new_south_wales&level=superior
GET /api/courts/:code?include_judges=1&include_venues=1
GET /api/courthouses?jurisdiction=victoria&q=melbourne&limit=50&offset=0
GET /api/courthouses/:code?include_judges=1
GET /api/judges?court=high_court_of_australia&q=Gageler¤t=1
GET /api/judges/:code
x-api-key: <your-key>
```
| Param | Endpoints | Notes |
| ----- | --------- | ----- |
| `jurisdiction` | courts, courthouses, judges | Jurisdiction code |
| `level` | courts | `apex` / `superior` / `intermediate` / `inferior` / `specialist` |
| `q` | courthouses, judges | Name / suburb / role search |
| `court` | courthouses, judges | Filter by institutional court code |
| `include_judges` | court/courthouse detail | Default on for courts; opt-in (`=1`) for courthouses |
| `include_venues` | court detail | Default on; set `0` to omit |
| `current` | judges | Default current-only; set `0` for historical |
Responses include a `freshness` object for the relevant sync job.
---
## 5. Field reference
### Document summary
| Field | Type | Description |
| ------------------------- | --------------- | ----------- |
| `version_id` | string | Stable unique identifier for the document version |
| `document_type` | enum | Machine code (see below) |
| `document_type_label` | string | Human label, e.g. "Primary legislation" |
| `jurisdiction` | enum | Machine code |
| `jurisdiction_label` | string | Human label, e.g. "Commonwealth" |
| `jurisdiction_short_code` | string | e.g. `CTH`, `NSW` |
| `source` | enum | Machine code |
| `source_label` | string | Human label |
| `document_date` | date \| null | Primary date for the document |
| `commencement_date` | date \| null | When it commenced (legislation) |
| `effective_status` | enum | `in_force` / `repealed` / `unknown` |
| `citation` | string | Display citation |
| `source_url` | string | Canonical source URL |
| `latest_status` | string \| null | Result of the most recent source-link check |
| `latest_http_status` | int \| null | HTTP status from that check |
| `latest_checked_at` | datetime \| null| When the source URL was last checked |
| `last_queried_at` | datetime \| null| When this document was last fetched via the API |
| `enrichment_summary` | string \| null | Plain-English AI summary (null until enriched) |
| `enrichment_tags` | string[] \| null| Topic tag codes from the fixed taxonomy |
### Document detail (adds)
| Field | Type | Description |
| ------------- | -------- | ----------- |
| `mime` | string | Source MIME type |
| `scraped_at` | datetime | When the document was scraped into the corpus |
| `body` | string | Full document text |
| `body_length` | int | Character length of `body` |
| `metadata` | object | Source-specific metadata |
---
## 6. Enumerations
### Document types
| Code | Label |
| ---- | ----- |
| `primary_legislation` | Primary legislation (Acts) |
| `secondary_legislation` | Secondary legislation (regulations, rules) |
| `bill` | Bill (proposed legislation) |
| `decision` | Court / tribunal decision |
### Jurisdictions
| Code | Short | Label |
| ---- | ----- | ----- |
| `commonwealth` | CTH | Commonwealth |
| `new_south_wales` | NSW | New South Wales |
| `queensland` | QLD | Queensland |
| `western_australia` | WA | Western Australia |
| `south_australia` | SA | South Australia |
| `tasmania` | TAS | Tasmania |
| `norfolk_island` | NF | Norfolk Island |
### Sources
| Code | Label | Jurisdiction |
| ---- | ----- | ------------ |
| `federal_register_of_legislation` | Federal Register of Legislation | Commonwealth |
| `federal_court_of_australia` | Federal Court of Australia | Commonwealth |
| `high_court_of_australia` | High Court of Australia | Commonwealth |
| `nsw_caselaw` | NSW Caselaw | New South Wales |
| `nsw_legislation` | NSW Legislation | New South Wales |
| `queensland_legislation` | Queensland Legislation | Queensland |
| `south_australian_legislation` | South Australian Legislation | South Australia |
| `tasmanian_legislation` | Tasmanian Legislation | Tasmania |
| `western_australian_legislation` | Western Australian Legislation | Western Australia |
### Effective status
| Code | Meaning |
| ---- | ------- |
| `in_force` | Currently in force |
| `repealed` | Repealed |
| `unknown` | Status not determined |
---
## 7. Errors
| Status | Body | When |
| ------ | ---- | ---- |
| `400` | `{ "error": "Invalid registration payload." }` | Bad `/api/register` body |
| `401` | `{ "error": "Valid x-api-key header required." }` | Missing/invalid/inactive key |
| `404` | `{ "error": "Document not found." }` | Unknown `version_id` |
---
## Directory write
Keys with the `directory:write` scope may create or update courts and judges.
Ordinary issued keys are read-only and receive `403` if they call these methods.
```http
POST /api/courts
PATCH /api/courts/:code
POST /api/judges
PATCH /api/judges/:code
x-api-key: <key-with-directory:write>
```
**Court upsert body** (POST / PUT): `code`, `name`, `short_name`, `jurisdiction_code`,
`level` (`apex` | `superior` | `intermediate` | `inferior` | `specialist`), plus
optional `parent_code`, URLs, `sort_order`, `metadata`.
**Judge upsert body**: `full_name`, `court_code`, `role`, optional `code`,
`display_name`, appointment dates, `is_current`, source URLs, `metadata`.
Officer codes are derived from role + display name when omitted.
---
## 8. Quickstart
```bash
# 1. Set up
export BASE_URL="http://localhost:3000"
export API_KEY="your-issued-key"
# 2. Search in-force Commonwealth privacy law
curl "$BASE_URL/api/laws?q=privacy&jurisdiction=commonwealth&effective_status=in_force" \
-H "x-api-key: $API_KEY"
# 3. Fetch one document's full text (use a version_id from the search results)
curl "$BASE_URL/api/laws/SOME_VERSION_ID" \
-H "x-api-key: $API_KEY"
```
### TypeScript
```ts
const BASE_URL = process.env.BASE_URL ?? "http://localhost:3000";
const API_KEY = process.env.API_KEY!;
async function searchLaws(query: string) {
const url = new URL("/api/laws", BASE_URL);
url.searchParams.set("q", query);
url.searchParams.set("jurisdiction", "commonwealth");
const res = await fetch(url, { headers: { "x-api-key": API_KEY } });
if (!res.ok) throw new Error(`Legal Lookup ${res.status}: ${await res.text()}`);
const { data } = await res.json();
return data;
}
```
