Registry Contract: Resolve, Card, Values, History
The Field Registry as a source of truth for agents and apps: stable ids under curation, name resolution, named maturity levels, the concept card, cross-document values with provenance, curation 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
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 to receive the survivor instead; the hops are listed in redirected_from so you can rewrite your keys.
/v1/fields/resolveQuery parameters
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
Values across documents
/v1/fields/{id}/valuesQuery parameters
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.