Brainby arc-labs/docs
Python SDK

Namespaces resource

Admin-plane CRUD for Recall namespaces from Python — create, list, get, delete, stats, reembed, reextract, with the async background-job model.

Namespaces is a JWT-only resource. API keys are rejected with HTTP 403 on every namespaces endpoint. Authenticate via your control-plane JWT issuer (typically your IDP); rotate JWTs through the recall.identity.refresh() cycle.

list()

GET/v1/namespacesJWTstable
def list(self) -> List[Dict[str, Any]]: ...

List every namespace owned by the calling org, newest first.

for ns in recall.namespaces.list():
    print(ns['id'], ns['name'], ns['mode'])
for ns in await recall.namespaces.list():
    print(ns['id'], ns['name'], ns['mode'])

create()

POST/v1/namespacesJWTstable
def create(self, req: Dict[str, Any]) -> Dict[str, Any]: ...

Create a new namespace and bootstrap its data plane (Postgres pool, embedding column, indexes). Returns the namespace row with provisioning status.

ParameterTypeRequired
reqdictrequired

Namespace creation request. Required keys: mode ('self-hosted' or 'cloud'), name. Optional: description, database_id, llm_id, embedder. See the namespace concept page for full schema.

ns = recall.namespaces.create({
    'mode': 'self-hosted',
    'name': 'prod',
    'description': 'Production namespace for support agent',
})
print('Created:', ns['id'])
ns = await recall.namespaces.create({
    'mode': 'self-hosted',
    'name': 'prod',
    'description': 'Production namespace for support agent',
})

get()

GET/v1/namespaces/:idJWTstable
def get(self, namespace_id: str) -> Dict[str, Any]: ...

Fetch a namespace by ID, including its provisioning status.

ns = recall.namespaces.get('ns_01HEX…')
print(ns['provisioning_status'])
ns = await recall.namespaces.get('ns_01HEX…')

stats()

GET/v1/namespaces/:id/statsJWTstable
def stats(self, namespace_id: str) -> Dict[str, Any]: ...

Return live operational counters for the namespace. Server caches the result for 30 seconds per namespace, so frequent calls don't hammer the data plane.

The response follows the NamespaceStats TypedDict (see Types):

{
  "id": "ns_…",
  "memory_count": 12450,
  "entity_count": 873,
  "relation_count": 2104,
  "bytes_used": "84 MB",
  "embedding_coverage": 0.99,
  "oldest_memory_at": "2025-12-15T…",
  "last_write_at": "2026-05-08T…",
  "pending_hype_jobs": 0,
  "health": "ready",
  "pending_admin_jobs": {"reembed": 1}
}
s = recall.namespaces.stats('ns_01HEX…')
print(f"{s['memory_count']} memories, {s['embedding_coverage']:.0%} embedded")
s = await recall.namespaces.stats('ns_01HEX…')

delete()

DELETE/v1/namespaces/:idJWTstable
def delete(self, namespace_id: str, *, confirm: str) -> None: ...

Soft-delete a namespace and cascade-revoke its API keys. The confirm kwarg MUST equal the namespace's slug exactly — GitHub-style "type the name to confirm" gating. Mismatches return HTTP 400 with the expected slug echoed back.

ns = recall.namespaces.get('ns_01HEX…')
recall.namespaces.delete(ns['id'], confirm=ns['name'])
ns = await recall.namespaces.get('ns_01HEX…')
await recall.namespaces.delete(ns['id'], confirm=ns['name'])

Soft-delete does NOT free the underlying Postgres rows immediately. The retention worker eventually hard-deletes the data on the configured schedule. To force immediate purge, follow up with the hard-delete admin job (recall.namespaces.reembed(...) is unrelated; see ops documentation for the hard-delete tool).

reembed()

POST/v1/namespaces/:id/reembedJWTstable
def reembed(
    self,
    namespace_id: str,
    *,
    types: Optional[List[str]] = None,
    only_missing: Optional[bool] = None,
    batch_size: Optional[int] = None,
    embedder: Optional[str] = None,
    force: Optional[bool] = None,
) -> Dict[str, Any]: ...

Enqueue a reembed admin job. Returns a JobAcceptedResponse (HTTP 202) with a jobId; poll progress with recall.jobs.get(job_id).

Parameters

ParameterTypeRequired
typeslist[str]optional

Restrict to specific memory types. Empty/omitted = all.

only_missingbooloptional

Skip memories that already have an embedding. Default: True server-side.

batch_sizeintoptional

Per-batch size; clamped server-side to [1, 500].

embedderstroptional

Pin a non-default embedder model. Null/omitted keeps the namespace default.

forcebooloptional

Permit destructive operations. When True, the worker drops and rebuilds the pgvector index if the embedder dimension differs. Default False rejects dim changes.

job = recall.namespaces.reembed(
    'ns_01HEX…',
    only_missing=True,
    batch_size=100,
)
print('Job:', job['jobId'], '→', job['status'])
job = await recall.namespaces.reembed(
    'ns_01HEX…',
    only_missing=True,
    batch_size=100,
)

Polling pattern

import time

job = recall.namespaces.reembed('ns_…', only_missing=True)
job_id = job['jobId']

while True:
    status = recall.jobs.get(job_id)
    print(f"{status['status']}: {status['processed']}/{status['totalEstimated']}")
    if status['status'] in {'done', 'failed', 'cancelled'}:
        break
    time.sleep(5)

See Jobs for richer polling patterns including exponential backoff.

reextract()

POST/v1/namespaces/:id/reextractJWTstable
def reextract(
    self,
    namespace_id: str,
    *,
    types: Optional[List[str]] = None,
    batch_size: Optional[int] = None,
) -> Dict[str, Any]: ...

Re-resolve persisted entity-name surface forms (subject_name, object_name) against the current entity dictionary. Updates each memory's subject / object EntityId whenever resolution yields a different result. NO-OP for memories whose surface forms are both NULL.

reextract does not invoke the LLM and does not scan content. It only re-resolves names that the write pipeline already extracted at write time. To discover new entity mentions in raw conversation text, re-run the write pipeline.

job = recall.namespaces.reextract(
    'ns_01HEX…',
    types=['fact', 'relation'],
)
job = await recall.namespaces.reextract(
    'ns_01HEX…',
    types=['fact', 'relation'],
)

Async background-job pattern

Both reembed and reextract follow the same enqueue-and-poll pattern:

  1. Call the resource method. Server returns 202 Accepted with a JobAcceptedResponse carrying jobId, namespaceId, totalEstimated, status='queued', and kind.
  2. Poll recall.jobs.get(jobId) until status is one of 'done', 'failed', or 'cancelled'.
  3. Inspect processed / failed_items / error for results.

Job state transitions are documented in Jobs along with the recommended polling backoff.

Errors

CodeStatusRetry
RecallAuthError401/403fatal

Missing JWT, expired JWT, or API-key auth attempted on the admin plane.

RecallNotFoundError404fatal

Namespace ID not found, or out of org.

RecallValidationError422fatal

confirm mismatch on delete; invalid mode or name on create; out-of-range batch_size.

RecallApiError409fatal

Concurrent provisioning or job-conflict (e.g. another reembed already running on the namespace).

Was this page helpful?

On this page