# ANZSCO Work Types
> 1,256 ACC Work Type Detail Sheets for occupational assessors and AI agents
**Canonical machine contract.** This file is the single source of truth for the API schema and enum values.
`/agents` and `README.md` are derived — if they conflict, this file wins.
## Human UI
- `/` — search and shortlist tool for occupational assessors (the primary human interface)
- `/agents` — API guide, entry_signal contract, live playground, and worked recipe for agent integrators
## About
ANZSCO Work Types is a searchable index of 1,256 Australian and New Zealand Standard Classification of Occupations (ANZSCO) work type detail sheets maintained by ACC (Accident Compensation Corporation, New Zealand). Designed for occupational assessors, vocational psychologists, and AI agents working on ACC Initial Occupational Assessments (IOA).
Scope: IOA specifically. Every affordance the tool offers — the shortlist tray, the copy-with-context output, the entry signal — is shaped around IOA work. The data may be useful input to other ACC vocational products, but nothing here is tailored to them and no claim of fitness for them is made.
## Data
- Records: 1,256 work types
- Refresh cadence: Daily at 6am NZT
- Source: ACC Work Type Detail Sheets — see https://www.acc.co.nz/for-providers/treatment-recovery/work-type-detail-sheets/
- Attribution: ACC (Accident Compensation Corporation, New Zealand)
Fields per record (snake_case):
`title`, `anzsco_code` (6-digit), `major_group`, `physical_demand` (Sedentary | Light | Medium | Heavy | Very Heavy | Unknown), `acc_sheet_id`, `anchor_id`, `description`, `entry_requirements`, `work_tasks`, `work_environment`, `physical_mental_demands`, `notes`.
`major_group` is ACC's own "Group" label passed through unchanged (e.g. `Professionals`, `Technicians and Trades Workers`, `Labourers`, `Community and Personal Service Workers`, `Clerical and Administrative Workers`, `Machinery Operators and Drivers`, `Sales Workers`, `Managers`). It is upstream data, not a locked enum — read the values you get back rather than hardcoding this list.
HTML-preserved twins (present when ACC supplied markup): `description_html`, `entry_requirements_html`, `work_tasks_html`, `work_environment_html`, `physical_mental_demands_html`, `notes_html`. Each is the same content as its plain-text field with ACC's original `
` / `
` / `- ` / `` markup. If you are not rendering HTML, request `fields=summary` (see API below) and skip them entirely.
Optional Tahatū Career Navigator fields (when matched): `tahatu_url`, `tahatu_salary_most_common_lower`, `tahatu_salary_most_common_upper`, `tahatu_salary_upper`, `tahatu_training_duration`, `tahatu_summary`, `tahatu_match_confidence`, `tahatu_matched_title`, `tahatu_match_source`, `tahatu_related`. Tahatū data is licensed CC BY 4.0.
- `tahatu_match_source`: how the match was made — `heading` (matched on Tahatū's main title), `alternate_title` (matched on an alt-title synonym), or `keyword` (matched on a keyword, the weakest tier).
- `tahatu_related`: up to 2 additional related careers, each an object with `url`, `matched_title`, `match_confidence`, `match_source` and the optional `training_duration`, `summary`, `salary_most_common_lower`, `salary_most_common_upper`, `salary_upper`. Additive and optional — absent when there is no secondary match.
- Presence of `tahatu_url` is what the `has_career=` filter tests.
## Entry signal
Every record carries an `entry_signal` object: a pre-computed, categorical summary of what a work type requires to ENTER it (derived once from `entry_requirements`), so an agent does not have to re-reason over the free text. It describes the JOB, never whether a given client meets it. LLM-derived + clinician-reviewed; a few sheets fall back to a placeholder until the next review (see `source` / `needs_review`).
```
"entry_signal": {
"barrier": "short_course_or_licence",
"licence_or_registration": "may_be_required",
"next_day_ready": false,
"anzsco_skill_level": 4,
"confidence": 0.82,
"source": "verify",
"needs_review": false,
"rationale": "ER names a gateway ticket obtainable in weeks; licensing varies by employer."
}
```
- `barrier` — entry-barrier height / time-to-entry:
- `none` — no formal requirements; can start now.
- `short_course_or_licence` — a gateway course / ticket / licence (days–weeks).
- `experience_or_training` — entered via experience or on-the-job training; no mandatory pre-entry qualification.
- `qualification` — a substantial formal qualification (certificate → degree).
- `unknown` — the text could not be classified. **`unknown` is NOT `none`** — it means "no signal", not "no barrier". Treat it as do-not-assert and read `entry_requirements`.
- `licence_or_registration` — the legal gate: `required` (legally cannot start without it) · `may_be_required` (for some roles/employers) · `none`. **NZ spelling: the field is `licence_or_registration`, not `license`.**
- `next_day_ready` (boolean) — could a person start work the next workday (no pre-entry training or legal wall). **A reviewed judgment, NOT a pure formula** — the default (`barrier ∈ {none, experience_or_training}` AND `licence ≠ required`) is deliberately overridden on some sheets (e.g. a role needing substantial prior experience reads `false`). **Read this field; do NOT re-derive it.**
- `anzsco_skill_level` — 1 (highest: degree / 5yr) … 5 (lowest: no formal quals), or `null` if absent.
- `confidence` — 0–1, the model's confidence in THIS row's classification. Rows below 0.7 carry `needs_review: true`.
- `source` — how the row was produced: `classify` (single-pass) · `verify` (adversarially checked) · `fallback` (no real signal yet — treat like `unknown`).
- `needs_review` (boolean) — `true` means the row is model-generated and NOT yet clinician-signed-off. **Do not rely on a `needs_review: true` row for a clinical or statutory determination without human confirmation.**
- `rationale` — human-readable provenance. **Cite it, do not parse it** — the structured fields above are authoritative.
A single ANZSCO code can map to MULTIPLE work-type sheets (`?anzsco=` returns several records), each with its OWN `entry_signal` keyed by `acc_sheet_id`. Do not assume one signal per code.
Enum stability: values are additive-only within `/api/v1/` — new values may be added; existing values are never renamed or removed. Treat an unrecognized value as `unknown` and fail safe. The valid domains are also returned in every response under `enums`.
## API
Base endpoint: `https://anzscoworktypes.co.nz/api/v1/worktypes`
Filtering (all AND-ed):
- `q` (string, optional, max 200 chars) — full-text substring search (case-insensitive) across `title`, `description`, `work_tasks`
- `anzsco` (string, optional) — exact match on 6-digit ANZSCO code (e.g. `254418`)
- `demand` (enum, optional) — physical demand level: `sedentary`, `light`, `medium`, `heavy`, `very_heavy`
- `group` (string, optional, max 200 chars) — exact whole-string match on `major_group`, case-insensitive. Mirrors the group dropdown in the human UI. NOT enum-validated: `major_group` is upstream ACC data, so an unrecognised group returns `matched: 0` rather than an error — exactly as an unmatched `anzsco` does.
- `has_career` (`true` | `false`, optional) — `true` returns only records that carry a `tahatu_url`; `false` only those that do not. Mirrors the career dropdown in the human UI.
Ordering (pick at most one; `sort` wins if both are given):
- `sort` (enum, optional) — `title-az`, `title-za`, `code`, `demand`. Identical to the human UI's sort dropdown, including its demand weighting (Sedentary 1 → Very Heavy 5, any other value 99, i.e. last). Sorting is stable: equal-ranked records keep dataset order, so `sort` + `offset`/`limit` paginate one consistent ordering.
- `ranked` (`true` | `false`, default `false`) — opt-in relevance ranking, the same scorer the human search box uses (exact title > exact code > title prefix > title substring, plus per-term word scoring; `title` ascending as the tie-break). **Only meaningful alongside `q`: `ranked=true` without `q` is ignored and the response keeps dataset order.** An empty query scores every record identically at the prefix tier, so ranking it would be noise, not signal.
Projection:
- `fields` (enum, optional) — `summary` returns a reduced record: every `*_html` field is dropped, and `entry_signal.rationale` is dropped. Every other field, including all the *structured* `entry_signal` fields, is unchanged. On the production corpus this is roughly a 40% smaller payload. Omit `fields` for the full record — the default response is unchanged.
Pagination:
- `limit` (integer, default `50`, max `200`) — results per page
- `offset` (integer, default `0`) — pagination offset
Returns `total` (unfiltered count), `matched` (filtered count), and the page of results.
Errors: an invalid value for any parameter returns HTTP 400 with `{"error": "..."}` naming the accepted values. An empty value (`?sort=`) is treated as absent, not as invalid.
Example queries:
- `GET /api/v1/worktypes?q=nurse&limit=10`
- `GET /api/v1/worktypes?anzsco=254418`
- `GET /api/v1/worktypes?demand=light&limit=50&offset=0`
- `GET /api/v1/worktypes?q=nurse&ranked=true&fields=summary` — best-match-first, minimal payload (the recommended default for an agent doing a lookup)
- `GET /api/v1/worktypes?group=Technicians%20and%20Trades%20Workers&has_career=true&sort=title-az`
- `GET /api/v1/worktypes?demand=sedentary&has_career=true&fields=summary&limit=200`
Response (HTTP 200):
```
{
"query": { "q": "nurse", "anzsco": null, "demand": null, "limit": 10, "offset": 0 },
"total": 1256,
"matched": 42,
"results": [ /* full WTDS records, each including an entry_signal (see above) */ ],
"enums": {
"barrier": ["none","short_course_or_licence","experience_or_training","qualification","unknown"],
"licence_or_registration": ["required","may_be_required","none"],
"source": ["classify","verify","fallback"]
}
}
```
The `query` block echoes what was applied. `q`, `anzsco`, `demand`, `limit` and `offset` are always present. `group`, `has_career`, `sort`, `ranked` and `fields` appear **only when you supplied them** — their absence means "not supplied", and a request that uses none of them gets byte-identical bytes to the pre-existing v1 response. `has_career=false` and `ranked=false` echo as `false`, not as absence.
The `enums` block returns the valid domains so a consumer can validate its routing and detect a newly-added value at runtime without an external fetch.
CORS: `Access-Control-Allow-Origin: *`; `If-None-Match` is an allowed request header and `ETag` is exposed to browser clients. Cache: `public, max-age=3600`.
Conditional requests: every HTTP 200 carries a weak `ETag` over the exact response body. Send it back as `If-None-Match` and an unchanged response returns `304 Not Modified` with no body and the same caching + CORS headers. `HEAD` returns the same `ETag` as the equivalent `GET`, so you can cheaply probe before fetching. The dataset refreshes daily, so a cached page normally revalidates to a 304 all day.
Methods: `GET`, `HEAD`, `OPTIONS`. Anything else returns 405 with an `Allow` header.
## MCP server
Endpoint: `https://anzscoworktypes.co.nz/mcp` (Model Context Protocol, revision `2025-06-18`, Streamable HTTP transport)
Optional — the JSON API above is the full contract and the MCP server is a thin, read-only wrapper over it. Use MCP if your agent framework speaks it; use the API otherwise. Same data, same filters, same enum domains.
Transport: JSON-RPC 2.0 over `POST`, one message per request, `application/json` responses. No auth, no session (`Mcp-Session-Id` is never issued), no SSE — `GET` and `DELETE` return 405. A notification (a message with no `id`) is answered with `202` and an empty body. JSON-RPC batching is not supported (it was removed from MCP in `2025-06-18`); an array body returns `-32600`. Methods: `initialize`, `ping`, `tools/list`, `tools/call`. Capabilities advertised: `tools` only — no resources, prompts, sampling or logging. Errors use the standard codes: `-32700` malformed JSON, `-32600` invalid request, `-32601` unknown method or unknown tool name, `-32602` invalid tool arguments.
CORS: `Access-Control-Allow-Origin: *`, same public read-only posture as the API. `Cache-Control: no-store` — one URL, a different body per method.
Tools:
- `search_worktypes` — args `q` (string, max 200), `group` (string, max 200), `demand` (enum, see above), `has_career` (boolean), `limit` (integer, default `10`, max `25`). All optional, all AND-ed. Equivalent to `GET /api/v1/worktypes?...&ranked=true&fields=summary`: results are **reduced records** (every `*_html` field and `entry_signal.rationale` dropped, all structured `entry_signal` fields kept) and are ordered best-match-first whenever `q` is supplied. Returns `{ total, matched, returned, truncated, results, enums }`. The `limit` cap is deliberately far below the API's `200` — it is a context budget, not a byte budget. There is no `offset`: page the JSON API if you need the long tail.
- `get_worktype` — args `anzsco_code` (6-digit string) **or** `anchor_id` (the per-sheet slug); supply exactly one. Returns the **full** record — the only path that returns the `*_html` fields and `entry_signal.rationale`. A single ANZSCO code can map to MULTIPLE sheets, so `anzsco_code` returns **all** matching sheets in `results` rather than silently picking one; check `matched` and disambiguate by `title` / `acc_sheet_id`. `anchor_id` is unique and returns at most one record. Returns `{ matched, results, enums }`.
- `list_demand_levels` — no args. Returns each `demand` filter value with the exact `physical_demand` label a record carries, so `demand` can be populated without guessing. Also notes that records may carry `physical_demand: "Unknown"`, for which there is no filter value.
The `entry_signal` caveats above are restated in the tool descriptions and in the `initialize` `instructions`, because that is what a model actually reads: **`barrier: "unknown"` means "no signal", not "no barrier"**, and **a `needs_review: true` row must not carry a clinical or statutory determination without human confirmation**.
## Optional
Nothing further. `/agents` (human-readable API guide and live playground) and `README.md` are derived from this file.
## License
Source code: MIT. ACC attribution required for derivative works.
Tahatū Career Navigator fields are licensed CC BY 4.0. Required attribution:
"Data sourced from Tahatū Career Navigator (www.tahatu.govt.nz) under CC BY 4.0."
## Contact
Josh Wilks — josh.wilks111@gmail.com
GitHub: https://github.com/joshwilks111-max/anzsco-worktypes