Onboard a User
Create a human user, assign the standard USER role, and issue a one-time enrollment credential.
Onboard a User
This guide is for administrators adding a person to a GoodMem instance. The result of onboarding is an enrollment credential, which you deliver to the person. They exchange it for their own API key by following Redeem an Enrollment. Onboarding never issues the person's API key; only they can do that.
Creating a user has no side effects. A freshly created user has no credential, no role, and no grants — it is an identity and nothing more. Onboarding is the three administrative steps that turn that identity into a usable account:
- Create the user.
- Assign the instance
USERrole, which supplies the ordinary self-service capabilities. - Create a one-time enrollment credential bound to that user.
The goodmem user onboard command runs all three. Each step is a separate RPC and a separate
audit record; the command coordinates them but does not pretend they are one transaction. Over
REST you make the three calls yourself, as shown in The Manual Path.
Before You Start
- An administrative credential: the
goodmemCLI authenticated, or an API key for REST. - Authority to create users and manage enrollments. The instance
ADMINrole covers both. If you authenticate with a scoped API key, its ceiling must coverCREATE_USER,MANAGE_ACCESS, andMANAGE_USER_ENROLLMENT. - Export the values the REST examples use:
export GOODMEM_REST_URL="https://localhost:8080" # REST base URL; on GoodMem Cloud, your instance's https:// hostname
export GOODMEM_API_KEY="gm_your_key"The CLI speaks gRPC, which GoodMem Cloud instances do not expose. On Cloud, use the cURL or HTTPie tab — every step here is a plain REST call over HTTPS — or the console, whose Invite user wizard runs the same three steps. If your own server uses a self-signed certificate, add -k to curl and --verify=no to HTTPie.
Enrollment administration is restricted to authenticated humans. A service identity cannot create enrollment credentials, and the authority cannot be granted to all authenticated principals. This keeps workloads and broad grants away from account bootstrap.
The Short Path
goodmem user onboard \
--email [email protected] \
--username sarah \
--display-name "Sarah"Only --email is required. The command prints progress for each step to stderr, then the result:
User ID: 70e025f6-76ca-4cbe-b8fc-7dab8e84590a
Role Assignment ID: 0198b9f4-c42f-7ae3-8c56-44d827ea03bd
Enrollment ID: 3f1c9d22-8a41-4f0e-9c37-5be2a17d6c91
Enrollment Token: <one-time enrollment credential>The enrollment token is shown once. The server stores only a verifier, so nothing can retrieve it later. If it is lost before the person redeems it, rotate the enrollment and deliver the new token.
Deliver the token to the person over a channel you trust — a password-manager share, or a direct message on an internal system. The token is single-use, bound to that user, and expires 24 hours after creation.
For scripting, --quiet prints only the token, and --format json returns the full result
including per-step status.
onboard is a client-side convenience; there is no single REST endpoint behind it. Over REST, make
the three calls in The Manual Path in that order.
Skipping steps
--skip-rolecreates the user and enrollment without assigningUSER. Use this when the person should get a different set of capabilities — for example, only a space role assigned by a space administrator. They will be able to authenticate after enrolling, but can do very little until someone grants them authority.--skip-enrollmentcreates the identity and role only. Use this when you are provisioning accounts ahead of time and will create enrollments later. With this flag,--quietprints the user UUID instead of a token.
When a step fails
The command generates every UUID client-side before the first RPC, and reports each step as
SUCCEEDED, FAILED, UNKNOWN, or NOT_RUN. Completed steps are durable: if role assignment
fails, the user still exists, and rerunning onboard would try to create a second user. Resume
with the individual commands from the manual path below, using the IDs from the step report.
A step reported UNKNOWN means the CLI lost the response and cannot say whether the server
committed. Inspect before retrying — goodmem user get <user-id> (GET /v1/users/{userId}) and
goodmem user enrollment list <user-id> (GET /v1/users/{userId}/enrollments) show what exists on
the server.
The Manual Path
The same three steps as separate calls, for when you want each one explicit — and the only path over REST. Capture the user's UUID from the first response; the other two calls need it.
Create the identity:
goodmem user create --email [email protected] --username sarah --display-name "Sarah"
export USER_ID="<the UUID from the output>"export USER_ID="$(curl -sS --json @- "$GOODMEM_REST_URL/v1/users" \
--header "x-api-key: $GOODMEM_API_KEY" <<'JSON' | jq -r '.userId'
{ "email": "[email protected]", "username": "sarah", "displayName": "Sarah" }
JSON
)"export USER_ID="$(http POST "$GOODMEM_REST_URL/v1/users" x-api-key:"$GOODMEM_API_KEY" \
email="[email protected]" username="sarah" displayName="Sarah" | jq -r '.userId')"Assign the standard instance role. An instance role names the INSTANCE singleton as its resource,
with no resourceId:
goodmem access-policy role-assignment assign \
--principal $USER_ID --role USER --resource-kind INSTANCEcurl -sS --json @- "$GOODMEM_REST_URL/v1/access-policy/role-assignments" \
--header "x-api-key: $GOODMEM_API_KEY" <<JSON
{ "principalId": "$USER_ID", "role": "USER", "assignedResource": { "kind": "INSTANCE" } }
JSONhttp POST "$GOODMEM_REST_URL/v1/access-policy/role-assignments" x-api-key:"$GOODMEM_API_KEY" \
principalId="$USER_ID" role="USER" assignedResource:='{"kind": "INSTANCE"}'Create the enrollment credential. The REST response carries the token exactly once, in
enrollmentToken, alongside the enrollment's metadata, and is served with
Cache-Control: no-store:
goodmem user enrollment create $USER_IDcurl -sS --json '{}' "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
--header "x-api-key: $GOODMEM_API_KEY"http POST "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY"user create accepts --id (userId over REST) for a stable client-provided UUID, useful when
mirroring an external directory. The Built-in Roles
reference lists what USER grants; substitute a different role, direct grants, or a space role via
Share a Space if the defaults do not fit.
Managing Enrollments
A user has at most one open enrollment. The lifecycle operations — history, metadata for one enrollment (never the token), permanent revocation, and atomic rotation:
goodmem user enrollment list $USER_ID # history: pending, consumed, revoked, expired
goodmem user enrollment get $USER_ID $ENROLLMENT_ID # metadata only, never the token
goodmem user enrollment revoke $USER_ID $ENROLLMENT_ID # permanent
goodmem user enrollment create $USER_ID --rotate-existing # revoke and replace atomicallycurl -sS "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
--header "x-api-key: $GOODMEM_API_KEY"
curl -sS "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" \
--header "x-api-key: $GOODMEM_API_KEY"
curl -sS -X DELETE "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" \
--header "x-api-key: $GOODMEM_API_KEY"
curl -sS --json '{"rotateExisting": true}' "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
--header "x-api-key: $GOODMEM_API_KEY"http GET "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY"
http GET "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" x-api-key:"$GOODMEM_API_KEY"
http DELETE "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" x-api-key:"$GOODMEM_API_KEY"
http POST "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY" \
rotateExisting:=trueEnrollments expire on their own after 24 hours. Creating a new enrollment after expiration needs
no flag; replacing one that is still open requires --rotate-existing ("rotateExisting": true),
which revokes the old credential and issues the new one in a single transaction. Revoke without
replacement when an invitation was sent to the wrong person or the account should stay dormant.
Once the person has enrolled, the enrollment is consumed and cannot be reused. Eligibility for enrollment ends permanently at that point — deleting or revoking the person's keys does not restore it. Credential recovery is ordinary API-key rotation, performed by the person with a key they still hold; human keys are self-issued, so an administrator can revoke a compromised key but cannot mint the replacement. A person whose only working key is lost has no recovery path, which is why Redeem an Enrollment advises minting a backup key immediately after enrolling.
Enrollment administration can be delegated without granting ADMIN. An EXACT grant scopes it to
one user:
goodmem access-policy grant create \
--principal $MANAGER_ID \
--operation MANAGE_USER_ENROLLMENT \
--selector EXACT --resource-kind USER --resource-id $USER_IDcurl -sS --json @- "$GOODMEM_REST_URL/v1/access-policy/grants" \
--header "x-api-key: $GOODMEM_API_KEY" <<JSON
{
"audience": { "principalId": "$MANAGER_ID" },
"rule": { "operation": "MANAGE_USER_ENROLLMENT", "selector": "EXACT",
"assignedResource": { "kind": "USER", "resourceId": "$USER_ID" } }
}
JSONhttp POST "$GOODMEM_REST_URL/v1/access-policy/grants" x-api-key:"$GOODMEM_API_KEY" <<JSON
{
"audience": { "principalId": "$MANAGER_ID" },
"rule": { "operation": "MANAGE_USER_ENROLLMENT", "selector": "EXACT",
"assignedResource": { "kind": "USER", "resourceId": "$USER_ID" } }
}
JSONThe manager must be an authenticated human, and can then create, rotate, and revoke that one user's enrollments. Nobody can manage their own.
Further Reading
- Redeem an Enrollment — the guide to hand to the person you just onboarded.
- Users and Service Identities — why identity, role, and credential are separate steps.
- Security Model — how GoodMem evaluates authority once the person is enrolled.
- Console: Access Control — the Invite user wizard and the per-user enrollment controls.
- REST reference: users, access policy.