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 only

Issue 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&current=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;
}
```