Skip to content

Users API ​

Provision Programmatic Users — login-disabled service accounts — and mint API tokens for them, entirely from an API token. This is the token-driven counterpart to the dashboard's Configure → Members page and its per-user API Tokens tab.

All endpoints live under /api/client/v1 and are authenticated with a cpeer_ API token:

Authorization: Bearer cpeer_…

Both endpoints require an owner or admin token (the same gate as the Members dashboard page); any other token gets 403 Forbidden.

Programmatic Users only

POST /client/v1/users can never create a login-capable account. canLogin is forced to false server-side regardless of what the request body sends — there is no field that overrides this. To provision a human teammate with a password, use the dashboard's Invite flow (Configure → Members) or the session-authenticated members API instead.

Create a Programmatic User ​

http
POST /api/client/v1/users
Authorization: Bearer cpeer_…
Content-Type: application/json
json
{
  "name": "billing-sync-bot",
  "email": "billing-sync-bot@example.com",
  "role": "user",
  "servicePermissions": {
    "models": "read",
    "vector": "write"
  }
}
FieldTypeRequiredNotes
namestringyesTrimmed; must be non-empty.
emailstringnoProgrammatic Users don't need one — omit it entirely. When supplied it must be a valid address and unique within the tenant.
roleuser | project_admin | adminnoDefaults to user.
servicePermissionsobjectnoPer-service permission overrides (none | read | write | admin), same shape as a member's service permissions. Normalized server-side.

canLogin is not an accepted input field — it is always set to false on users created through this endpoint.

Response ​

201 Created

json
{
  "user": {
    "_id": "66aa1f2e9c1d4400123abcde",
    "canLogin": false,
    "createdAt": "2026-05-01T09:00:00.000Z",
    "email": "billing-sync-bot@example.com",
    "invitedAt": "2026-05-01T09:00:00.000Z",
    "invitedBy": "6650…",
    "name": "billing-sync-bot",
    "role": "user",
    "servicePermissions": { "models": "read", "vector": "write" },
    "updatedAt": "2026-05-01T09:00:00.000Z"
  }
}

invitedBy is the user id of the token owner that made the call. The user has a randomly generated password hash under the hood (unusable, since canLogin is false) — no password or temp-password field is ever returned.

Mint a token for a user ​

http
POST /api/client/v1/users/:id/tokens
Authorization: Bearer cpeer_…
Content-Type: application/json

Mints a new API token owned by the target user (:id) and returns the plaintext secret once — it is never retrievable again after this response. Works for any user in the caller's tenant, not just Programmatic Users: this is how you mint a service token for a login-disabled account (which can never reach the session-based token page itself), but it can also mint an extra token for a regular member.

json
{
  "label": "billing-sync nightly job",
  "servicePermissions": {
    "models": "read",
    "vector": "write"
  }
}
FieldTypeRequiredNotes
labelstringyesAt least 3 characters.
servicePermissionsobjectnoPer-service scope for the minted token. Omit for an unscoped token that inherits the target user's own effective permissions.

Scope clamping: the requested (or inherited) scope is always clamped down to the target user's own effective permissions, and additionally clamped to the calling token's own scope whenever the caller is itself a scoped token — a scoped token can never mint a token that reaches further than itself, for any target user, including one it just created via the endpoint above.

Response ​

201 Created

json
{
  "id": "665f1a2b3c4d5e6f70819a2b",
  "label": "billing-sync nightly job",
  "message": "API token created successfully",
  "servicePermissions": { "models": "read", "vector": "write" },
  "token": "cpeer_9f2a1c...redacted",
  "userId": "66aa1f2e9c1d4400123abcde"
}

token is the only time the plaintext secret is returned. servicePermissions is null on an unscoped token.

Errors ​

StatusCause
400name missing/blank, invalid role, invalid email format, or label shorter than 3 characters.
401Missing or invalid API token.
403Caller's token is not owned by an owner/admin user.
404:id does not resolve to a user in the caller's tenant.
409email already belongs to another user in the organization.
429User quota exceeded (create) or API token quota exceeded (mint).
500Internal error.

Example ​

Create a Programmatic User, then mint a scoped token for it:

bash
# Create a login-disabled service account
curl -X POST https://your-console.example.com/api/client/v1/users \
  -H "Authorization: Bearer cpeer_…" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "billing-sync-bot",
        "role": "user",
        "servicePermissions": { "models": "read", "vector": "write" }
      }'

# Mint an API token for the user just created (id from the response above)
curl -X POST https://your-console.example.com/api/client/v1/users/66aa1f2e9c1d4400123abcde/tokens \
  -H "Authorization: Bearer cpeer_…" \
  -H "Content-Type: application/json" \
  -d '{
        "label": "billing-sync nightly job",
        "servicePermissions": { "models": "read", "vector": "write" }
      }'

Store the returned token immediately — it is shown once, both here and in the dashboard's per-user token page.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub