Brainby arc-labs/docs
Python SDK

Entities resource

List, fetch, traverse, and merge entities in the Recall knowledge graph from Python.

Recall extracts named entities from every conversation it ingests and stores them as typed graph nodes (one of person, organisation, product, place, concept). Memories of type relation connect those entities into a queryable graph. The entities resource is the read and maintenance surface for that graph.

list()

GET/v1/entitiesAPI keystable
def list(self, limit: int = 20, offset: int = 0) -> Any: ...

Page through the entities visible in the caller's scope.

ParameterTypeRequired
limitintoptional
Per-page row count.
offsetintoptional
Skip the first N rows.
page = recall.entities.list(limit=50)
for ent in page['entities']:
    print(ent['name'], ent['kind'])
page = await recall.entities.list(limit=50)
for ent in page['entities']:
    print(ent['name'], ent['kind'])

The response is a paginated list — the exact wrapper shape (entities, cursor, total) depends on the server version. Cursor-style pagination is recommended for large namespaces.

When to use. Building a left-rail entity browser, exporting the graph, running maintenance scripts that touch every entity.

get()

GET/v1/entities/:idAPI keystable
def get(self, entity_id: str) -> Any: ...

Fetch a single entity by ID. Returns the canonical entity row — id, name, kind, aliases, scope, created_at, updated_at. For the full neighborhood (relations, evidence memories) use get_graph.

ent = recall.entities.get('e_01HEX…')
print(ent['name'], ent['aliases'])
ent = await recall.entities.get('e_01HEX…')
print(ent['name'], ent['aliases'])

Raises RecallNotFoundError if the entity does not exist or the caller is out of scope.

get_graph()

GET/v1/entities/:id/graphAPI keystable
def get_graph(self, entity_id: str) -> Any: ...

Return the entity along with all incoming and outgoing relations and the memories that evidence each relation. This is the primary read surface for graph traversal.

graph = recall.entities.get_graph('e_arc_labs')
print(graph['entity']['name'])
for edge in graph['edges']:
    print(edge['predicate'], '->', edge['target']['name'])
graph = await recall.entities.get_graph('e_arc_labs')
print(graph['entity']['name'])
for edge in graph['edges']:
    print(edge['predicate'], '->', edge['target']['name'])

The response carries:

  • entity — the focal entity row.
  • edges — array of relations with source, target, predicate, confidence, weight, and evidence_memory_ids.
  • nodes — entities reachable in one hop (the focal entity plus its immediate neighbors).

When to use. Building a graph view of a single entity ("everything we know about X"), feeding the graph into an LLM for entity-aware reasoning, debugging dedupe/merge decisions.

merge()

POST/v1/entities/mergeAPI keystable
def merge(
    self,
    source_id: str,
    target_id: str,
    *,
    idempotency_key: Optional[str] = None,
) -> Any: ...

Merge two entities — the source entity is folded into the target. Aliases and incoming/outgoing relations transfer; the source row is soft-deleted.

ParameterTypeRequired
source_idstrrequired

Entity to be merged away. Will be soft-deleted.

target_idstrrequired

Canonical entity that absorbs the source's aliases and edges.

idempotency_keystroptional

Override the auto-generated UUID for replay safety.

# Two entities for the same company — merge "Arc" into "Arc Labs".
recall.entities.merge(
    source_id='e_arc',
    target_id='e_arc_labs',
)
await recall.entities.merge(
    source_id='e_arc',
    target_id='e_arc_labs',
)

When to use. When dedupe missed a duplicate and you've manually verified the two entities refer to the same real-world thing. Merge is asymmetric — pick the canonical one as target_id.

Merge is destructive in the sense that the source ID becomes unreachable through the entity graph going forward. The row remains for audit purposes but new memories written against the source name resolve to the target. Test merges in a non-production namespace first.

Errors

CodeStatusRetry
RecallAuthError401/403fatal
Auth failed.
RecallNotFoundError404fatal

Entity ID does not exist or out of scope. On merge, raised when either source or target is missing.

RecallValidationError422fatal

source_id == target_id, or the entities have incompatible kinds.

RecallRateLimitError429fatal
Rate limit exceeded.

Entity types

The kind field is a closed enum:

OptionTypeDefault / Env
personstr
Named individuals.
organisationstr
Companies, teams, groups.
productstr
Products, services, software.
placestr
Geographic locations.
conceptstr

Catch-all for everything else. The extractor falls back to concept when the LLM produces an unrecognised classification.

See Types for the EntityKind Literal alias and the full Entity TypedDict.

Filtering by kind

list() does not currently accept a kind= filter — call list() and filter client-side, or use recall.memories.search(query=…, memory_type='entity') for semantic-driven entity discovery.

Search via memories.search

To find entities matching a query, search memories of type entity:

hits = recall.memories.search(query='startup CTOs', memory_type='entity')
for hit in hits['results']:
    # hit['subject'] is the EntityId of the matched entity
    ent = recall.entities.get(hit['subject'])

This is faster than scanning every entity client-side because it leverages the same retrieval pipeline used for memory search.

Was this page helpful?

On this page