Ontology API reference

client.ontology is available on both backends since 0.7.0: NamsOntology (hosted, REST) and BoltOntology (your own Neo4j, stored as (:Ontology)-[:HAS_VERSION]→(:OntologyVersion)). Both satisfy the neo4j_agent_memory.ontology.OntologyAPI protocol, so the signatures below are backend-independent; the Bolt column records where the behavior differs. The TypeScript SDK targets NAMS only; it has no Bolt transport.

Methods

Method Semantics Bolt

Python: ontology.list()
TypeScript: ontology.list()

System templates + workspace-owned ontologies; the active one is flagged (is_active).

The eight built-in domain templates come first, as is_system=True rows with an id of template:<name> and no current_revision; stored ontologies follow.

Python: ontology.get(id)
TypeScript: ontology.get(id)

One ontology with its full revision history ({record, versions[]}).

Versions sorted by ascending revision. A template:<name> id returns a synthetic, read-only record with a single revision.

Python: ontology.get_active()
TypeScript: ontology.getActive()

Parsed active document plus binding metadata. Raises NotSupportedError when nothing is bound. Python 0.6.0 and TypeScript 0.5.0 can report the wrong revision; see Active binding in the released SDKs before restoration.

Per database. Never falls back to the built-in POLE+O ontology; the client’s connect-time resolution applies that fallback itself. SchemaError if the stored document is unreadable.

Python: ontology.clone(template_name)
TypeScript: ontology.clone(templateName)

Editable workspace copy of a system template; returns the rev-1 version.

The copy takes the template’s name, or <name>-copy-N when that name is taken, and domain.id / domain.name are rewritten to match. Not activated. NotFoundError for an unknown template.

Python: ontology.create(name, schema, validation_mode=None)
TypeScript: ontology.create({name, schema, validationMode?})

New workspace ontology from a schema document; returns the rev-1 version. Identity comes from schema.domain (id, then name); name is the fallback.

validate_structure() runs first: a structurally invalid document raises ValueError listing every problem. Default mode permissive.

Python: ontology.update(id, schema, validation_mode=None)
TypeScript: ontology.update({id, schema, validationMode?})

New immutable revision (n+1); returns it. Does not change which version is active.

validate_structure() runs first. An omitted validation_mode inherits the previous revision’s. ValueError for a template: id; NotFoundError for an unknown ontology.

Python: ontology.activate(version_id)
TypeScript: ontology.activate(versionId)

Bind the specified version to the workspace. Validation enforcement is a service behavior, separate from successful activation.

Swaps the active flag in a single write, so exactly one version per database is active even under concurrent activation. Takes effect for clients that connect after it.

Python: ontology.delete(id)
TypeScript: ontology.delete(id)

Delete a workspace-owned ontology.

Also deletes its revisions and (:OntologyMigration) records. ValueError for a template: id.

Python: ontology.import_(content=/url=, format=)
TypeScript: ontology.import({content?, url?, format?})

Convert an external graph/ontology document into a non-persisted draft (OntologyImportResult). Nothing is saved until you create() / update() from the returned document. Note the trailing underscore on the Python name (import is a keyword).

Converts locally: json, yaml, native, arrows, auto. url= and the extraction-backed formats raise NotSupportedError. Each validate_structure() problem is returned as an invalid_structure warning rather than raised.

Python: ontology.diff(id, from_revision, to_revision)
TypeScript: ontology.diff(id, fromRevision, toRevision)

Structural diff between two revisions of an ontology (OntologyDiff).

Same shape, computed locally by diff_documents(). mode_change is {"from": …​, "to": …​} when the two revisions' validation modes differ, as on NAMS.

Python: ontology.migrate(id, from_version_id=, to_version_id=, type_mappings=, dry_run=False)
TypeScript: ontology.migrate(id, {fromVersionId, toVersionId, typeMappings, dryRun?})

Enqueue an async label-rename migration from one version to another; returns a MigrationJob. Pass dry_run=True to preview without writing.

Runs synchronously (see migrate on Bolt) and returns a finished job. batch_size is accepted and ignored.

Python: ontology.get_migration(job_id)
TypeScript: ontology.getMigration(jobId)

Poll a migration job’s status and progress (MigrationJob).

Reads back the persisted (:OntologyMigration) node. The job is already finished, so there is nothing to poll.

migrate on Bolt

migrate executes one transaction per (from_label, to_label) pair, inline. For each pair it removes the from label, adds the to label, sets e.subtype from the target version’s entity type, and rewrites e.type when the target label maps onto a different pole_type. The node’s old type label (when the type changes) and old built-in subtype label are removed, judged from each node’s own stored type and subtype, and the target’s type and built-in subtype labels are added, so the node ends up with the labels a fresh write would give it. Labels are matched and written in the form the write paths give them: PascalCase that keeps the label’s own capitals (SupportCase stays SupportCase, tv_show becomes TvShow). type_mappings accepts (from, to) tuples or {"from": …​, "to": …​} dicts.

dry_run=True counts matching nodes (total) and mutates nothing (processed = 0). A failed step is recorded on the job (errored, error_message, status = "failed") rather than raised. Either way the job is persisted as an (:OntologyMigration) node.

NotFoundError if either version id is unknown. ValueError for a malformed mapping, an invalid Neo4j label, a target label the target version does not declare, or a source label the source version does not declare when the target declares no subtype. On a large graph, prefer a maintenance window, or run the equivalent relabel in batches with apoc.periodic.iterate. For a complete rename-and-migrate run, see Rename an ontology type and migrate a Bolt graph.

Return types

list() returns OntologySummary[]. get() returns Ontology ({record, versions[]}). get_active() returns ActiveOntology ({document, validation_mode?, revision?, ontology_id?, version_id?, schema_hash?}; TypeScript uses the camelCase names, and its 0.5.0 release has no schemaHash). clone / create / update / activate return an OntologyVersion.

import_() returns an OntologyImportResult ({document?, warnings[] (ImportWarning), detected_format?, suggested_name?}). diff() returns an OntologyDiff ({from_revision, to_revision, entity_types, relationships, mode_change?}). migrate() and get_migration() return a MigrationJob ({id, status (pending|running|completed|failed|paused), total, processed, errored, …}).

Active binding in the released SDKs

The GET /ontologies/active response carries a version object (id, ontology_id, revision, validation_mode, …) that identifies the bound revision, which can be older than the ontology’s latest revision.

Python neo4j-agent-memory==0.7.0 reads that object. The ActiveOntology returned by get_active() takes version_id, ontology_id, revision, validation_mode and schema_hash from it, so they name the bound revision. When a response carries no version object, those fields are None: the binding is unknown, and the SDK never substitutes the latest revision. Version metadata that is present but malformed, or whose schema_json differs from the active document, raises ValueError. On Bolt, get_active() reads the active (:OntologyVersion) node directly.

Python 0.6.0 and TypeScript @neo4j-labs/[email protected] ignore the version object: get_active() / getActive() compose the version metadata from a second list() / get() lookup, which picks the ontology’s current (latest) revision rather than the bound one. When an older revision is active, the returned version_id / versionId, revision and validation_mode / validationMode describe the latest revision instead. With those releases, do not use them to capture a rollback target or certify restoration.

The hosted Python lessons read GET /ontologies/active themselves:

  • Python: the self-contained ontology lesson uses read_active_binding(http) from hosted_tutorial_helpers.py. It uses the same resolved NAMS settings as the SDK, validates the authoritative version and schema, and stops before mutation if the binding is incomplete or missing. See the activation recipe for its imports and client lifecycle. The helper is example code displayed on those pages, not an SDK method.

  • TypeScript 0.5.0: send the same request with the workspace API key and read version from the JSON body. The response’s version uses the snake_case wire names.

    const response = await fetch("https://memory.neo4jlabs.com/v1/ontologies/active", {
      headers: { Authorization: `Bearer ${process.env.MEMORY_API_KEY}` },
    });
    if (!response.ok) throw new Error(`GET /ontologies/active: ${response.status}`);
    const { version } = (await response.json()) as {
      ontology: unknown;
      version?: { id: string; ontology_id: string; revision: number; validation_mode: string };
    };
    // version.id, version.revision and version.validation_mode name the bound revision.

The TypeScript repository source also reads the authoritative version (and adds schemaHash to ActiveOntology); that change is not in the 0.5.0 release.

Import formats

import_() accepts these format ids (or auto / omit to detect from content):

format Source Bolt

arrows

Arrows.app JSON

Yes

data-importer

Neo4j Data Importer model

NotSupportedError

cypher

Cypher DDL (CREATE CONSTRAINT / schema statements)

NotSupportedError

graphql

GraphQL SDL type definitions

NotSupportedError

linkml

LinkML schema

NotSupportedError

rdf

RDF / OWL (parsed server-side; the extraction service must be reachable)

NotSupportedError

native

The library’s own ontology JSON/YAML (NAMS aliases yaml / yml to native)

Yes

json, yaml

Explicit parsers for a native or arrows body

Yes (Bolt only)

auto

Detect from content (the default when format is omitted)

Yes

On Bolt, auto, json and yaml pick the arrows converter when the parsed body has top-level nodes and relationships keys, and the native path otherwise; the native path also accepts a legacy EntitySchemaConfig body and converts it. url= raises NotSupportedError on Bolt, because the service’s fetch is SSRF-guarded and size-capped and a local converter cannot promise the same; download the document and pass it as content=.

Ontology document schema

Field Shape Notes

domain

{id, name, description?, tagline?, emoji?}

Identity + display metadata.

entity_types[]

{label, pole_type, subtype?, color?, icon?, properties[], description?, aliases?, threshold?, resolution_threshold?, review_threshold?}

pole_type ∈ PERSON / ORGANIZATION / LOCATION / EVENT / OBJECT.

…properties[]

{name, type, required?, unique?, enum?, description?}

type ∈ string / datetime / date / float / integer.

relationships[]

{type, source, target, description?, threshold?, inverse?, unique_source?, unique_target?, acyclic?, allow_self?}

type is UPPER_SNAKE; source/target are single entity labels. Several entries may share one type.

no_self_loops

bool (default true)

Applied per relation, so an explicit allow_self: true still wins.

Parsing is deliberately lenient (unknown fields are ignored and every extension field has a default), so any document that parsed before 0.7.0 still parses.

Extension fields

These fields, added in 0.7.0, drive the local extraction and resolution pipeline on Bolt, where the stored document keeps all of them. NamsOntology.create / update send an extension field only when it differs from its default, so a document without extensions keeps its pre-0.7.0 request shape. Whether the service keeps fields it does not model is not verified for this release, and on NAMS extraction and resolution run server-side against the service’s own engine.

Field On Effect

description

entity type / relationship / property

Serialized into the GLiNER2.5 encoder input as a zero-shot annotation guideline, and rendered into the LLM extractor’s prompt. Write negative cases into it ("Not a job title.").

aliases

entity type

Canonical name → known surface forms. The resolver’s gazetteer: a blocking key and a 1.0 match rule.

threshold

entity type

Per-label confidence floor for the decoder.

resolution_threshold / review_threshold

entity type

Per-type overrides of resolution.auto_merge_threshold / resolution.review_threshold.

threshold

relationship

Per-relation confidence floor for JointIE (the model cards suggest about 0.6; 0.15–0.35 measured better on crowded relations).

unique_source / unique_target

relationship

Compile to JointIE’s unique_head / unique_tail: at most one edge of this type per source / per target.

acyclic

relationship

Forbids cycles during decoding.

allow_self

relationship

Permits a self-loop. Only valid when source == target.

inverse

relationship

Names the mirror relationship. validate_structure() checks that the pair mirrors each other’s source and target. There is no "symmetric" flag; it is broken upstream in gliner2 2.0.0.

validate_structure() is the single validator. It returns a list of human-readable problems (duplicate labels, a pole_type outside POLE+O, an undeclared relationship endpoint, a duplicate relationship triple, a bad allow_self, a non-mirroring inverse), and BoltOntology.create / update and compile_joint_schema call it and raise ValueError when it reports any. content_key() returns a deterministic content-addressed key used to cache compiled schemas by content rather than by name.

The OntologyVersion carries id, ontology_id, revision, validation_mode (permissive | strict), the parsed document, schema_hash, created_at, and message.

REST endpoints

These apply to NAMS only; BoltOntology issues Cypher instead. The SDK targets these workspace-scoped endpoints (under the versioned base, e.g. /v1). Request bodies use snake_case. See the OpenAPI spec for exact request/response shapes.

Method Path Maps to

GET

/ontologies

list()

GET

/ontologies/{id}

get()

GET

/ontologies/active

get_active()

POST

/ontologies/{name}/clone

clone()

POST

/ontologies — body {ontology, validation_mode?}

create()

PUT

/ontologies/{id} — body {ontology, validation_mode?}

update()

POST

/ontologies/active — body {version_id}

activate()

DELETE

/ontologies/{id}

delete()

POST

/ontologies/import — body {content?, url?, format?}

import_() / import()

GET

/ontologies/{id}/diff?from={n}&to={m}

diff()

POST

/ontologies/{id}/migrate

migrate()

GET

/ontologies/migrations/{job_id}

get_migration()

migrate runs asynchronously: POST /ontologies/{id}/migrate returns a MigrationJob you poll with get_migration() (the service also exposes /migrations/{job_id}/pause and /resume). The preview/dry-run path of import is rate-limited to 30 requests/hour/workspace (see NAMS limits and behavior).

Errors

Condition Exception

Strict-mode validation rejection

ValidationError (carries the offending detail)

No active ontology bound

NotSupportedError (both backends)

Active version metadata malformed, or its schema differs from the active document (NAMS)

ValueError

Structurally invalid document passed to Bolt create / update

ValueError listing every problem

import_(url=…​), or a format only the hosted converter runs, on Bolt

NotSupportedError naming NAMS

update / delete against a template:<name> id on Bolt

ValueError

Unknown ontology, version, template or migration id on Bolt

NotFoundError

Stored document unreadable, or the migration record could not be written (Bolt)

SchemaError

System templates

~28 system templates ship on the service (each is_system = true, revision 1). nams-default is bound until a workspace activates its own:

agent-memory, conservation, cybersecurity, data-journalism, digital-twin, education, financial-services, gaming, genai-llm-ops, gis-cartography, golf-sports, government, healthcare, hospitality, legal, manufacturing, nams-default, oil-gas, options-intelligence, personal-knowledge, product-management, real-estate, retail-ecommerce, scientific-research, software-engineering, trip-planning, vacation-industry, wildlife-management.

On Bolt the templates are the eight built-in domain templates (poleo, podcast, news, scientific, business, entertainment, medical, legal), listed with an id of template:<name>. poleo is the curated POLE+O ontology (5 labels, 16 relationship types, annotation guidelines and decoding constraints) and is the library default.

Runnable examples

See also