Upgrade to the New Authorization Model
What changes for existing deployments when the new authorization model arrives, and what to do about each change.
Upgrade to the New Authorization Model
This release replaces GoodMem's flat permission model with principals, roles, grants, and API-key ceilings. The security model overview explains the new system on its own terms. This guide covers the other half: what an existing deployment observes after upgrading, and what to do about it. Items appear roughly in order of how likely they are to affect you.
1. API Key Responses Have a New Shape
The user_id field on API keys is gone. A key now records which principal it authenticates as and
which principal administers it, and these can differ:
| Old field | New fields |
|---|---|
userId | subjectPrincipalId (who the key authenticates as), ownerPrincipalId (who administers the key) |
| — | authorityMode (INHERIT_SUBJECT or SCOPED), ceiling, validFrom, revokedAt, revokedById |
Client code that reads userId from key responses breaks. For a personal key, subjectPrincipalId
carries the value userId used to hold. The distinction between subject and owner matters once
service identities enter the picture; API Keys and Ceilings
explains the split.
2. Deactivating a Key Is Now Permanent
Setting a key's status to INACTIVE revokes it. Setting it back to ACTIVE fails with gRPC
FAILED_PRECONDITION, HTTP 412. DELETE /v1/apikeys/{id} also revokes rather than deleting:
the row survives with its audit history, the response is still 204, and the revoked key
continues to appear in listings as inactive.
Any automation that toggles keys off and on — a pause switch, a scheduled disable — needs a new design. The supported pattern is rotation: issue a replacement key, move the workload over, revoke the old one. Revocation is the end of a key's life, and the API now says so.
3. Keys That Worked Yesterday Return 401
Authentication now enforces the full set of validity rules on every request: the key must be
unrevoked, the current time must fall inside [validFrom, expiresAt), and the key's subject must
be an active principal. Keys that slipped through looser historical checks fail closed with 401.
A key's reported status is likewise derived from the clock and revocation state rather than a
stored value.
When a key starts returning 401 after the upgrade, check in this order:
revokedAt— the key was revoked, possibly by an old "deactivate" step (section 2).validFromandexpiresAt— the clock is outside the validity window.- The subject — the user or service identity the key authenticates as has been deleted (section 4).
goodmem apikey list --format jsonThe fix in every case is a replacement key. There is no repair operation for an ineligible key.
4. Deleted Users Return 404
Deleted users are permanent tombstones. /v1/users/me, /v1/users/{id}, and
/v1/users/email/{email} return 404 for a deleted user, and every API key whose subject is a
deleted user stops authenticating. The keys themselves are not marked revoked; they no
longer resolve to an active principal.
Separately, GetUser now takes exactly one selector — UUID, email, or username. The old request
shape allowed several at once with a priority order; sending more than one is now an error.
5. Permission Names Have Changed
The *_OWN and *_ANY permission strings are gone. Authority is now expressed as an operation
plus a selector:
| Old name | Operation | Selector |
|---|---|---|
LIST_SPACE_ANY | LIST_SPACE | ANY |
CREATE_APIKEY_OWN | CREATE_API_KEY | OWN |
Two selectors have no old equivalent: EXACT (one named resource) and DIRECT_MEMBERS_OF (the
direct members of one container, such as the memories in a space). Scripts, saved policies, and
internal documentation that mention the old names need the two-part vocabulary. The
operations and selectors reference has the
complete catalog.
6. New Retryable Errors
PostgreSQL serialization and deadlock failures now surface as gRPC ABORTED, HTTP 409, instead
of a generic 500. These are safe to retry: the transaction did not commit. Clients that treated
every 409 as a duplicate-resource conflict should distinguish the two cases; ALREADY_EXISTS
also maps to 409 but retrying it will not change the answer.
7. Regenerate Your Clients
The OpenAPI specification, the generated SDKs, and the CLI all change shape in this release.
Generated clients from earlier releases still carry removed fields such as userId and lack the
new key fields, so they fail in the ways described above even when your own code is correct.
Upgrade the SDKs and CLI to the versions matching your server before debugging anything else.
8. What Does Not Change
- Existing personal API keys issued before the upgrade keep working, provided they are unrevoked and their subject is active. They behave as inheriting keys: they carry their user's current authority.
- Spaces, memories, embedders, rerankers, and LLMs carry over untouched, along with their ownership.
- The bootstrap key created at install time is an ordinary inheriting key for the instance owner and continues to work.
- The
x-api-keyheader, the CLI profile format, and theGOODMEM_API_KEYenvironment variable are unchanged.
9. Suggested Order of Operations
- Find and fix any automation that toggles or deletes keys (section 2) and any code reading
userIdfrom key responses (section 1). - Regenerate SDK clients and upgrade CLIs (section 7).
- Translate any remaining
*_OWN/*_ANYreferences to operations and selectors (section 5). - Add retry handling for
ABORTED/409where your clients mutate data (section 6).
After that, the new machinery — roles, scoped keys, service identities — is available when you want it, and nothing requires you to adopt it on day one.