Skip to content

URL migration map — Branch D Wave 1 (API namespace refactor)

This document is the authoritative source for the API surface rename landed in Branch D Wave 1. Wave D2 (frontend) and Wave D3 (docs + samples) consume it.

Conventions

  • All paths are prefixed with /api/v1 (omitted from the table for brevity).
  • Where a handler is registered at two URLs (top-level + nested), the row notes "dual-route".
  • "Body-shape change" rows mark cases where the handler's request body no longer carries fields that are now path parameters.

Top-level namespace map

New namespace Source modules (merged or re-prefixed)
/auth/ auth.py
/contracts/ contracts.py, semantic_bindings*.py (re-parented), semantic_field_mapping.py (re-parented), semantic_proposals.py (dual)
/proposals/ semantic_proposals.py
/suggestions/ semantic_suggestions.py
/metadata/ metadata.py, metadata_entities.py
/semantic/ Sub-routers: ontologies/, concepts/, discovery/, ingestion/, context/, health/, export/, plus retained governance/, lineage/, provenance/, reviews/, activity/, namespaces/, query/, dashboard/
/pipeline/ pipeline.py, pipeline_configs.py, pipeline_runs.py, extractors.py, runs.py
/quality/ quality.py, validation.py, validation_jobs.py, tracking.py
/editor/ All editor_*.py
/ops/ templates.py, events.py, settings.py, audit.py (audit-export), evolution.py, reconciliation.py, schemas.py (codegen), schema_import.py, docs.py

/auth (unchanged)

Old URL New URL Notes
POST /auth/login POST /auth/login
POST /auth/logout POST /auth/logout
GET /auth/me GET /auth/me

/contracts

Old URL New URL Notes
POST /contracts/create-from-artifacts POST /contracts/create-from-artifacts
POST /contracts/create-mixed POST /contracts/create-mixed
GET /contracts GET /contracts
GET /contracts/{contract_id} GET /contracts/{contract_id}
PUT /contracts/{contract_id} PUT /contracts/{contract_id}
DELETE /contracts/{contract_id} DELETE /contracts/{contract_id}
GET /contracts/versions/{contract_name} GET /contracts/versions/{contract_name}

/contracts/{contract_id}/fields/{field_path}/binding (re-parented)

These move from /semantic/bindings/... to per-contract nested routes. Handlers stay byte-identical; the URL is the only change. Query parameters contract_id, field_path, concept_id continue to be read off the request, but on the new routes contract_id and field_path are taken from the path.

Old URL New URL Notes
GET /semantic/bindings/events GET /contracts/{contract_id}/fields/{field_path}/binding/events Body-shape: contract_id + field_path now from path (query still accepted for back-compat list-all variant — see notes).
GET /semantic/bindings/state GET /contracts/{contract_id}/fields/{field_path}/binding/state concept_id still a query param.
POST /semantic/bindings/transitions POST /contracts/{contract_id}/fields/{field_path}/binding/transitions Body still carries to_state, actor, reason, concept_id, contract_version.
POST /semantic/bindings/accept POST /contracts/{contract_id}/fields/{field_path}/binding/accept Body-shape: contract_id + field_path removed from body; concept_id + others retained.
POST /semantic/bindings/remap POST /contracts/{contract_id}/fields/{field_path}/binding/remap Body-shape: same.
POST /semantic/bindings/mark-technical-only POST /contracts/{contract_id}/fields/{field_path}/binding/mark-technical-only Body-shape: same.
POST /semantic/bindings/annotate POST /contracts/{contract_id}/fields/{field_path}/binding/annotate Body-shape: same.

/contracts/{contract_id}/field-mapping (re-parented)

Old URL New URL Notes
GET /semantic/field-mapping/contracts/{contract_id}/semantic-health GET /contracts/{contract_id}/field-mapping Renamed from "semantic-health" to "field-mapping" coverage.
GET /semantic/field-mapping/contracts/by-name/{name}/{version}/semantic-health GET /contracts/by-name/{name}/{version}/field-mapping Same shape, renamed.
POST /semantic/field-mapping/parse POST /contracts/field-mapping/parse Utility under contracts.
POST /semantic/field-mapping/validate POST /contracts/field-mapping/validate Utility under contracts.

/contracts/{contract_id}/proposals (dual-route)

Old URL New URL Notes
GET /semantic/proposals/contract/{contract_id} GET /contracts/{contract_id}/proposals AND GET /proposals/by-contract/{contract_id} Dual-route registration. Handler unchanged.

/contracts/{contract_id}/audit (per-contract audit slice)

This is not yet registered as a distinct route — there is no existing handler for per-contract audit beyond the global stream. Wave D2 may surface this from /ops/audit-export?contract_id=.... No change required for Wave D1.


/proposals (was /semantic/proposals)

Old URL New URL Notes
POST /semantic/proposals/ POST /proposals
GET /semantic/proposals/ GET /proposals
POST /semantic/proposals/link POST /proposals/link
GET /semantic/proposals/{proposal_id} GET /proposals/{proposal_id}
GET /semantic/proposals/contract/{contract_id} GET /proposals/by-contract/{contract_id} dual-route w/ /contracts/{contract_id}/proposals Two registered URLs, same handler.
PUT /semantic/proposals/{proposal_id}/status PUT /proposals/{proposal_id}/status
POST /semantic/proposals/{proposal_id}/merge POST /proposals/{proposal_id}/merge
POST /semantic/proposals/{proposal_id}/validate POST /proposals/{proposal_id}/validate

/proposals/{proposal_id}/reviews (from /semantic/reviews)

Old URL New URL Notes
POST /semantic/reviews/ POST /proposals/{proposal_id}/reviews Body-shape: proposal_id now from path (was in body).
GET /semantic/reviews/proposal/{proposal_id} GET /proposals/{proposal_id}/reviews
GET /semantic/reviews/proposal/{proposal_id}/status GET /proposals/{proposal_id}/reviews/status

/suggestions (was /semantic/suggestions)

Old URL New URL Notes
GET /semantic/suggestions GET /suggestions
GET /semantic/suggestions/{suggestion_id} GET /suggestions/{suggestion_id}
GET /semantic/suggestions/{suggestion_id}/review-events GET /suggestions/{suggestion_id}/review-events
POST /semantic/suggestions/{suggestion_id}/accept POST /suggestions/{suggestion_id}/accept
POST /semantic/suggestions/{suggestion_id}/dismiss POST /suggestions/{suggestion_id}/dismiss
POST /semantic/suggestions/{suggestion_id}/request-changes POST /suggestions/{suggestion_id}/request-changes
POST /semantic/suggestions/accept-batch POST /suggestions/accept-batch
POST /semantic/suggestions/dismiss-batch POST /suggestions/dismiss-batch
POST /semantic/suggestions/request-changes-batch POST /suggestions/request-changes-batch

/metadata — JSON Schema artifact routes

Contract JSON Schema storage routes were renamed for clarity (DB table json_schemas, model JsonSchemaModel).

Old URL pattern New URL pattern Notes
/metadata/schemas /metadata/json_schemas list + POST store
/metadata/schemas/{contract_name}/{contract_version} same path under /metadata/json_schemas/ GET schema
/metadata/schemas/{contract_name}/{contract_version}/complete same under /metadata/json_schemas/ GET with rules merged
/metadata/metadata[/...] /metadata/metadata[/...] unchanged
/metadata/coercion-rules[/...] /metadata/coercion-rules[/...] unchanged
/metadata/validation-rules[/...] /metadata/validation-rules[/...] unchanged
/metadata/artifacts /metadata/artifacts unchanged
/metadata/coercion-functions /metadata/coercion-functions unchanged
/metadata/validation-functions /metadata/validation-functions unchanged
/metadata/{entity_type} (catch-all) /metadata/{entity_type} unchanged
/metadata/owners[/...] (and 5 siblings) /metadata/owners[/...] (and 5 siblings) unchanged

/semantic — sub-namespaces

/semantic/ontologies (renamed from /semantic/schemes + versioning)

Old URL New URL Notes
GET /semantic/schemes GET /semantic/ontologies
GET /semantic/schemes/{scheme_id} GET /semantic/ontologies/{scheme_id}
POST /semantic/schemes POST /semantic/ontologies
PUT /semantic/schemes/{scheme_id} PUT /semantic/ontologies/{scheme_id}
GET /semantic/schemes/{scheme_key}/rdf GET /semantic/ontologies/{scheme_key}/rdf
POST /semantic/schemes/rdf POST /semantic/ontologies/rdf
POST /semantic/versioning/versions POST /semantic/ontologies/versions
GET /semantic/versioning/versions/{contract_id}/history GET /semantic/ontologies/versions/{contract_id}/history
GET /semantic/versioning/versions/{contract_id}/latest GET /semantic/ontologies/versions/{contract_id}/latest
POST /semantic/versioning/branches POST /semantic/ontologies/branches
GET /semantic/versioning/branches/{contract_id} GET /semantic/ontologies/branches/{contract_id}
POST /semantic/versioning/diff POST /semantic/ontologies/diff
POST /semantic/versioning/merge POST /semantic/ontologies/merge
GET /semantic/schema/version GET /semantic/ontologies/schema-version renamed for clarity
GET /semantic/schema/export GET /semantic/ontologies/schema-export renamed for clarity
GET /semantic/schema/relationship-types (and CRUD) GET /semantic/ontologies/relationship-types + sibling CRUD verbs
GET /semantic/schema/concept-types (and CRUD) GET /semantic/ontologies/concept-types + sibling CRUD verbs
GET /semantic/schema/tiers (and CRUD) GET /semantic/ontologies/tiers + sibling CRUD verbs
GET /semantic/schema/cardinality-rules GET /semantic/ontologies/cardinality-rules

/semantic/concepts (graph + reasoning consolidated)

Old URL New URL Notes
GET /semantic/graph/concepts/{concept_id}/ancestors GET /semantic/concepts/{concept_id}/ancestors
GET /semantic/graph/concepts/{concept_id}/descendants GET /semantic/concepts/{concept_id}/descendants
GET /semantic/graph/concepts/tree GET /semantic/concepts/tree
GET /semantic/graph/concepts/{concept_id}/fields GET /semantic/concepts/{concept_id}/fields
GET /semantic/graph/concepts/by-name/{name}/fields GET /semantic/concepts/by-name/{name}/fields
GET /semantic/concept-graph/concepts/{concept_id}/relationships GET /semantic/concepts/{concept_id}/relationships
POST /semantic/concept-graph/concepts/relationships POST /semantic/concepts/relationships
DELETE /semantic/concept-graph/concepts/relationships/{rid} DELETE /semantic/concepts/relationships/{rid}
PUT /semantic/concept-graph/concepts/relationships/{rid} PUT /semantic/concepts/relationships/{rid}
GET /semantic/reasoning/{concept_id} GET /semantic/concepts/{concept_id}/lineage re-homed reasoning under concepts
GET /semantic/rdf/concepts/export GET /semantic/concepts/export RDF I/O folded under concepts
POST /semantic/rdf/concepts/import POST /semantic/concepts/import
GET /semantic/rdf/concepts/{name} GET /semantic/concepts/by-name/{name} renamed to disambiguate
POST /semantic/rdf/concepts POST /semantic/concepts
PUT /semantic/rdf/concepts/{name} PUT /semantic/concepts/by-name/{name} renamed to disambiguate
DELETE /semantic/rdf/concepts/{name} DELETE /semantic/concepts/by-name/{name} renamed to disambiguate
GET /semantic/rdf/ontology/{contract_id}/export GET /semantic/ontologies/{contract_id}/export moved to ontologies
POST /semantic/rdf/ontology/{contract_id}/validate-shacl Removed — use POST /semantic/governance/validate SEM-1
POST /semantic/rdf/ontology/import-jsonld POST /semantic/ontologies/import-jsonld
POST /semantic/rdf/ontology/{contract_id}/validate-semantic POST /semantic/ontologies/{contract_id}/validate-semantic

/semantic/discovery (no change)

Old URL New URL Notes
GET /semantic/discovery/suggest GET /semantic/discovery/suggest
GET /semantic/discovery/synonyms GET /semantic/discovery/synonyms
GET /semantic/discovery/cross-contract GET /semantic/discovery/cross-contract

/semantic/ingestion (unified — absorbs kb-ingest, extraction, assets, ingest-runs)

Old URL New URL Notes
POST /semantic/ingestion/import POST /semantic/ingestion/import
POST /semantic/ingestion/import-all POST /semantic/ingestion/import-all
POST /semantic/kb/ingest-text POST /semantic/ingestion/kb-text
POST /semantic/kb/ingest-upload POST /semantic/ingestion/kb-upload
POST /semantic/extraction/runs POST /semantic/ingestion/runs
GET /semantic/assets GET /semantic/ingestion/assets
GET /semantic/assets/{asset_id} GET /semantic/ingestion/assets/{asset_id}
POST /semantic/assets POST /semantic/ingestion/assets
POST /semantic/assets/{asset_id}/link-ingest-run POST /semantic/ingestion/assets/{asset_id}/link-ingest-run
GET /semantic/ingest-runs GET /semantic/ingestion/runs merged with extraction/runs (different verb path)
GET /semantic/ingest-runs/{run_id} GET /semantic/ingestion/runs/{run_id}
POST /semantic/ingest-runs POST /semantic/ingestion/runs
GET /semantic/ingest-runs/{run_id}/status GET /semantic/ingestion/runs/{run_id}/status
PATCH /semantic/ingest-runs/{run_id}/status PATCH /semantic/ingestion/runs/{run_id}/status
GET /semantic/ingest-runs/{run_id}/summary GET /semantic/ingestion/runs/{run_id}/summary

Note: POST /semantic/extraction/runs and POST /semantic/ingest-runs are kept as separate handlers (different request semantics) but both surface at POST /semantic/ingestion/runs only one can win at register time. We resolve by putting extraction at /semantic/ingestion/extraction-runs and keeping the asset-lineage runs handler at /semantic/ingestion/runs. Wave D2 must use extraction-runs for the extraction-run start endpoint and runs for the asset-lineage ingest-run CRUD.

/semantic/context (no change)

Old URL New URL Notes
GET /semantic/context GET /semantic/context
GET /semantic/context/concepts/{concept_id} GET /semantic/context/concepts/{concept_id}
GET /semantic/context/contracts/{contract_id}/fields/{field_path:path} (unchanged)
GET /semantic/context/contracts/{contract_id} GET /semantic/context/contracts/{contract_id}
GET /semantic/context/query GET /semantic/context/query

/semantic/health (no change)

Old URL New URL Notes
GET /semantic/health GET /semantic/health
GET /semantic/health/issues/{fingerprint}/history GET /semantic/health/issues/{fingerprint}/history
POST /semantic/health/issues/history POST /semantic/health/issues/history

/semantic/export (single unified export endpoint)

Old URL New URL Notes
POST /semantic/export/obsidian POST /semantic/export/obsidian
GET /semantic/export/neo4j GET /semantic/export/neo4j
GET /semantic/export/formats GET /semantic/export/formats
GET /semantic/export/graph GET /semantic/export/graph

/semantic — retained sub-namespaces

Old URL New URL Notes
POST /semantic/governance/validate POST /semantic/governance/validate
POST /semantic/lineage/upstream POST /semantic/lineage/upstream
POST /semantic/lineage/downstream POST /semantic/lineage/downstream
POST /semantic/lineage/path POST /semantic/lineage/path
POST /semantic/lineage/record POST /semantic/lineage/record
GET /semantic/provenance/concepts/{cid}/origin GET /semantic/provenance/concepts/{cid}/origin
GET /semantic/provenance/contracts/{cid}/grown GET /semantic/provenance/contracts/{cid}/grown
GET /semantic/provenance/pipelines/{pid}/grown GET /semantic/provenance/pipelines/{pid}/grown
GET /semantic/activity GET /semantic/activity
GET /semantic/namespaces[/...] GET /semantic/namespaces[/...] unchanged (4 routes)
POST /semantic/query POST /semantic/query
GET /semantic/dashboard/summary GET /semantic/dashboard/summary

/pipeline

Old URL New URL Notes
POST /pipeline/run POST /pipeline/run
POST /pipeline/configs (+ CRUD) POST /pipeline/configs (+ CRUD) unchanged
POST /pipeline/jobs (+ stats + cancel) POST /pipeline/jobs (+ stats + cancel) unchanged
POST /pipeline/runs POST /pipeline/runs
GET /pipeline/runs/{run_id}/stream GET /pipeline/runs/{run_id}/stream
GET /runs (+ stats, timeseries, {run_id}) GET /pipeline/runs/legacy (+ legacy/stats etc.) rehomed under /pipeline/runs/legacy/* to avoid collision with /pipeline/runs/{run_id}
GET /pipelines (+ stats) GET /pipeline/registry (+ /pipeline/registry/{name}/stats) renamed for clarity vs /pipeline/configs
POST /extractors/test-connection POST /pipeline/extractors/test-connection
POST /extractors/sample POST /pipeline/extractors/sample
POST /ontology/configs (+ CRUD) POST /pipeline/linkml-schema-configs (+ CRUD) re-homed: pipeline-owned ontology snapshots

Note on /runs legacy: this is the legacy aggregated runs API (lists all runs across pipeline + validation). It lives at /pipeline/runs/legacy/* — Wave D2 should migrate UI off it incrementally.


/quality

Old URL New URL Notes
POST /quality/check POST /quality/check
POST /quality/check/upload POST /quality/check/upload
POST /quality/violations POST /quality/violations
GET /quality/metrics GET /quality/metrics
GET /quality/metrics/{metric_id} GET /quality/metrics/{metric_id}
GET /quality/reports/{schema_id} GET /quality/reports/{schema_id}
GET /quality/thresholds/{name}/{version} GET /quality/thresholds/{name}/{version}
PUT /quality/thresholds/{name}/{version} PUT /quality/thresholds/{name}/{version}
POST /validation/validate POST /quality/validation/validate re-homed: validation = quality concern
POST /validation/validate-batch POST /quality/validation/validate-batch
POST /validation/jobs (+ CRUD) POST /quality/jobs (+ CRUD) re-homed
GET /quality/tracking (+ CRUD) GET /quality/tracking (+ CRUD) unchanged

/editor

All editor_*.py paths already canonical at /editor/.... No URL changes — only internal restructuring of editor_knowledge_base.py which currently exposes both /editor/kb/... and /editor/lineage/....

Old URL pattern New URL pattern Notes
/editor/kb/[...] /editor/kb/[...] unchanged
/editor/lineage[/...] /editor/lineage[/...] unchanged
/editor/concepts[/...] /editor/concepts[/...] unchanged
/editor/concept-graphs[/...] /editor/concept-graphs[/...] unchanged
/editor/drafts[/...] /editor/drafts[/...] unchanged
/editor/completeness/ontology/{sid} /editor/completeness/ontology/{sid} unchanged
/editor/search /editor/search unchanged
/editor/pipelines/run /editor/pipelines/run unchanged
/editor/field-name-suggestions /editor/field-name-suggestions unchanged
/layouts (router prefix) /editor/layouts rehomed: editor-owned layouts

/ops — new namespace

/ops/ consolidates infrastructure / cross-cutting concerns that are not domain-specific.

Old URL New URL Notes
GET /audit/export GET /ops/audit-export per spec
GET /templates/schema GET /ops/templates/json_schema renamed for clarity
GET /templates/metadata GET /ops/templates/metadata
GET /templates/coercion-rules GET /ops/templates/coercion-rules
GET /templates/validation-rules GET /ops/templates/validation-rules
GET /templates/contract-artifacts GET /ops/templates/contract-artifacts
GET /templates/pipeline GET /ops/templates/pipeline
GET /templates/pipeline/{filename:path} GET /ops/templates/pipeline/{filename:path}
GET /templates/pipeline-artifacts GET /ops/templates/pipeline-artifacts
GET /semantic/templates GET /ops/domain-templates renamed: domain seed templates
GET /semantic/templates/{template_id} GET /ops/domain-templates/{template_id}
POST /semantic/templates/parse-nl POST /ops/domain-templates/parse-nl
GET /folders/tree (+ CRUD) GET /ops/folders/tree (+ CRUD)
GET /events/stream GET /ops/events/stream
GET /settings/app-config GET /ops/settings/app-config public, no auth
GET /settings/runtime-overview GET /ops/settings/runtime-overview public, no auth
GET /settings/database-config GET /ops/settings/database-config
POST /settings/test-database POST /ops/settings/test-database
POST /settings/test-dlq POST /ops/settings/test-dlq
POST /settings/dlq-stats POST /ops/settings/dlq-stats
GET /settings/server-config (+ PATCH) GET /ops/settings/server-config (+ PATCH)
GET /settings/quality (+ PUT) GET /ops/settings/quality (+ PUT)
GET /settings/notifications (+ PUT) GET /ops/settings/notifications (+ PUT)
POST /settings/notifications/test POST /ops/settings/notifications/test
POST /evolution/check POST /ops/evolution/check
POST /evolution/diff POST /ops/evolution/diff
GET /evolution/{schema_name}/diff GET /ops/evolution/{schema_name}/diff
GET /evolution/{schema_name}/check GET /ops/evolution/{schema_name}/check
POST /evolution/{schema_name}/check POST /ops/evolution/{schema_name}/check
POST /reconciliation/match POST /ops/reconciliation/match
POST /schemas/generate POST /ops/codegen/schemas renamed for clarity (was "schemas/generate")
POST /schemas/convert POST /ops/codegen/convert
POST /schema-import/ddl POST /ops/schema-import/ddl
POST /schema-import/import POST /ops/schema-import/import
POST /semantic/definitions/draft POST /ops/definitions/draft
POST /docs/generate POST /ops/docs/generate
POST /docs/section POST /ops/docs/section
GET /docs/{contract_name}/{contract_version} GET /ops/docs/{contract_name}/{contract_version}

Deletions (production routes removed)

Removed URL Notes
GET /docs/playground-routes per spec; previously surfaced OpenAPI subset for UI playground. Wave D2 must consume the canonical /openapi.json instead.
GET /schema-import/plugins per spec; plugin discovery deemed an implementation detail. Wave D2 must remove any UI that hit it.

Dual-route handlers

The following handlers are registered at two URLs (same handler body served at both):

  1. proposals_by_contract
  2. GET /api/v1/proposals/by-contract/{contract_id}
  3. GET /api/v1/contracts/{contract_id}/proposals

No other handlers are dual-registered in Wave D1.


Body-shape changes (path params now carry data)

These routes moved their identifier from request body to path params. Handlers were updated to read the value from Path(...) rather than the request model. Where a back-compat shim could be cheap, the model field was kept and ignored when the path value is present.

Handler Field moved from body → path
accept_binding (semantic_bindings_actions) contract_id, field_path
remap_binding contract_id, field_path
mark_binding_technical_only contract_id, field_path
annotate_binding contract_id, field_path
create_transition (semantic_bindings) contract_id, field_path
list_events (semantic_bindings) contract_id, field_path (now mandatory)
get_state (semantic_bindings) contract_id, field_path
create_review (semantic_reviews) proposal_id

Out-of-scope (Wave D2 + D3 will consume this map)

  • Frontend (src/pycharter/ui/) — Wave D2 owns. UI fetchers must be updated against this map.
  • Docs (docs/guides/, docs/reference/* except this file), samples (samples/) — Wave D3 owns.
  • DB migrations — out of scope for Branch D.

VC-8 — Vocabulary consolidation (2026-07-12)

The ~37 semantic/editor routers collapsed into ten vocabulary routers under /api/v1/vocabulary/* (vocabulary-consolidation ADR, VC-8 — approved reversal of the T10 path-stability note; minor version bump). Old paths return 404; there is no dual-accept window.

Old prefix New prefix
/semantic/schemes /vocabulary/schemes
/semantic/namespaces /vocabulary/namespaces
/semantic/concepts /vocabulary/concepts
/semantic/concepts/{id}/lineage (reasoning) /vocabulary/insights/concepts/{id}/lineage
/semantic/search /vocabulary/search
/semantic/discovery /vocabulary/search/discovery
/semantic/health /vocabulary/health
/semantic/dashboard /vocabulary/health/dashboard
/semantic/activity /vocabulary/health/activity
/semantic/governance /vocabulary/governance
/semantic/ingestion /vocabulary/extraction
/semantic/lineage /vocabulary/insights/lineage
/semantic/context /vocabulary/insights/context
/semantic/export /vocabulary/insights/export
/semantic/provenance /vocabulary/insights/provenance
/semantic/query /vocabulary/insights/query
/semantic/resource-bindings /vocabulary/bindings
/suggestions /vocabulary/suggestions
/inbound/suggestions /vocabulary/suggestions/inbound
/proposals /vocabulary/governance/proposals
/editor/search /vocabulary/search/universal
/editor/completeness /vocabulary/health/completeness
/editor/concepts (POST create) /vocabulary/concepts/quick-create
/editor/concepts/{key}/lineage /vocabulary/concepts/{key}/lineage
/editor/concept-graphs /vocabulary/concept-graphs
/editor/drafts /vocabulary/drafts
/editor/field-name-suggestions /vocabulary/suggestions/field-names

Unchanged: contract-scoped surfaces (/contracts/{id}/fields/{fp}/binding, /contracts/{id}/proposals, /contracts/*/field-mapping), /ops/* (schema import, domain templates, definitions, reconciliation, audit), /editor/layouts, and /editor/pipelines/run.

The full route table is pinned by the OpenAPI snapshot gate (tests/unit/test_openapi_snapshot.py + tests/fixtures/openapi/routes.txt) — every surface change lands as a reviewable fixture diff.