Registry Contract: Resolve, Card, Values, History
Build on the Field Registry: stable ids under curation, name resolution, maturity levels, the concept card, values with provenance, history and field.* events.
These reads make the Field Registry safe to build on: an id you stored keeps working after a merge, a name resolves to the live concept, every read names its maturity level, and curation is announced on the event feed so a cached vocabulary can invalidate instead of polling.
Maturity levels
Every /v1/fields* response carries maturity beside the frozen numeric tier. Treat it as the contract for what you may rely on.
Levels
field.demoted.field.renamed / field.merged.GET /v1/fields?maturity=core|proven|candidate filters by level. The list hides rows merged into another concept unless include_superseded=true, so a cached vocabulary never carries two ids for one concept.
Stable identity under curation
When curation merges a concept into a survivor, the absorbed row stays readable with superseded_by: { id, links.self } — GET /v1/fields/{id} never 404s a stored id. Pass follow_redirects=true (default false on GET /v1/fields/{id}, /card, and /values) to receive the survivor instead; the hops are listed in redirected_from so you can rewrite your keys. GET /v1/fields/resolve follows redirects by default.
/v1/fields/resolveQuery parameters
Invoice No, invoice_number, Rechnungsnummer.superseded_by to the survivor. Default: trueLookup order, first hit wins: exact canonical_name → the registry match_key fold (separator, case, accent and plural insensitive — the same fold the resolver binds with) → a merge alias → exact synonyms[] containment (names folded in by merges and cleanup) → case-insensitive canonical name → case-insensitive synonym. The first four arms are index lookups; the last two scan the tenant registry and run only when everything indexed missed. The response says which arm matched; no match is 404 unknown_field.
Response
{
"name": "Invoice No",
"matched_by": "synonym",
"redirected_from": ["b7c1…"],
"field": { "id": "a1b2…", "canonical_name": "invoice_number", "maturity": "proven", "tier": 2, "synonyms": ["inv_no", "invoice no"], "superseded_by": null, "links": { "card": "/v1/fields/a1b2…/card" } }
}The concept card
/v1/fields/{id}/cardResponse sections
description, instruction (the master extraction instruction), variance_notes, synonyms[], aliases[].pinned, source, link_key, superseded_by, absorbed[] (concepts merged INTO this one that still exist as redirect rows).occurrence_count, document_count, occurrence_rate, first_seen_at, last_seen_at, promoted_at, document_type_distribution.total, distinct_count, top[] (value, count, share), examples[].schema_count, schema_field_count — how many Specs bind the concept.Values across documents
/v1/fields/{id}/valuesQuery parameters
provenance.via_redirect: true). Default: trueOne row per bound occurrence — a per-cell table column is several rows sharing document_id. Each row carries value, confidence, and provenance (raw_field_name, verbatim source_text, the resolved_by band, needs_confirmation). Rows and pagination.total are Sources-IAM filtered as the key's minting user.
Curation history and events
/v1/fields/{id}/historyEvent kinds: tier_changed (with from_maturity / to_maturity and direction), merge_log_absorbed / merge_log_merged_into, absorbed, superseded, renamed, rewritten, split, deleted, alias_added. Never follows redirects — the trail belongs to the id asked for.
The same changes arrive live on GET /v1/events (and to webhook / delivery bindings) as field.graduated, field.promoted, field.demoted, field.merged and field.renamed. Every payload names the concept by stable field_id; field.merged carries merged_into_id and mode (redirect keeps the absorbed row, deleted removes it). Events are published only after the curation write has committed.
id, read maturity, subscribe to field.*, and re-resolve through GET /v1/fields/resolve when a name stops matching. Never key on canonical_name alone.Frequently asked questions
A field id I stored now returns superseded_by. Did I lose data?+
superseded_by.id (or call with follow_redirects=true) and rewrite your key; GET /v1/fields/{id}/values on the survivor includes the not-yet-repointed occurrences with provenance.via_redirect: true.Which maturity level is safe to hard-code?+
core. A proven concept keeps its id but its name may still be groomed; a candidate may be merged or expire. Subscribe to the field.* events either way so a cache never goes stale silently.How do I go from a name in a user prompt to the right concept?+
GET /v1/fields/resolve?name= with the wording you have. It tries the exact canonical name, the registry spelling fold, merge aliases and synonyms in that order (then case-insensitive fallbacks), follows redirects, and tells you which arm matched — then read /card to confirm the definition and example values before querying.