Users & Identity
MemorySync has one User table that backs three different identity patterns. The same row represents a dashboard human, a B2B end-user, or an OAuth client-credentials end-user, depending on which fields are populated. This page walks through every identity pattern and the headers that drive them.
User model fields
| Field | Role |
|---|---|
id | Internal numeric primary key. Never exposed in APIs. |
public_id | Stripe-style external id, prefixed usr_. Use this in client code. |
external_id | Optional. Used by SCIM and B2B end-user mapping. |
tenant_id | Outer scope key. |
email | Optional. |
encryption_key | Per-user key for memory ciphertext. |
api_key | Legacy field; current API keys live on a separate model. |
role | user, admin, developer. |
Three identity patterns, one model
| Pattern | Auth | How the end-user is identified |
|---|---|---|
| Dashboard user | JWT from /auth/login | The JWT user is the end-user. |
| B2B service | X-API-Key | X-End-User-ID header maps to a User row via composite external_id. |
| OAuth client-credentials | OAuth bearer token | X-End-User-ID header same as B2B. |
End-user resolution rule
- 1If the request is JWT-authenticated,
X-End-User-IDis ignored and the JWT subject is used. - 2If the request is API-key-authenticated,
X-End-User-IDis required for memory routes; missing it returns 400. - 3The end-user id is combined with the resolved tenant:
external_id = "{tenant_id}:{end_user_id}". This composite key prevents collisions across tenants. - 4If no User row matches, the platform creates one on first sight — idempotent on the composite key.
Public id vs internal id
The internal id is an integer primary key that never appears in API responses. The public_id is the opaque usr_-prefixed identifier that does. Always reference users by public_id in client code; assume the internal id can change shape.
Per-user encryption
Every User row carries an encryption_key. Memory text is encrypted with that key on insert and decrypted on read. Deleting a user destroys the key, which makes that user's memories unreadable even if their rows remain.
Roles
| Field | Allowed values |
|---|---|
User.role | user, admin, developer. |
Membership.role (org) | owner, admin, developer, analyst, member, viewer. |
User.role is the platform-wide capability; Membership.role scopes capabilities inside one organization.
What not to do with user ids
- Do not put a body
user_idon/memory/add— it is overwritten by the resolved end-user. - Do not pass
X-End-User-IDfrom a JWT-authenticated client — it is ignored, and you risk leaking your customer's id into logs. - Do not parse
public_idfor meaning — it is opaque.