GoodMemGoodMem
ReferenceSecurity

Access Control

Authentication mechanics, authorization checks, and error semantics across GoodMem APIs.

Access Control

GoodMem authenticates every request with an API key, then authorizes the operation the request performs. This page covers the wire mechanics and error semantics. For the model behind them — principals, grants, roles, and ceilings — start with the security model overview.

Authentication

Send the API key in one of these ways:

  • CLI: --api-key, then GOODMEM_API_KEY, then the active CLI profile.
  • REST/gRPC: x-api-key: <api-key>.

REST and gRPC accept the key only in the x-api-key header. The MCP endpoint additionally accepts Authorization: Bearer <api-key> for compatibility with MCP clients.

A key authenticates a request when all of the following hold:

  • The key has not been revoked.
  • The current time falls inside the key's validity window: at or after valid_from, and before expires_at when one is set.
  • The key's subject principal is active. A key whose subject has been deleted stops authenticating, even though the key itself is never marked revoked.

A request that fails any of these returns UNAUTHENTICATED over gRPC and 401 Unauthorized over REST. The response does not say which condition failed.

Endpoints That Skip API-Key Authentication

Three methods run without an API key:

REST routegRPC methodReason
POST /v1/user-enrollments:completeUserService/CompleteUserEnrollmentThe caller has no API key yet. The one-time enrollment credential authenticates the call, and the endpoint is rate-limited (see below).
POST /v1/system/initUserService/InitializeSystemOne-time instance bootstrap, before any key exists.
GET /v1/system/infoSystemService/GetServerInfoServer metadata clients may need before authenticating.

Every other GoodMem API method requires a key. Infrastructure endpoints are also served without a key: gRPC health checking and reflection, the REST probes /health, /livez, /readyz, and /startupz, and the /openapi documentation UI.

Authorization

Each API method maps to an operation from the operations catalog, applied to a target resource. The request succeeds when the authenticated principal's live authority — assembled from direct grants, role assignments, all-authenticated grants, and ownership — allows that operation on that target. When the request authenticates through a scoped API key, the key's immutable permission ceiling must allow the same operation and target independently.

If authentication succeeds but authorization fails, gRPC returns PERMISSION_DENIED and REST returns 403 Forbidden.

Error Precedence

Most resource API requests are validated in this order, and the first failure determines the status:

  1. INVALID_ARGUMENT — the request is malformed.
  2. NOT_FOUND — the target does not exist in the requested lifecycle view. Soft-deleted principals return NOT_FOUND unless the request sets include_deleted.
  3. PERMISSION_DENIED — the target exists, but the caller lacks authority over it.
  4. FAILED_PRECONDITION — the target's state does not permit the transition, such as reactivating a revoked API key or updating a deleted principal.
  5. ALREADY_EXISTS — a uniqueness conflict, such as reusing an email, username, or client-provided UUID.

Lookups by email or username are an exception to step 3: a missing record and an existing but unreadable record both return NOT_FOUND, so these guessable identifiers do not reveal whether a user exists. UUID lookups keep the ordinary NOT_FOUND/PERMISSION_DENIED distinction.

Some RPCs enforce caller-type preconditions before resolving the target. For example, only human principals may administer user enrollments. CompleteUserEnrollment also deliberately collapses unusable enrollment credentials to UNAUTHENTICATED rather than exposing target or authorization details.

HTTP Status Mapping

REST endpoints translate gRPC status codes as follows:

gRPC statusHTTP statusNotes
UNAUTHENTICATED401Missing, invalid, expired, or revoked key, or a deleted subject.
PERMISSION_DENIED403Authenticated but not authorized.
NOT_FOUND404Includes unreadable email/username lookups, as above.
FAILED_PRECONDITION412409 instead when the server marks the failure as a resource-in-use conflict.
ALREADY_EXISTS409Uniqueness conflict.
ABORTED409Transient database conflict (serialization failure or deadlock). Safe to retry with backoff.
RESOURCE_EXHAUSTED429Rate limited. The response carries a Retry-After header.

Enrollment Completion Rate Limits

POST /v1/user-enrollments:complete is unauthenticated, so it carries its own throttle, independent of license-based request quotas. The limits apply even when the license sets no API limits at all.

Default policy:

  • 20 attempts per minute per client source address.
  • 200 attempts per minute in a shared fallback bucket, used when no peer address is available.
  • Each bucket permits a burst up to its full capacity and refills continuously at the stated per-minute rate.

Rejected attempts return RESOURCE_EXHAUSTED over gRPC and 429 over REST, with a Retry-After header.

OCR

OCR requests require the OCR_DOCUMENT operation. Missing or invalid credentials yield UNAUTHENTICATED/401, and missing authority yields PERMISSION_DENIED/403. OCR is provided by the GoodMem OCR add-on service/image and is not included in the base install, so requests fail if the add-on is not enabled even when authority is present.

See also: