SmartMemory
Guides

Authentication & Multi-Tenancy

SmartMemory's hosted API enforces tenant isolation at the security layer (the SecureSmartMemory wrapper), not in route handlers. Every request resolves to a MemoryScopeProvider that auto-injects scope metadata on writes and auto-filters on reads.

Required headers

HeaderRequiredPurpose
AuthorizationAll authenticated routesBearer <jwt> (Clerk-issued or refresh-rotated)
X-Workspace-Id (alias X-Team-Id)Most routes — workspace contextSelects the workspace data boundary
X-API-KeyAPI-key mode onlyReplaces Authorization for server-to-server

The active workspace is required by every route whose ScopePolicy is team_required (most write paths and ingestion). Routes with ScopePolicy.user_only work for any authenticated user without a workspace.

Isolation levels

service_common.security.scope_provider.IsolationLevel defines three modes:

LevelReads seeWrites are tagged with
WORKSPACE (default)Workspace data + the caller's personal itemsworkspace_id + tenant_id + user_id
USEROnly the caller's items within the workspaceworkspace_id + user_id
TENANTEvery workspace in the tenanttenant_id

Source: smart-memory-common/service_common/security/scope_provider.py. The default is intentionally permissive — the whole point of a workspace is shared collaboration. Drop to USER for personal-data-only workloads, raise to TENANT for organisation-wide admin tooling.

Auth flow — Clerk JWT

The hosted control plane uses Clerk for identity. The web SDK exchanges a Clerk session for a SmartMemory access + refresh token pair:

Clerk login                              POST /auth/clerk/session
   │                                       (body: { clerk_token })
   ▼                                       ──────────────────────►
client (browser) ──────► SmartMemory API
   ▲                                       ◄──────────────────────
   │                                       sets sm_access_token / sm_refresh_token
   │                                       cookies (or returns JSON for SDK)

Tokens are short-lived. The JS SDK's RefreshManager rotates them silently via POST /auth/refresh before expiry; on cookie auth, the body is empty and the refresh token is read from sm_refresh_token.

Manual flow:

curl -X POST https://api.smartmemory.ai/auth/clerk/session \
  -H 'content-type: application/json' \
  -d '{"clerk_token": "<clerk_jwt>"}'
# → { "access_token": "...", "refresh_token": "...", "user": {...} }

# Inspect the active session
curl https://api.smartmemory.ai/auth/me \
  -H 'authorization: Bearer <access_token>'

# Refresh
curl -X POST https://api.smartmemory.ai/auth/refresh \
  -H 'content-type: application/json' \
  -d '{"refresh_token": "..."}'

# Logout (this device)
curl -X POST https://api.smartmemory.ai/auth/logout \
  -H 'authorization: Bearer <access_token>'

# Logout (all devices)
curl -X POST https://api.smartmemory.ai/auth/logout-all \
  -H 'authorization: Bearer <access_token>'

/auth/onboarding/{start,complete,retry} drives the post-signup workspace provisioning sequence — usually called by the web UI after Clerk signup.

Auth flow — API keys

For server-to-server callers, mint a key under the user's account:

# Create
curl -X POST https://api.smartmemory.ai/memory/api-keys \
  -H 'authorization: Bearer <access_token>' \
  -H 'content-type: application/json' \
  -d '{
        "name": "ingestion-bot",
        "scopes": ["read:memories", "write:memories"],
        "expires_in_days": 90
      }'
# → { "id": "...", "key_prefix": "...", "key": "sm_live_..." }
# The full key is only shown once. Minted keys are prefixed `sm_live_`
# (production) or `sm_test_` (test environment).

# List
curl https://api.smartmemory.ai/memory/api-keys -H 'authorization: Bearer <token>'

# Revoke
curl -X DELETE https://api.smartmemory.ai/memory/api-keys/<key_id> \
  -H 'authorization: Bearer <token>'

Use the key as either Authorization: Bearer <key> or X-API-Key: <key>. Keys with the write:telemetry scope must be bound to a workspace (team_id in the create body).

The superadmin role

memory_service/api/routes/superadmin.py is registered with ScopePolicy.system. It's reserved for SmartMemory operators — cross-tenant queries, recovery tools, drift sweeps. Regular tenants cannot reach it; the superadmin role on UserResponse.roles is the gate.

Common patterns

Per-user memory inside a workspace

Create the MemoryScopeProvider with IsolationLevel.USER. Reads and writes are scoped to (workspace_id, user_id). Useful for personal scratchpads in a shared product.

from service_common.security.scope_provider import (
    MemoryScopeProvider,
    IsolationLevel,
)

scope = MemoryScopeProvider(
    user_id=user.id,
    tenant_id=user.tenant_id,
    workspace_id=team.id,
    isolation_level=IsolationLevel.USER,
)

Team-shared memory (default)

IsolationLevel.WORKSPACE is the default. All users in the workspace see shared items, plus their own personal items. This is the right choice for 99% of multi-user products.

Cross-tenant admin queries

Inside an admin tool, instantiate a scope provider with IsolationLevel.TENANT and only the tenant_id set. Combined with the superadmin role, this lets ops walk every workspace in a tenant.

Key code surfaces

  • smart-memory-service/memory_service/api/routes/auth.py — JWT + Clerk
  • smart-memory-service/memory_service/api/routes/api_keys.py — API keys
  • smart-memory-common/service_common/security/scope_provider.py — isolation enum
  • smart-memory-common/service_common/auth/core.py — request → scope provider
  • smart-memory-common/service_common/security/__init__.pySecureSmartMemory

See also

On this page