Def-Store API
Def-Store API
What it is. The def-store owns WIP's controlled vocabularies: terminologies (a named vocabulary, e.g. DOC_STATUS) and their terms (draft, approved, …), plus typed term relations (is_a, part_of, maps_to, related_to, finding_site, causative_agent, or any custom type) that let terms form an ontology graph. It also imports/exports terminologies (JSON, CSV, and OBO Graph JSON ontologies such as HPO/GO/CHEBI), keeps a per-term audit log of every change, and validates caller-supplied values against a terminology (including alias matches and close-match suggestions). Terminologies and terms are registered with the Registry service to get unique, system-wide IDs; the ID scheme is configurable per namespace (default UUID7).
Reaching it. Base path /api/def-store, aggregating five sub-routers: /terminologies, a set of unprefixed term/validate routes, /import-export, /audit, and /ontology. Every endpoint depends on require_api_key (re-exported from the shared wip_auth library) and requires an API key via the X-API-Key header (the service's OpenAPI ApiKeyAuth security scheme). See also: Auth & Access; Routing & Ingress.
Conventions that apply. Every write endpoint here is bulk-first: POST/PUT/DELETE bodies are arrays, and the store always answers 200 OK with a BulkResponse envelope (results, total, succeeded, failed) — a 200 does not mean every item in the array succeeded; check each BulkResultItem.status/error/error_code. Namespace scoping applies throughout but its shape varies by endpoint: entity-mutating and traversal endpoints require an explicit namespace query parameter, list/read endpoints accept an optional namespace (omitted → every namespace the caller can access, via resolve_namespace_filter), and the recent-audit-log / cross-namespace export endpoints resolve the caller's full accessible-namespace set via resolve_accessible_namespaces (a superadmin key gets no filter — every namespace). Def-store does not use document-store's identity-hash / natural-upsert model: terminology and term IDs are minted by the Registry on create (or supplied pre-assigned for restore/migration), and re-submitting an existing value either errors or, with on_conflict=validate, returns an idempotent status=unchanged rather than a new version. See also: API Conventions; PoNIFs.
Endpoints
POST /api/def-store/terminologies
- Purpose. Create one or more terminologies (controlled vocabularies).
- Request. Body: array of
CreateTerminologyRequest— requiredvalue,label,namespace(namespace is per-item; there is no default). Optional:description,terminology_id(pre-assigned ID for restore/migration — Registry uses it as-is instead of generating one),case_sensitive(defaultfalse),allow_multiple(defaultfalse),extensible(defaultfalse),mutable(defaultfalse),metadata(TerminologyMetadata:source,source_url,version,languagedefault"en",custom),created_by. Query:on_conflict(errordefault |validate). - Response. 200
BulkResponse—results: BulkResultItem[](index,status,id,error,error_code,details,value),total,succeeded,failed. Onstatus="created",idcarries the newterminology_id. - Errors. 400 if
on_conflictis neithererrornorvalidate. Duplicates surface as a per-itemerror_code="already_exists"(default) or, withon_conflict=validate,status="unchanged"for an identical re-create orerror_code="incompatible_config"(with the differing fields indetails) for a changed one — inside the 200 envelope, not as a top-level error. Registry-call failures surface per item as"Registry error: …". - Example.
curl -X POST https://<host>/api/def-store/terminologies \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"value":"DOC_STATUS","label":"Document Status","namespace":"acme"}]'{"results":[{"index":0,"status":"created","id":"<terminology_id>"}],"total":1,"succeeded":1,"failed":0}
GET /api/def-store/terminologies
- Purpose. List terminologies with pagination and optional filters.
- Request. Query:
namespace(omit for all),status,value(exact match),page(default 1),page_size(default 50, max 1000). - Response. 200
TerminologyListResponse—items: TerminologyResponse[],total,page,page_size,pages. - Errors. 422 on out-of-range paging params.
- Example.
curl "https://<host>/api/def-store/terminologies?namespace=acme&page_size=10" -H "X-API-Key: $KEY"→{"items":[{"terminology_id":"...","namespace":"acme","value":"DOC_STATUS","label":"Document Status","status":"active","term_count":2,...}],"total":1,"page":1,"page_size":10,"pages":1}
GET /api/def-store/terminologies/by-value/{value}
- Purpose. Get a terminology by its exact value (e.g.
DOC_STATUS). - Request. Path
value. Query:namespace(omit to search all namespaces). - Response. 200
TerminologyResponse. - Errors. 404
"Terminology not found". - Example.
curl https://<host>/api/def-store/terminologies/by-value/DOC_STATUS -H "X-API-Key: $KEY"
GET /api/def-store/terminologies/{terminology_id}
- Purpose. Get a terminology by Registry ID, with a fallback to lookup by value.
- Request. Path
terminology_id(ID or synonym). Query:namespace(used both for synonym resolution and the value-fallback lookup). - Response. 200
TerminologyResponse. - Errors. 404 if the synonym doesn't resolve or nothing matches by ID/value. Read-permission failures also return 404 (
"Namespace not found") rather than 403 — deliberately, so the response doesn't confirm that an ID exists in a namespace the caller can't read. - Example.
curl https://<host>/api/def-store/terminologies/DOC_STATUS -H "X-API-Key: $KEY"
PUT /api/def-store/terminologies
- Purpose. Update one or more terminologies.
- Request. Body: array of
UpdateTerminologyItem(terminology_idrequired;value,label,description,case_sensitive,allow_multiple,extensible,mutable,metadata,updated_byall optional). Query:namespace(for synonym resolution). - Response. 200
BulkResponse. Changingvalueadds a Registry synonym, so both the old and new value keep resolving. - Errors. Per-item
"Terminology not found";"Registry error: …"on Registry failures. - Example.
curl -X PUT https://<host>/api/def-store/terminologies \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"terminology_id":"<id>","label":"Doc Status v2"}]'
GET /api/def-store/terminologies/{terminology_id}/dependencies
- Purpose. Report what depends on a terminology (templates referencing it via
terminology_reffields) — check before deactivating. - Request. Path
terminology_id(ID or synonym). - Response. 200
TerminologyDependencies—terminology_id,terminology_value,template_count,templates[],has_dependencies,can_deactivate,warning_message. - Errors. 404 if the terminology can't be resolved; read-permission gated the same way as the entity
GETabove. - Example.
curl https://<host>/api/def-store/terminologies/DOC_STATUS/dependencies -H "X-API-Key: $KEY"
POST /api/def-store/terminologies/{terminology_id}/restore
- Purpose. Restore a soft-deleted (inactive) terminology back to active status.
- Request. Path
terminology_id. Query:restore_terms(defaulttrue— also reactivates terms deactivated with it),namespace(for synonym resolution). - Response. 200
TerminologyResponse. - Errors. 404
"Terminology not found". - Example.
curl -X POST "https://<host>/api/def-store/terminologies/DOC_STATUS/restore?restore_terms=false" -H "X-API-Key: $KEY"
DELETE /api/def-store/terminologies
- Purpose. Soft-delete one or more terminologies (and deactivate all their terms).
- Request. Body: array of
DeleteItem(idrequired;forcedefaultfalse;hard_deletedefaultfalse— requires the namespace'sdeletion_mode='full';updated_by). Query:namespace. - Response. 200
BulkResponse. - Errors. Per-item error when
template_count > 0andforceis not set ("Terminology has N dependent templates. Use force=true to delete anyway.");"Terminology not found"if the id doesn't resolve. - Example.
curl -X DELETE https://<host>/api/def-store/terminologies \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"id":"<terminology_id>"}]'
POST /api/def-store/terminologies/{terminology_id}/terms
- Purpose. Create one or more terms in a terminology (namespace inherited from the parent terminology).
- Request. Path
terminology_id. Query:namespace(inferred from the terminology if omitted),batch_size(default 1000, MongoDB batch size),registry_batch_size(default 100, Registry HTTP call batch size),on_conflict(error|validate, single-item path only). Body: array ofCreateTermRequest— requiredvalue; optionalterm_id(pre-assigned ID for restore/migration),aliases[],label(defaults tovalue),description,sort_order(default 0),parent_term_id,translations[](TermTranslation:language,label,description),metadata,created_by. - Response. 200
BulkResponse. A single-item body uses the direct-create path and honorson_conflict; a 2+-item body uses the batch path (insert_manyplus batched Registry calls) and always skips duplicates (status="skipped") regardless ofon_conflict. - Errors. 404 if the terminology doesn't resolve; 400 for other
ValueErrors from the bulk path; 502 ("Registry error: …") if the bulk path's Registry calls fail. - Example.
curl -X POST https://<host>/api/def-store/terminologies/DOC_STATUS/terms \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"value":"draft","label":"Draft"},{"value":"approved","label":"Approved"}]'{"results":[{"index":0,"status":"created","value":"draft"},{"index":1,"status":"created","value":"approved"}],"total":2,"succeeded":2,"failed":0}
GET /api/def-store/terminologies/{terminology_id}/terms
- Purpose. List terms in a terminology, paginated.
- Request. Path
terminology_id(ID or value). Query:namespace,page,page_size,status,search(matches value/aliases). - Response. 200
TermListResponse—items: TermResponse[],total,page,page_size,pages,terminology_id,terminology_value. - Errors. 404
"Terminology not found". - Example.
curl "https://<host>/api/def-store/terminologies/DOC_STATUS/terms?status=active" -H "X-API-Key: $KEY"
GET /api/def-store/terms/{term_id}
- Purpose. Get a term by its ID or synonym — synonyms support colon notation (e.g.
STATUS:approved). - Request. Path
term_id. Query:namespace(for synonym resolution). - Response. 200
TermResponse. - Errors. 404
"Term not found"/ unresolved synonym; read-permission gated by the term's namespace. - Example.
curl https://<host>/api/def-store/terms/STATUS:approved -H "X-API-Key: $KEY"
PUT /api/def-store/terms
- Purpose. Update one or more terms.
- Request. Body: array of
UpdateTermItem(term_idrequired;value,aliases[](replaces the existing list),label,description,sort_order,parent_term_id,translations[],metadata(merged with existing),updated_byall optional). Query:namespace. - Response. 200
BulkResponse. - Errors. Per-item
"Term not found";"Registry error: …"on Registry failures. - Example.
curl -X PUT https://<host>/api/def-store/terms \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"term_id":"<id>","aliases":["appr","ok"]}]'
POST /api/def-store/terms/deprecate
- Purpose. Deprecate one or more terms — kept for historical data, marked deprecated, with an optional replacement.
- Request. Body: array of
DeprecateTermItem(term_id,reasonrequired;replaced_by_term_id,updated_byoptional). Query:namespace. - Response. 200
BulkResponse. - Errors. Per-item
"Term not found". - Example.
curl -X POST https://<host>/api/def-store/terms/deprecate \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"term_id":"<id>","reason":"superseded by approved_v2","replaced_by_term_id":"<new-id>"}]'
DELETE /api/def-store/terms
- Purpose. Soft-delete one or more terms.
- Request. Body: array of
DeleteItem(id,force,hard_delete,updated_by). Query:namespace.forceis accepted but not consulted here — it only bypasses the dependency check on terminology deletion. - Response. 200
BulkResponse. - Errors. Per-item
"Term not found". - Example.
curl -X DELETE https://<host>/api/def-store/terms \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"id":"<term_id>","hard_delete":false}]'
POST /api/def-store/validate
- Purpose. Validate a value against a terminology; returns the matched term (if any) and a close-match suggestion.
- Request. Body
ValidateValueRequest—valuerequired; one ofterminology_idorterminology_valuerequired. - Response. 200
ValidateValueResponse—valid,terminology_id,terminology_value,value,matched_term(TermResponseor null),matched_via("value"|"alias", or null),suggestion(TermResponseor null),error. - Errors. 400 if neither
terminology_idnorterminology_valueis given. If the terminology can't be resolved, the endpoint returns 200 withvalid=false, error="Terminology not found"rather than a 404. - Example.
curl -X POST https://<host>/api/def-store/validate \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"terminology_value":"DOC_STATUS","value":"aproved"}'{"valid":false,"terminology_id":"...","terminology_value":"DOC_STATUS","value":"aproved","matched_via":null,"suggestion":{"value":"approved","...":"..."}}
POST /api/def-store/validate/bulk
- Purpose. Validate multiple values in one call.
- Request. Body
BulkValidateRequest—items: ValidateValueRequest[]. - Response. 200
BulkValidateResponse—results: ValidateValueResponse[],total,valid_count,invalid_count. - Errors. No top-level error; per-item
errorfollows the same rules as the single-value endpoint and is counted intoinvalid_count. - Example.
curl -X POST https://<host>/api/def-store/validate/bulk \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"items":[{"terminology_value":"DOC_STATUS","value":"draft"}]}'
GET /api/def-store/import-export/export/{terminology_id}
- Purpose. Export a terminology with all its terms.
- Request. Path
terminology_id. Query:namespace,format(json|csv, defaultjson),include_metadata(defaulttrue),include_inactive(defaultfalse),include_relations(defaultfalse— includes ontology relations in JSON exports),languages(comma-separated codes). - Response.
format=csv→text/csv,Content-Disposition: attachment; filename=<value>.csv.format=json→ 200 JSON built around aterminologykey (the route readsresult["terminology"]["value"]to name the CSV file, so that key is present in both formats' underlying result).⚠ GAP: the full JSON export schema (fields beyond
terminology.value) isn't established by this doc'ssource_scope—ImportExportServicelives incomponents/def-store/src/def_store/services/import_export.py, outsidecomponents/def-store/src/**/api/. - Errors. 404 if the terminology can't be resolved.
- Example.
curl "https://<host>/api/def-store/import-export/export/DOC_STATUS?format=csv" -H "X-API-Key: $KEY" -o doc_status.csv
GET /api/def-store/import-export/export
- Purpose. Export all terminologies (with their terms) that the caller can read.
- Request. Query:
format(json, default),include_inactive(defaultfalse). - Response. 200
{"terminologies": [...], "count": <int>}; each element has the same shape as the single-terminology export (see the GAP above). Non-admin callers are restricted to their own accessible namespaces; a superadmin key gets every namespace. - Errors. None beyond standard request validation.
- Example.
curl https://<host>/api/def-store/import-export/export -H "X-API-Key: $KEY"
POST /api/def-store/import-export/import
- Purpose. Import a terminology with terms from a JSON or CSV payload.
- Request. Body
dict. JSON format:
CSV format needs{"terminology":{"value":"DOC_STATUS","label":"Document Status","description":"...","case_sensitive":false},"terms":[{"value":"draft","label":"Draft"},{"value":"approved","label":"Approved"}]}terminology_value+terminology_labelpluscsv_content(columns:value, label, description, sort_order). Query:format(json|csv),skip_duplicates(defaulttrue),update_existing(defaultfalse),created_by,batch_size(default 1000),registry_batch_size(default 100). - Response. 200 JSON, shape produced by
ImportExportService.import_terminology— not fixed by aresponse_model.⚠ GAP: exact response keys not established by scope (see the export GAP above).
- Errors. 409 if the target already exists (
ValueErrorcontaining"already exists"); 400 for otherValueErrors; 500 wrapping any other exception as"Import failed: …". Requires write permission ondata.terminology.namespacewhen present in the JSON body — CSV imports without a namespace in the payload fall through to the service's own validation. - Example.
curl -X POST https://<host>/api/def-store/import-export/import \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"terminology":{"value":"DOC_STATUS","label":"Document Status","namespace":"acme"},"terms":[{"value":"draft","label":"Draft"}]}'
POST /api/def-store/import-export/import-ontology
- Purpose. Import an OBO Graph JSON ontology (HPO, GO, CHEBI, etc.) — parses
graphs[0].nodes[]into terms andgraphs[0].edges[]into term relations. - Request. Body: OBO Graph JSON (
dict; must contain a non-emptygraphsarray). Query:namespace(required — target namespace),terminology_value/terminology_label(auto-detected if omitted),prefix_filter(only import nodes with this OBO prefix),include_deprecated(defaultfalse),max_synonyms(default 10),batch_size(default 1000),registry_batch_size(default 50),relation_batch_size(default 500),skip_duplicates(defaulttrue),update_existing(defaultfalse),created_by. - Response. 200 JSON, shape produced by
ImportExportService.import_ontology— not fixed by aresponse_model(see the GAP above). For large ontologies the docstring points at a CLI alternative,scripts/import_obo_graph.py. - Errors. 400 if
graphsis missing/empty or any otherValueError; 500 wrapping other exceptions as"Ontology import failed: …". Requires write permission on the targetnamespace. - Example.
curl -X POST "https://<host>/api/def-store/import-export/import-ontology?namespace=acme" \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"graphs":[{"nodes":[...],"edges":[...]}]}'
POST /api/def-store/import-export/import/url
- Purpose. Fetch a terminology payload from a URL and import it.
- Request. Query:
url(required),format(json|csv),terminology_value/terminology_label(CSV only),skip_duplicates(defaulttrue),update_existing(defaultfalse),created_by,batch_size(default 1000),registry_batch_size(default 100). - Response. 200 JSON, shape produced by
ImportExportService.import_from_url— not fixed by aresponse_model(see the GAP above). - Errors. 403 for any non-superadmin caller — the destination namespace only appears inside the fetched payload, so the route can't pre-authorize per-namespace without fetching the URL itself; the error message directs scoped-key callers to fetch locally and
POST /import-export/importinstead. 409/400 on import failure (same rules as/import). - Example.
curl -X POST "https://<host>/api/def-store/import-export/import/url?url=https://example.org/doc_status.json" -H "X-API-Key: $SUPERADMIN_KEY"
GET /api/def-store/audit/terms/{term_id}
- Purpose. Get the complete change history for a specific term.
- Request. Path
term_id. Query:namespace(synonym resolution),page,page_size. - Response. 200
AuditLogResponse—items: AuditLogEntry[](term_id,terminology_id,action,changed_at,changed_by,changed_fields[],previous_values,new_values,comment),total,page,page_size; sorted bychanged_atdescending. - Errors. 404 if the term can't be resolved; read-permission gated by the term's namespace — audit entries embed
previous_values/new_values, so this endpoint is treated as sensitive. - Example.
curl "https://<host>/api/def-store/audit/terms/<term_id>?page_size=5" -H "X-API-Key: $KEY"
GET /api/def-store/audit/terminologies/{terminology_id}
- Purpose. Get the change history for every term in a terminology.
- Request. Path
terminology_id. Query:namespace,action(filter:created|updated|deprecated|deleted),page,page_size. - Response. 200
AuditLogResponse. - Errors. 404 if the terminology can't be resolved; same namespace-read gating as above.
- Example.
curl "https://<host>/api/def-store/audit/terminologies/DOC_STATUS?action=deprecated" -H "X-API-Key: $KEY"
GET /api/def-store/audit
- Purpose. Get recent audit-log entries across every terminology the caller can read.
- Request. Query:
action(filter),page,page_size. - Response. 200
AuditLogResponse. A superadmin key sees every namespace unfiltered; other callers are restricted toresolve_accessible_namespaces(identity). - Errors. None beyond standard request validation.
- Example.
curl "https://<host>/api/def-store/audit?page_size=20" -H "X-API-Key: $KEY"
POST /api/def-store/ontology/term-relations
- Purpose. Create one or more typed relations between terms.
- Request. Query:
namespace(required). Body: array ofCreateTermRelationRequest(source_term_id,target_term_id,relation_typerequired;metadatafor provenance/confidence/OWL axioms;created_by). - Response. 200
BulkResponse.source_term_id/target_term_idaccept synonyms, resolved in bulk before the write. - Errors. Write-permission failure on
namespace. - Example.
curl -X POST "https://<host>/api/def-store/ontology/term-relations?namespace=acme" \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"source_term_id":"HP:0001250","target_term_id":"HP:0012639","relation_type":"is_a"}]'
GET /api/def-store/ontology/term-relations
- Purpose. List relations for a single term.
- Request. Query:
term_id(required),direction(outgoing|incoming|both, defaultoutgoing),relation_type(filter),namespace(omit for all accessible),page,page_size. - Response. 200
TermRelationListResponse—items: TermRelationResponse[](namespace,source_term_id,target_term_id,relation_type,relation_value,source_terminology_id,target_terminology_id,source_term_value,source_term_label,target_term_value,target_term_label,metadata,status,created_at,created_by),total,page,page_size,pages. - Errors. 404 if
term_iddoesn't resolve. - Example.
curl "https://<host>/api/def-store/ontology/term-relations?term_id=HP:0001250&direction=both" -H "X-API-Key: $KEY"
DELETE /api/def-store/ontology/term-relations
- Purpose. Soft-delete one or more relations.
- Request. Query:
namespace(required). Body: array ofDeleteTermRelationRequest(source_term_id,target_term_id,relation_typerequired;hard_deletedefaultfalse— requires the namespace'sdeletion_mode='full'). - Response. 200
BulkResponse(succeededcounts bothdeletedandskippedstatuses). - Errors. Write-permission failure on
namespace. - Example.
curl -X DELETE "https://<host>/api/def-store/ontology/term-relations?namespace=acme" \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '[{"source_term_id":"HP:0001250","target_term_id":"HP:0012639","relation_type":"is_a"}]'
GET /api/def-store/ontology/term-relations/all
- Purpose. List all relations, paginated — for batch sync/export, unlike the per-term list above.
- Request. Query:
namespace(omit for cross-namespace results),relation_type,source_terminology_id,status(defaultactive),page,page_size. - Response. 200
TermRelationListResponse. - Errors. 404 if
source_terminology_idis given but doesn't resolve. - Example.
curl "https://<host>/api/def-store/ontology/term-relations/all?source_terminology_id=HP" -H "X-API-Key: $KEY"
GET /api/def-store/ontology/terms/{term_id}/ancestors
- Purpose. Traverse upward from a term along outgoing relations of a given type.
- Request. Path
term_id. Query:relation_type(defaultis_a),namespace(required),max_depth(1-50, default 10). - Response. 200
TraversalResponse—term_id,relation_type,direction,nodes: TraversalNode[](term_id,value,terminology_id,depth,path[]),total,max_depth_reached. Forrelation_type=is_a, traversal also follows the legacyparent_term_idfield, for backward compatibility with simple hierarchical terminologies. - Errors. 404 if
term_iddoesn't resolve. - Example.
curl "https://<host>/api/def-store/ontology/terms/HP:0012639/ancestors?namespace=acme" -H "X-API-Key: $KEY"
GET /api/def-store/ontology/terms/{term_id}/descendants
- Purpose. Traverse downward from a term along incoming relations of a given type — mirror of
ancestors. - Request / Response / Errors. Same shape as
ancestors, direction reversed. Foris_a, also includes children linked viaparent_term_id. - Example.
curl "https://<host>/api/def-store/ontology/terms/HP:0001250/descendants?namespace=acme&max_depth=3" -H "X-API-Key: $KEY"
GET /api/def-store/ontology/terms/{term_id}/parents
- Purpose. Get the immediate (non-transitive) parents of a term.
- Request. Path
term_id. Query:relation_type(defaultis_a),namespace(required). - Response. 200
list[TermRelationResponse]— a flat array, not the paginated list envelope. Foris_a, also includes theparent_term_idlink. - Errors. 404 if
term_iddoesn't resolve. - Example.
curl "https://<host>/api/def-store/ontology/terms/HP:0012639/parents?namespace=acme" -H "X-API-Key: $KEY"
GET /api/def-store/ontology/terms/{term_id}/children
- Purpose. Get the immediate (non-transitive) children of a term — mirror of
parents. - Request / Response / Errors. Same shape as
parents, direction reversed. - Example.
curl "https://<host>/api/def-store/ontology/terms/HP:0001250/children?namespace=acme" -H "X-API-Key: $KEY"
Store-specific gotchas
- Registry-minted IDs, not natural-upsert.
terminology_id/term_idare minted by the Registry on create (or supplied pre-assigned for restore/migration). Re-creating an existing value doesn't version anything in place — it either errors (on_conflict=error, the default) or is a no-op returningstatus="unchanged"(on_conflict=validate), witherror_code="incompatible_config"and adetailsdiff when the existing config doesn't match. - Bulk term-create conflict handling changes shape with array length. A single-item
POST .../terminologies/{id}/termshonorson_conflict; a 2+-itemPOSTto the same endpoint always skips duplicates (status="skipped") regardless ofon_conflict. mutable=trueimpliesextensible=true. It controls whether a terminology's terms can be hard-deleted rather than only deprecated.hard_delete=trueneeds more than the flag. OnDeleteItemandDeleteTermRelationRequest,hard_deleteadditionally requires the target namespace'sdeletion_mode='full'— see Namespaces & Tenancy.DeleteItem.forceonly affects terminology deletion. It bypasses the dependent-template check (GET .../dependencies) when deleting a terminology; it's accepted but not consulted when deleting terms.- IDs accept Registry synonyms everywhere. A terminology can be looked up by its Registry ID or its human
value(DOC_STATUS); a term additionally accepts colon notation (STATUS:approved). is_atraversal is dual-model.ancestors/descendants/parents/childrenfollow explicitis_aterm-relations and the legacyparent_term_idfield, so a terminology built before ontology relations existed still traverses correctly.POST /import-export/import/urlis superadmin-only. The destination namespace lives inside the fetched payload, so the route can't check permission before fetching it; scoped-key callers must fetch locally and usePOST /import-export/importinstead.- Audit-log reads are gated as sensitive.
/audit/...endpoints require read-permission on the entity's namespace because entries embed fullprevious_values/new_valuesdiffs, not just metadata. - A permission failure can look like a 404.
GET /terminologies/{terminology_id}documents returning 404 ("Namespace not found") rather than 403 on a read-permission failure, specifically so the response doesn't confirm that an ID exists in a namespace the caller can't read.
