REST API reference

The hosted service at https://memory.neo4jlabs.com/v1 exposes a REST API. This page is a curated companion to the machine-readable contract — it maps selected endpoints to protocol operation names and client accessors. A protocol operation is not necessarily exposed as a public method in every SDK.

The authoritative, always-current contract is the OpenAPI document at memory.neo4jlabs.com/openapi.json (production). Request/response schemas live there; this page covers the cross-cutting rules — auth, versioning, errors, pagination — and the SDK-method mapping the spec doesn’t show.

The protocol-operation column uses conformance names from the agent-memory-tck cross-language conformance suite. Check the client accessor column rather than converting a protocol name to another casing. Python endpoint registry names can differ (for example, its message-list route uses list_messages, while its public method is short_term.get_conversation).

Base URL and protocol version

NAMS is an unversioned, continuously shipped service. /v1 identifies its REST protocol; it is independent of Python/TypeScript package versions. The production base URL is https://memory.neo4jlabs.com/v1. The SDK appends paths to the configured MEMORY_ENDPOINT (default the URL above). The /v{n} prefix also selects the REST transport; endpoints without it use the TCK bridge protocol (see Bolt vs NAMS).

The active workspace is resolved from the API key (and/or the X-Workspace-Id header), not from the URL path — so endpoints like read-only Cypher are POST /v1/query, scoped to the caller’s workspace. Treat the OpenAPI spec as the source of truth for exact paths, and prefer the SDK accessors (e.g. client.query.cypher(…​)) over hand-built URLs.

Request headers

Every request carries Authorization: Bearer <token>, where the token is either your API key (nams_…​) used directly, or a short-lived JWT obtained from the Auth0 exchange endpoints (POST /v1/auth/exchange, POST /v1/auth/refresh).

The target workspace depends on the key’s category (see Authentication and API keys): a workspace key is already bound to one workspace, so X-Workspace-Id is optional (and a different value is rejected with 403); an admin key names the workspace per request with X-Workspace-Id: <workspace_id>. The SDK sends the header automatically when workspace_id / workspaceId (or MEMORY_WORKSPACE_ID) is configured. Python’s default transport sends its configured API key directly; calling a token-refresh wrapper does not install automatic token refresh. See Use NAMS.

Conversations

HTTP Path Protocol operation Python accessor TS

GET

/conversations

list_conversations

short_term.list_conversations

listConversations

POST

/conversations

create_conversation

short_term.create_conversation

createConversation

GET

/conversations/{id}

get_conversation_metadata

short_term.get_conversation (combined metadata/messages)

getConversationMetadata

DELETE

/conversations/{id}

delete_conversation

short_term.clear_session

deleteConversation

GET

/conversations/{id}/messages

get_conversation

short_term.get_conversation

getConversation

POST

/conversations/{id}/messages

add_message

short_term.add_message

addMessage

POST

/conversations/{id}/messages/bulk

bulk_add_messages

short_term.bulk_add_messages

bulkAddMessages

POST

/conversations/{id}/search

search_messages

short_term.search_messages

searchMessages

GET

/conversations/{id}/context

get_context

short_term.get_context

getContext

GET

/conversations/{id}/observations

get_observations

short_term.get_observations

getObservations

GET

/conversations/{id}/reflections

get_reflections

short_term.get_reflections

getReflections

GET

/conversations/{id}/extraction-status

get_extraction_status

short_term.get_extraction_status

No public wrapper

Go and C# clients are not part of this repository; their method names for this table are collapsed below for reference.

Details
Path Go C#

/conversations

ListConversations

ListConversationsAsync

/conversations

CreateConversation

CreateConversationAsync

/conversations/{id}

GetConversationMetadata

GetConversationMetadataAsync

/conversations/{id}

DeleteConversation

DeleteConversationAsync

/conversations/{id}/messages

GetConversation

GetConversationAsync

/conversations/{id}/messages

AddMessage

AddMessageAsync

/conversations/{id}/messages/bulk

BulkAddMessages

BulkAddMessagesAsync

/conversations/{id}/search

SearchMessages

SearchMessagesAsync

/conversations/{id}/context

GetContext

GetContextAsync

/conversations/{id}/observations

GetObservations

GetObservationsAsync

/conversations/{id}/reflections

GetReflections

GetReflectionsAsync

/conversations/{id}/extraction-status

GetExtractionStatus

GetExtractionStatusAsync

Because extraction runs asynchronously on NAMS, Python short_term.get_extraction_status reads the conversation pipeline status; long_term.wait_for_extraction(session_id=…​) polls it for you. TypeScript has no public extraction-status wrapper in this checkout: its longTerm.waitForExtraction polls entity search using expected names, a predicate, or result count. Entity search counts alone can match unrelated existing workspace entities.

On Python, get_conversation makes both metadata and message requests. There is no public get_conversation_metadata or delete_conversation; use clear_session for whole-conversation deletion.

Entities

HTTP Path Protocol operation Python accessor TS

GET

/entities

list_entities

No public wrapper

listEntities

POST

/entities

add_entity

long_term.add_entity

addEntity

GET

/entities/{id}

get_entity

long_term.get_related_entities / get_entity_relationships (relationships only)

getEntity

PUT

/entities/{id}

update_entity

No public wrapper

updateEntity

DELETE

/entities/{id}

delete_entity

No public wrapper

deleteEntity

PUT

/entities/{id}/feedback

set_entity_feedback

long_term.set_entity_feedback

setEntityFeedback

GET

/entities/{id}/history

get_entity_history

long_term.get_entity_history

getEntityHistory

POST

/entities/{id}/merge

merge_entities

No public wrapper

mergeEntities

GET

/entities/graph

get_entity_graph

No public wrapper

getEntityGraph

POST

/entities/search

search_entities

long_term.search_entities

searchEntities

POST

/graph/expand

expand_graph

long_term.expand_graph

expandGraph

Go and C# clients are not part of this repository; their method names for this table are collapsed below for reference.

Details
Path Go C#

/entities

ListEntities

ListEntitiesAsync

/entities

AddEntity

AddEntityAsync

/entities/{id}

GetEntity

GetEntityAsync

/entities/{id}

UpdateEntity

UpdateEntityAsync

/entities/{id}

DeleteEntity

DeleteEntityAsync

/entities/{id}/feedback

SetEntityFeedback

SetEntityFeedbackAsync

/entities/{id}/history

GetEntityHistory

GetEntityHistoryAsync

/entities/{id}/merge

MergeEntities

MergeEntitiesAsync

/entities/graph

GetEntityGraph

GetEntityGraphAsync

/entities/search

SearchEntities

SearchEntitiesAsync

/graph/expand

ExpandGraph

ExpandGraphAsync

POST /graph/expand returns the 1-hop neighbourhood of a node ({nodes, edges}), excluding any IDs you already have (loadedIds on the wire; Python argument loaded_ids) — it backs incremental graph-visualization ("expand this node"). Exposed as long_term.expand_graph / longTerm.expandGraph.

Auto-merged entity creates

When POST /entities resolves a name to an existing entity, it returns a resolution: "merged" envelope instead of a complete entity. Python add_entity and TypeScript addEntity fetch the canonical entity and return it. Both expose the create operation’s resolution in entity.metadata under the reserved nams_resolution key, preserving other entity metadata:

{
  "nams_resolution": {
    "resolution": "merged",
    "merged_into": "00000000-0000-0000-0000-000000000001",
    "merge_confidence": 0.93,
    "fallback": false
  }
}

merge_confidence is the service’s name-match score and is omitted when absent or null. It does not replace the canonical entity’s own confidence.

If the canonical GET returns 404, an empty body, or an incomplete response without a usable name, the SDK can return a fallback using the server-provided merge ID and requested name/type. fallback: true identifies this result; its creation timestamp is generated by the client, not read from the server. Python uses its entity confidence default of 1.0; TypeScript leaves entity confidence undefined. These fallback values are not estimates of stored canonical values. Missing merge IDs and malformed populated fields are validation errors. Authentication, rate-limit, network, and server errors propagate to the caller instead of returning a fallback.

Ordinary creates and review_pending responses return their entity directly without a follow-up GET.

Reasoning

HTTP Path Protocol operation Python accessor TS

POST

/reasoning/steps

record_step

reasoning.add_step

recordStep

GET

/reasoning/steps

list_steps

No public wrapper

listSteps

GET

/reasoning/explain/{step}

explain_step

No public wrapper

explainStep

POST

/reasoning/tool-calls

record_tool_call

reasoning.record_tool_call

recordToolCall

GET

/reasoning/trace/{conv}

get_trace_by_conversation

reasoning.get_session_traces / get_trace_with_steps

getTraceByConversation

GET

/reasoning/provenance/{entity}

get_entity_provenance

long_term.get_entity_provenance

getEntityProvenance

Go and C# clients are not part of this repository; their method names for this table are collapsed below for reference.

Details
Path Go C#

/reasoning/steps

RecordStep

RecordStepAsync

/reasoning/steps

ListSteps

ListStepsAsync

/reasoning/explain/{step}

ExplainStep

ExplainStepAsync

/reasoning/tool-calls

RecordToolCall

RecordToolCallAsync

/reasoning/trace/{conv}

GetTraceByConversation

GetTraceByConversationAsync

/reasoning/provenance/{entity}

GetEntityProvenance

GetEntityProvenanceAsync

Python reasoning uses a local synthetic trace lifecycle over conversation-level server steps. complete_trace does not persist a server trace/outcome. See Reasoning API for lifetime and retrieval limits.

Cypher and authentication

HTTP Path Protocol operation Python accessor TS

POST

/query

cypher_query

query.cypher

query.cypher

GET

/auth/api-keys

list_api_keys

auth.list_api_keys

auth.listApiKeys

POST

/auth/api-keys

create_api_key

auth.create_api_key

auth.createApiKey

DELETE

/auth/api-keys/{id}

revoke_api_key

auth.revoke_api_key

auth.revokeApiKey

GET

/auth/api-keys/{id}/reveal

reveal_api_key

auth.reveal_api_key

auth.revealApiKey

POST

/auth/api-keys/{id}/rotate

rotate_api_key

auth.rotate_api_key

auth.rotateApiKey

POST

/auth/refresh

refresh_access_token

auth.refresh_access_token

auth.refreshAccessToken

Go and C# clients are not part of this repository; their method names for this table are collapsed below for reference.

Details
Path Go C#

/query

Query.Cypher

Query.CypherAsync

/auth/api-keys

Auth.ListAPIKeys

Auth.ListApiKeysAsync

/auth/api-keys

Auth.CreateAPIKey

Auth.CreateApiKeyAsync

/auth/api-keys/{id}

Auth.RevokeAPIKey

Auth.RevokeApiKeyAsync

/auth/api-keys/{id}/reveal

Auth.RevealAPIKey

Auth.RevealApiKeyAsync

/auth/api-keys/{id}/rotate

Auth.RotateAPIKey

Auth.RotateApiKeyAsync

/auth/refresh

Auth.RefreshAccessToken

Auth.RefreshAccessTokenAsync

The Python and TypeScript SDKs expose authentication operations on client.auth (Python NamsAuth, TypeScript AuthClient): list_api_keys / create_api_key / reveal_api_key / rotate_api_key / revoke_api_key / refresh_access_token. On the bolt backend client.auth is a NotSupportedError sentinel (bolt uses Neo4j credentials). You can also manage keys from the dashboard. See Authentication and API keys.

Ontologies

Typed, versioned domain schemas. The workspace is selected by credentials and the configured workspace header. Request bodies are snake_case. See Ontology API for the document schema and validation modes.

HTTP Path Python TypeScript

GET

/ontologies

ontology.list

ontology.list

GET

/ontologies/{id}

ontology.get

ontology.get

GET

/ontologies/active

ontology.get_active

ontology.getActive

POST

/ontologies/{name}/clone

ontology.clone

ontology.clone

POST

/ontologies

ontology.create

ontology.create

PUT

/ontologies/{id}

ontology.update

ontology.update

POST

/ontologies/active

ontology.activate

ontology.activate

DELETE

/ontologies/{id}

ontology.delete

ontology.delete

POST

/ontologies/import

ontology.import_

ontology.import

GET

/ontologies/{id}/diff

ontology.diff

ontology.diff

POST

/ontologies/{id}/migrate

ontology.migrate

ontology.migrate

GET

/ontologies/migrations/{job_id}

ontology.get_migration

ontology.getMigration

import, diff, and migrate are exposed on the client.ontology accessor in both SDKs (Python import_ — trailing underscore, since import is a keyword). migrate is asynchronous: it returns a MigrationJob you poll with get_migration(). Consult NAMS limits and behavior and the current service contract for hosted request limits. See Ontology API for method signatures and the accepted import formats.

Errors

Error responses use standard HTTP status codes with a JSON body of the shape {"error": "<message>"}. Some errors carry extra fields (e.g. message, limit on quota errors). The SDK maps them to typed exceptions:

Status Meaning Python SDK exception

400

Invalid request, write Cypher attempted, validation failure

ValidationError (carries details)

401, 403

Missing/invalid token, or workspace not accessible

AuthenticationError

404

Resource not found

NotFoundError (subclasses MemoryError)

405, 501

Operation not available on this backend

NotSupportedError

429

Rate limited

RateLimitError (carries retry_after; honors Retry-After)

5xx

Service error

TransportError (after retries exhausted)

Other 4xx (e.g. 409, 410, 413, 422)

Conflict, gone, payload too large, semantic validation

TransportError

All SDK exceptions subclass MemoryError. The Python transport retries 429, 500, 502, 503, 504, and connection/timeout/remote-protocol errors with exponential backoff. It honors numeric Retry-After seconds; HTTP-date Retry-After values are not parsed. See NAMS limits and behavior for rate-limit and quota specifics.

The TypeScript REST transport reports 404 as TransportError with statusCode === 404; its exported NotFoundError is not used by that transport.

Pagination

Pagination is endpoint- and client-specific; inspect the OpenAPI schema for the endpoint you call. Python short_term.list_conversations forwards limit and user_identifier (as userId), but does not forward offset. Do not infer that an accepted **kwargs name becomes a request parameter. Other Python accessors may implement their own pagination controls. Pass supported limits explicitly when deterministic page sizes are needed.

Wire format differences

  • Hosted REST: camelCase fields (userId, conversationId, createdAt).

  • Bridge protocol: snake_case fields (user_id, conversation_id, created_at).

The wrappers translate their supported fields; NAMS ontologies use snake_case request documents. Python may synthesize model fields where hosted responses do not carry bolt equivalents; see the API references. If you’re writing a custom transport, see the bridge-protocol reference and the language-specific casing modules in the TCK repo.

See also