Skip to content

Users API

REST endpoints for the Users area of Lepsto.

The Users area publishes 49 REST endpoints. Paths are relative to https://api.lepsto.com; see Authentication for the required header.

GET/users/api-keys

List this product's API keys, newest first — id, label, type, visible prefix and revocation state. A server key's secret is never here; a publishable key's full value is, because it is not a secret.

MCP tool: users_apikeys_list

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
keys
object[]
•
—
POST/users/api-keys

Issue an API key for this product. type='publishable' mints a non-secret browser key (upk_…) that only names this product and stays readable afterwards. It is the only type: the secret server key (usk_…) is retired — the one route it authorized is now checked by the public edge against the product's platform public key.

MCP tool: users_apikeys_create

Parameters

NameInTypeRequiredDescription
namebody
string
•
A label only humans read — which backend or environment holds this key.
typebody
stringone of: publishable
•
'publishable' mints a non-secret key (upk_…) that identifies this product in browser flows and stays readable. It is the only key type: the secret server key (usk_…) is retired.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of the key. Pass it to users_apikeys_revoke.
key
string
•
The full secret key, shown ONCE. Store it now — it is never returned again, and a lost key is replaced by minting a new one.
name
string
•
The label a human gave this key — which backend or environment holds it.
type
stringone of: publishable
•
`publishable` — a browser-safe key (upk_…). It is the only type issued.
value
string | null
•
The full value of a publishable key, which is not a secret and stays readable. Null for a retired server key, whose secret was never stored.
prefix
string
•
`upk_` (or `usk_` on a retired server key) plus six characters — enough to recognise a key, never to use one.
createdAt
string (date-time)
•
When the key was issued (ISO 8601).
revokedAt
string (date-time) | null
•
When the key was revoked (ISO 8601). Null while it still works.
DELETE/users/api-keys/:id

Revoke an API key immediately. A revoked server key stops authenticating code exchanges on the next request; there is no grace period and no un-revoke.

MCP tool: users_apikeys_revoke

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the key to revoke. Revocation is immediate and final.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of the key that was revoked.
revoked
booleanone of: true
•
Always true — revocation is immediate and final.
GET/users/api-keys/:id

Read ONE of this product's API keys by id — its label, type, visible prefix, when it was issued and whether it has been revoked. The same shape users_apikeys_list returns per row. A server key's SECRET is not here and never will be: that value existed in exactly one response, ever, and what is stored is a hash. An unknown id, a malformed id and a key belonging to another product all answer alike.

MCP tool: users_apikeys_get

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the key to read — the `id` from users_apikeys_list.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of the key. Pass it to users_apikeys_revoke.
name
string
•
The label a human gave this key — which backend or environment holds it.
type
stringone of: publishable, server
•
`publishable` — a browser-safe key (upk_…), the only type issued. `server` — a retired secret key (usk_…), kept for audit and always revoked.
value
string | null
•
The full value of a publishable key, which is not a secret and stays readable. Null for a retired server key, whose secret was never stored.
prefix
string
•
`upk_` (or `usk_` on a retired server key) plus six characters — enough to recognise a key, never to use one.
createdAt
string (date-time)
•
When the key was issued (ISO 8601).
revokedAt
string (date-time) | null
•
When the key was revoked (ISO 8601). Null while it still works.
GET/users/audit

Read this product's audit log, newest first. Every management-plane mutation writes one entry (user.created, user.updated, user.deleted, user.banned, user.unbanned) with the actor, target and timestamp. Filter by action, target and time range; paginate with the opaque `nextCursor`.

MCP tool: users_audit_list

Parameters

NameInTypeRequiredDescription
actionquery
string
—
Exact action to filter by, e.g. 'user.banned'. Omit for all actions.
targetTypequery
string
—
Target kind to filter by, e.g. 'user'.
targetIdquery
string
—
Exact id of the audited target, e.g. a user id.
sincequery
string (date-time)
—
ISO-8601 lower bound (inclusive) on the event timestamp.
untilquery
string (date-time)
—
ISO-8601 upper bound (exclusive) on the event timestamp.
limitquery
integer
—
Page size, 1-100. Default 25.
cursorquery
string
—
Opaque cursor from a previous response's `nextCursor`. Omit for the first page.

Response

Returns object.

FieldTypeAlwaysDescription
events
object[]
•
The page of audit events, newest first.
nextCursor
string | null
•
Pass back as `cursor` to fetch the next page. Null when this page is the last.
GET/users/config

Read this product's end-user auth configuration (signup mode, password policy, session lifetime). Returns the defaults when nothing has been configured yet.

MCP tool: users_config_get

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
mfa
object
•
—
email
object
•
—
oauth
object
•
—
invite
object
•
—
status
stringone of: active, paused
•
—
captcha
object
•
—
passkeys
object
•
—
recovery
object
•
—
testMode
object
•
—
waitlist
object
•
—
productId
string
•
—
retention
object
•
—
protection
object
•
—
signupMode
stringone of: public, invite-only, waitlist
•
—
emailChange
object
•
—
notifications
object
•
—
allowedOrigins
string[]
•
—
passwordPolicy
object
•
—
sessionTtlHours
integer
•
—
lifecycleBlocked
boolean
•
—
lifecycleArchived
boolean
•
—
refreshSlidingDays
integer
•
—
refreshAbsoluteDays
integer
•
—
redirectUriAllowlist
string[]
•
—
accessTokenTtlMinutes
integer
•
—
captchaSecretConfigured
boolean
•
—
PUT/users/config

Create or update this product's end-user auth configuration. Only the fields you pass change; the rest keep their current values. Rejected while the product is paused (archived or blocked by the platform). `email.templates` sends an auth email from a PUBLISHED template in this product's mail toolkit instead of the built-in one ({ "<purpose>": "<template id>" }, null unbinds). The template is not checked by this server: if mail cannot render it at send time, the built-in email is sent instead and the fallback is audited. Variables Users passes, per purpose (`*` = required: the template must use it; `?` = sent only sometimes, so the template must not require it; the rest are always sent): sign_in: code*, expires_in_minutes, link_url? verify: code*, expires_in_minutes, link_url? password_reset: link_url*, expires_in_minutes invite: link_url*, expires_in_minutes email_change_confirm: link_url*, expires_in_minutes email_change_approve: link_url*, expires_in_minutes, new_address email_change_revert: link_url*, expires_in_minutes, new_address email_change_override_cancel: link_url*, expires_in_minutes, new_address, takes_effect_in password_changed: none new_device: browser? email_change_completed: none email_change_reverted: none mfa_reset_scheduled: link_url*, expires_in_minutes, takes_effect_in mfa_reset: none impersonation: reason waitlist_confirm: link_url*, expires_in_minutes waitlist_joined: link_url*, expires_in_minutes

MCP tool: users_config_upsert

Parameters

NameInTypeRequiredDescription
signupModebody
stringone of: public, invite-only, waitlist
—
How end users may join this product: 'public' (anyone), 'invite-only', or 'waitlist'. Default 'public'.
passwordPolicybody
object
—
Password rules for this product. Merged field-by-field into the current policy.
sessionTtlHoursbody
integer
—
End-user session lifetime in hours. Default 720 (30 days).
accessTokenTtlMinutesbody
integer
—
Access JWT lifetime in minutes, 5-60. Default 10. Short on purpose: a revoked session dies within one TTL.
refreshSlidingDaysbody
integer
—
Inactivity window of a session in days — every refresh slides it forward. Default 30.
refreshAbsoluteDaysbody
integer
—
Hard time-box of a session in days, counted from sign-in and never extended. Default 365.
redirectUriAllowlistbody
string[]
—
Callback URIs the browser code handoff may deliver a one-time authorization code to, up to 20. Matched EXACTLY — no wildcards, no prefixes, no fragments — so list every callback in full. Replaces the whole list.
allowedOriginsbody
string[]
—
Browser origins allowed to drive this product's end-user flows, up to 20. Scheme + host + port only, matched exactly. Replaces the whole list. Empty means "no browser origin" once this product has a publishable key.
protectionbody
object
—
Rate limits, lockout and breached-password settings. Merged field-by-field.
captchabody
object
—
Captcha (Cloudflare Turnstile) settings for flow creation. Merged field-by-field.
emailbody
object
—
Email OTP / magic-link settings. Merged field-by-field. Set `email.from` to a sender on a domain verified in this product's mail toolkit — until then the email strategies are not offered.
recoverybody
object
—
Password-recovery settings (whether it is offered, how long a reset link lives). Merged field-by-field.
emailChangebody
object
—
Email-change settings: confirmation lifetime, the old address's revert window, the post-revert cooldown and the operator-override delay. Merged field-by-field.
invitebody
object
—
Invite lifetime and re-invite cooldown. Merged field-by-field.
waitlistbody
object
—
Waitlist settings: the double-opt-in toggle, how long a pending entry is retained before the sweep erases it, the confirmation link lifetime, and the embeddable widget’s copy and colours. Merged field-by-field, including inside `widget`.
testModebody
object
—
Test mode: whether this product may have TEST USERS — accounts that are excluded from metering, kept out of the default directory listing, never really emailed, and (when the deployment sets one) able to sign in with the fixed magic OTP. REFUSED on a production deployment. Merged field-by-field.
retentionbody
object
—
How long DEAD rows are kept before the retention sweep removes them: spent one-time tokens, expired flow attempts, expired sessions and their refresh tokens. These are grace periods on top of expiry, never lifetimes — an unswept expired row is inert. Merged field-by-field.
mfabody
object
—
MFA settings: backup-code batch size, the operator-reset delay, how fresh authentication must be to enroll, and the second-factor lockout ceilings. Merged field-by-field.
notificationsbody
object
—
Which security notification emails this product sends. All on by default. Merged field-by-field.
passkeysbody
object
—
Passkey (WebAuthn) settings: whether this product is a relying party, the RP ID and name, and whether an assertion must be user-verified. Merged field-by-field. The `passkey` strategy is offered only when `enabled` is true AND an RP ID resolves.
oauthbody
object
—
OAuth provider credentials and switches, per provider. Merged field-by-field. A provider is offered as a sign-in strategy only when it is enabled AND a full client id + secret pair resolves — from here, or from the deployment's shared development credentials.

Response

Returns object.

FieldTypeAlwaysDescription
mfa
object
•
—
email
object
•
—
oauth
object
•
—
invite
object
•
—
status
stringone of: active, paused
•
—
captcha
object
•
—
passkeys
object
•
—
recovery
object
•
—
testMode
object
•
—
waitlist
object
•
—
productId
string
•
—
retention
object
•
—
protection
object
•
—
signupMode
stringone of: public, invite-only, waitlist
•
—
emailChange
object
•
—
notifications
object
•
—
allowedOrigins
string[]
•
—
passwordPolicy
object
•
—
sessionTtlHours
integer
•
—
lifecycleBlocked
boolean
•
—
lifecycleArchived
boolean
•
—
refreshSlidingDays
integer
•
—
refreshAbsoluteDays
integer
•
—
redirectUriAllowlist
string[]
•
—
accessTokenTtlMinutes
integer
•
—
captchaSecretConfigured
boolean
•
—
GET/users/keys

List this product's JWT signing keys, newest first: the key id (the `kid` a token header carries), its lifecycle state, and when it was created, retired and revoked. `current` signs; `retired` no longer signs but is STILL published in JWKS until `overlapExpiresAt`, so tokens it signed keep verifying; `standby` is the pre-generated successor and is not published; `revoked` was evicted from JWKS immediately. Key material — public or private — is never returned here.

MCP tool: users_keys_list

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
keys
object[]
•
—
POST/users/keys/revoke

THE KEY-COMPROMISE DRILL. Evict a signing key from the published JWKS IMMEDIATELY — no overlap, no grace — so every token it ever signed stops verifying at once, mint or promote a fresh signing key in its place, and (by default) sign every end user of this product out, because a token the attacker forged with the stolen key is indistinguishable from a real one. Pass `revokeSessions: false` only when you are certain no forged token was issued. Give a reason: it is the only account of the incident the audit log will keep. This is disruptive and irreversible — for routine rotation use users_keys_rotate.

MCP tool: users_keys_revoke

Parameters

NameInTypeRequiredDescription
kidbody
string
•
The key to evict, as listed by users_keys_list.
reasonbody
string
—
Why. Goes into the audit row and is the only account of the incident it keeps.
revokeSessionsbody
boolean
—
Sign every end user of this product out as well. Defaults to TRUE: a compromised signing key could have minted any of those sessions. Pass false ONLY when you know no forged token was ever issued.

Response

Returns object.

FieldTypeAlwaysDescription
currentKid
string
•
The fresh key minted or promoted in its place.
revokedKid
string
•
—
sessionsRevoked
integer
•
—
POST/users/keys/rotate

Rotate this product's JWT signing key with ZERO verifier downtime. The standby key (generated now if there is none) becomes the signing key, and the old one is retired into an overlap window — BOTH key ids stay in the published JWKS until `overlapExpiresAt`, so every token signed before the rotation keeps verifying for the rest of its life and no verifier has to re-fetch anything to keep working. The overlap is the product's access-token TTL plus five minutes. After it elapses the old key is revoked automatically and stops verifying. Use users_keys_revoke instead if the key is COMPROMISED and must die now.

MCP tool: users_keys_rotate

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
currentKid
string
•
The key signing from now on.
previousKid
string | null
•
The key just retired. Null if there was none.
overlapExpiresAt
string | null
•
When the retired key stops verifying. Both kids are in JWKS until then.
POST/users/ops/retention

What this product keeps and for how long. Returns the EFFECTIVE retention windows (spent one-time tokens, expired flow attempts, expired sessions and their refresh tokens) next to the documented defaults, the separate waitlist-entry window, the single cron that performs every sweep, and — deliberately — the list of what is NEVER swept and why. For the waitlist it also answers the two questions a published retention promise raises: whether this product's window is WIDER than the 180 days Lepsto published (`waitlistWiderThanPromised`, a flag rather than a refusal), and whether the job that honours it is actually running — `lastSweep` carries the last pass this replica performed, counting THIS product's erasures only, and is NULL when it has observed none — which is not the same as a pass that erased zero. The windows are GRACE PERIODS ON TOP OF EXPIRY, never lifetimes: every live read carries its own expiry predicate, so an unswept expired row is inert and a window of 0 shortens nothing. Change them with users_config_upsert `retention`.

MCP tool: users_ops_retention_get

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
sweep
object
•
The single retention job. There is exactly one — it replaced the sessions reaper and the waitlist sweep.
windows
object
•
This product’s EFFECTIVE windows — what it configured, with the documented default filling anything it did not.
defaults
object
•
The documented defaults, so a reader can tell a deliberate window from an inherited one without a second call.
notSwept
object[]
•
What this service deliberately keeps forever, and why. Reading a retention surface that lists only what IS deleted is how an operator concludes the audit log ages out — it does not.
lastSweep
object | null
•
The last sweep this replica performed, or NULL when it has observed none. Null and a run with `erased: 0` are DIFFERENT STATES and must never be collapsed: one means the job is working and there was nothing due, the other means this replica has no evidence the job ran at all. The durable per-entry trail is elsewhere — the append-only `waitlist.erased` audit rows.
waitlistRetentionDays
integer
•
How long an UNCONVERTED waitlist entry — still pending, or invited and never registered — is kept, counted from SIGN-UP. It lives in the `waitlist` config block rather than in `retention`, because it is not a grace period on a dead row: such an entry never expires on its own, so this window is the whole of what makes it go away — and the sweep ERASES it (audit row, `users/waitlist-entry.erased`) rather than deleting it.
waitlistPromisedMaxDays
integer
•
What Lepsto PUBLISHED, in its Privacy Policy §6: a waitlist address that never converted is deleted 6 months from sign-up. It rides along so the window above can be read against the promise it came from, rather than as a number with no provenance.
waitlistWiderThanPromised
boolean
•
True when this product configured a window WIDER than the published 180 days. Nothing refuses such a value and nothing clamps it — §6 is Lepsto’s own policy and a client Product answers to its own, under which a longer window may be perfectly lawful. This flag exists INSTEAD of that ceiling: a deliberate divergence from what Lepsto published must be diagnosable in one call rather than found by comparing two numbers nobody thought to compare.
POST/users/ops/slo

This product's service-level objectives and how it is doing against them RIGHT NOW: sign-in success rate, refresh error rate and mail-send failure rate, each with the objective, the evaluation window, the sample floor below which it is not judged, and the `users/alert.raised` kind a breach fires. Also returns the raw current window (sign-ins, refreshes, detected token reuse, mail sends) and the product's own trailing sign-in failure baseline — the number the per-product anomaly signal compares a spike against. A `status` of `insufficient_data` is NOT health: it means the window is too quiet to judge. The numbers come from ONE replica's in-process window, so treat them as a spot check rather than a fleet-wide error budget.

MCP tool: users_ops_slo_get

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
slos
object[]
•
Every published objective and its verdict for this window.
scope
stringone of: replica
•
These numbers come from ONE replica’s in-process window, not from the whole deployment. Treat them as a spot check, not as the fleet’s error budget.
window
object
•
The current window, exactly as the alert hook sees it.
GET/users/sessions

List the end-user sessions of one user of this product, newest first — one row per device, with its status, device metadata and both expiries (the sliding inactivity window and the hard time-box). Revoked and expired sessions are included so an operator can see what happened, not only what is live.

MCP tool: users_sessions_list

Parameters

NameInTypeRequiredDescription
userIdquery
string (uuid)
•
The end user whose sessions to list.

Response

Returns object.

FieldTypeAlwaysDescription
sessions
object[]
•
The sessions matching the filter, newest first.
GET/users/sessions/:sessionId

Read one end-user session in full: its status, device metadata, both expiries, the assurance level and factors it was established with, why it was revoked if it was, and the operator behind it if it is an impersonated session.

MCP tool: users_sessions_get

Parameters

NameInTypeRequiredDescription
sessionIdpath
string (uuid)
•
The session to read.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this session. Pass it to users_sessions_revoke.
ip
string | null
•
IP the session was opened from. Null when it was not recorded.
aal
stringone of: aal1, aal2
•
`aal2` only if a verified second factor completed.
amr
string[]
•
The factors this session was established with.
status
stringone of: active, revoked
•
Whether the session is still usable, expired, or revoked.
userId
string (uuid)
•
The end user this session belongs to.
createdAt
string (date-time)
•
When the session was opened (ISO 8601).
expiresAt
string (date-time)
•
When the current token stops working unless refreshed (ISO 8601).
revokedAt
string (date-time) | null
•
When the session was revoked (ISO 8601). Null while it is live.
userAgent
string | null
•
User agent the session was opened from. Null when none was sent.
revokedReason
string | null
•
Why the session ended, when it was revoked — e.g. `signout`, `ban`, `mfa_reset`. Null while it is live. The vocabulary is additive, so it is not an enum.
impersonatedBy
string | null
•
The operator behind an impersonated session, or null for an ordinary one.
lastRefreshedAt
string (date-time) | null
•
When the session last refreshed its tokens (ISO 8601). Null if it never has.
absoluteExpiresAt
string (date-time)
•
When the session ends no matter how often it refreshes (ISO 8601).
POST/users/sessions/impersonate

Start an impersonated session as one user of this product — support access, hard-capped. Returns an access token that expires within 30 minutes and CANNOT be refreshed; there is no refresh token. While it is in use, every credential, factor, identifier and session mutation is refused, the user is emailed (unless the product turned that notice off), and the whole thing is recorded in the audit trail against you. Give a real reason: it goes into the audit row and into that email.

MCP tool: users_sessions_impersonate

Parameters

NameInTypeRequiredDescription
userIdbody
string (uuid)
•
The end user to sign in as.
reasonbody
string
•
Why. Recorded in the audit trail and shown to the user in the notification email — write what you would want to read in an incident review (a ticket number and one sentence).

Response

Returns object.

FieldTypeAlwaysDescription
userId
string
•
—
expiresAt
string
•
When the session dies. It cannot be extended or refreshed.
expiresIn
integer
•
Seconds until the access token expires.
sessionId
string
•
—
accessToken
string
•
The access JWT of the impersonated session. There is NO refresh token.
POST/users/sessions/revoke

Revoke end-user sessions: pass `sessionId` to sign out one device, or `userId` to sign out every device of that user. Revocation is immediate in this toolkit's own checked verification; an access token already issued keeps verifying locally until it expires (≤10 minutes by default). Returns how many sessions this call actually revoked — a second call on the same target returns 0.

MCP tool: users_sessions_revoke

Parameters

NameInTypeRequiredDescription
sessionIdbody
string (uuid)
—
Revoke exactly this session.
userIdbody
string (uuid)
—
Revoke every session of this user.

Response

Returns object.

FieldTypeAlwaysDescription
revoked
integer
•
How many sessions were revoked. Zero when there was nothing live to revoke.
POST/users/stats/get

One snapshot of this product's auth deployment: directory users split by DERIVED status (active, contacts, waitlisted, banned, soft-deleted, GDPR-erased), sessions that are live right now, verified second factors, the sign-in success rate over `window`, and an abuse block — lockouts (with the MFA subset), failed sign-ins, detected refresh-token reuse and breached-password sightings. The populations are point-in-time; only the sign-in and abuse numbers honour `window`. A rising abuse block against a falling success rate is what credential stuffing looks like from here. `successRate` is null, not 0, when nothing was attempted.

MCP tool: users_stats_get

Parameters

NameInTypeRequiredDescription
windowbody
stringone of: 24h, 7d, 30d, 90d
—
Look-back window for the TIME-BOUNDED numbers — the sign-in counters, the abuse block and `sessions.createdInWindow`: '24h', '7d', '30d' or '90d'. Default '7d'. The user, live-session and factor populations are point-in-time and ignore it.

Response

Returns object.

FieldTypeAlwaysDescription
abuse
object
•
Abuse signals over the window. Denied origins are deliberately absent: that check fires on unauthenticated traffic and is served by logs and metrics, not by per-product audit rows.
since
string
•
ISO start of that window — the `created_at` floor every counter used.
users
object
•
Point-in-time directory populations by DERIVED status — `user` has no status column, these are computed from `kind`, `waitlisted` and the ban/delete/erase timestamps.
window
stringone of: 24h, 7d, 30d, 90d
•
The window the time-bounded numbers were computed over.
factors
object
•
Enrolled second factors (TOTP and backup-code batches).
signIns
object
•
Sign-in outcomes over the window, from the audit log.
sessions
object
•
Device sessions.
GET/users/users

List the end users of this product, newest first. Filter by an identifier/name substring and by status. Soft-deleted users are hidden unless `includeDeleted` is true. CONTACTS — people the Product knows who have no credential and have never signed in — are hidden by default, exactly as waitlist signups and test users are; pass `kind: "contact"` to list only them, or `includeContacts: true` to see both. Filter further by `stage` (new, contacted, replied, engaged, converted, lost) and by a single `tag`, both ANDed with everything else. `query` matches a substring of the display name or of ANY identifier's canonical value — an address, a phone in E.164, a handle. Paginate with the opaque `nextCursor`.

MCP tool: users_users_list

Parameters

NameInTypeRequiredDescription
queryquery
string
—
Case-insensitive substring match against the user's identifiers (email) and name.
statusquery
stringone of: active, banned, waitlisted
—
Filter by status: 'active' (not banned, not waitlisted), 'banned', or 'waitlisted'. Omit for all.
includeDeletedquery
boolean
—
Include soft-deleted users. Default false — deleted users are hidden.
includeWaitlistedquery
boolean
—
Include waitlist signups. Default false: a waitlisted row is somebody who ASKED to join and cannot sign in, so it is not part of the directory an operator browses. Use `status: "waitlisted"` to see only them, or `users_waitlist_list` for the funnel.
includeTestUsersquery
boolean
—
Include TEST USERS. Default false: a test user is a fixture, not a customer — it is excluded from metering and from this listing for the same reason. Ask for it explicitly when you are debugging the fixtures themselves.
kindquery
stringone of: account, contact
—
Filter by kind: 'account' or 'contact'. Passing 'contact' is how you list contacts — they are hidden otherwise. Omit for accounts only, unless `includeContacts` is true.
stagequery
stringone of: new, contacted, replied, engaged, converted, lost
—
Filter by relationship stage: new, contacted, replied, engaged, converted or lost. Omit for every stage.
tagquery
string
—
Return only people carrying this tag. ONE tag, matched against the stored (lowercased) value and ANDed with the other filters.
includeContactsquery
boolean
—
Include CONTACTS. Default false: a contact has no credential and has never signed in, so it is not part of the directory an operator browses — the same rule waitlist signups and test users follow. Use `kind: "contact"` to see only them.
lastTouchAfterquery
string (date-time)
—
Only people whose most recent logged interaction happened at or after this instant (ISO 8601). People never touched are EXCLUDED by this filter — list without it to see everyone.
lastTouchBeforequery
string (date-time)
—
Only people whose most recent logged interaction happened before this instant (ISO 8601) — the "gone quiet" list. People never touched are EXCLUDED rather than treated as infinitely stale; list without this filter to see everyone.
limitquery
integer
—
Page size, 1-100. Default 25.
cursorquery
string
—
Opaque cursor from a previous response's `nextCursor`. Omit for the first page.

Response

Returns object.

FieldTypeAlwaysDescription
users
object[]
•
The page of users, newest first.
nextCursor
string | null
•
Pass back as `cursor` to fetch the next page. Null when this page is the last.
POST/users/users

Create an end user of this product with at least one email identifier. Supply password to store a password hash; omit it to create the user without a password identity. Supply only hashes in password fields; do not send plaintext passwords. Only one verified copy of an email is allowed per product; unverified duplicates are allowed. Pass `kind: "contact"` to record somebody the Product knows who cannot sign in: a contact may not carry `password`, `waitlisted` or `requiresReset` (refused with `contact_cannot_hold_credentials`), is hidden from the default users_users_list view, and is never counted as a retained user. `stage` and `tags` set the relationship state; tags are stored trimmed, lowercased and de-duplicated. Besides `email`, an identifier may be a CONTACT CHANNEL — `phone`, `telegram`, `linkedin` or `x`. A channel is how to REACH somebody, not how they sign in: it is always stored `unverified` (a `verified: true` on one is ignored), it can never start a sign-in, carry a one-time code or be verified, and it is canonicalised by its own rule — `phone` to E.164 (`+7 (999) 123-45-67` is stored `+79991234567`), `telegram` and `x` lowercased without a leading `@`, `linkedin` to the public-profile slug, so a full `linkedin.com/in/<slug>` URL and the bare slug are one value. A value that does not fit its type is refused with `invalid_identifier`. One record may not carry the same channel twice (`duplicate_identifier`), but two different people MAY share one — a company phone is not a conflict. A contact may carry a channel and no address at all.

MCP tool: users_users_create

Parameters

NameInTypeRequiredDescription
namebody
string
—
The user's display name.
imageUrlbody
string (uri)
—
URL of the user's avatar image.
identifiersbody
object[]
•
How this user is reached: their email addresses, plus any contact channels (`phone`, `telegram`, `linkedin`, `x`). At least one is required.
passwordbody
object
—
Credential to store for this user. Supply hashes only; omit password to create no password identity.
waitlistedbody
boolean
—
Create the user as a waitlist signup: no way in until promoted. Default false.
testUserbody
boolean
—
Create this user as a TEST FIXTURE: excluded from metering (it never counts toward retained-users), hidden from the default users_users_list view, never really emailed, and — when the deployment configures one — able to sign in with the fixed magic OTP. REFUSED unless this product has test mode enabled, which a production deployment cannot do. There is no way to set or clear this later. Default false.
kindbody
stringone of: account, contact
—
What kind of person to create. 'account' (the default) can sign in. 'contact' is somebody the Product knows who has NO credential — a contact may not carry `password`, `waitlisted` or `requiresReset` (`contact_cannot_hold_credentials`), is hidden from the default users_users_list view, and is never counted as a retained user. A contact becomes an account only by proving an address, never by an operator setting this.
stagebody
stringone of: new, contacted, replied, engaged, converted, lost
—
The relationship stage to start them at, in funnel order: new, contacted, replied, engaged, converted, lost. Default 'new'.
tagsbody
string[]
—
Free labels for this person. Trimmed, lowercased and de-duplicated on write; at most 32 of at most 64 characters each.
requiresResetbody
boolean
—
Force a password reset before the user can sign in (use for unverifiable imported hashes). Default false.
publicMetadatabody
object
—
Metadata the end user may READ and that may flow into the JWT. Management-plane write only.
privateMetadatabody
object
—
Metadata visible to the management plane only. Never exposed to the end user.
unsafeMetadatabody
object
—
Metadata the end user may WRITE in a later phase. Never trust it for authorization.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
DELETE/users/users/:userId

Soft-delete an end user: the record is retained with `deletedAt` set and disappears from the default list. This is NOT GDPR erasure — no data is purged, and the user can still be fetched by id.

MCP tool: users_users_delete

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The opaque user id returned by users_users_create or _list.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
GET/users/users/:userId

Fetch one end user of this product by id, including their identifiers and metadata bags. Soft-deleted users are still returned (with `deletedAt` set). Identifiers include contact channels (`phone`, `telegram`, `linkedin`, `x`) alongside emails; a channel is always `unverified`.

MCP tool: users_users_get

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The opaque user id returned by users_users_create or _list.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
PATCH/users/users/:userId

Update an end user's profile, status flags and metadata bags. Only the fields you pass change. A metadata field REPLACES that whole bag — send the full object you want stored. Write matrix in this phase: the management plane writes all three bags (public: end-user readable and may flow into the JWT; private: management plane only; unsafe: end-user writable later and never authoritative for authorization). `stage` moves the person along the funnel and publishes `users/user.stage-changed`; a patch that changes ONLY the stage deliberately does not also publish `users/user.updated`, so a workflow that writes stages does not wake itself. `tags` REPLACES the whole list. `kind` is NOT editable here and is refused with `kind_not_updatable`: a contact becomes an account only by proving an address.

MCP tool: users_users_update

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The user to update.
namebody
string | null
—
The user's display name. Pass null to clear.
imageUrlbody
string (uri) | null
—
URL of the user's avatar image. Pass null to clear.
waitlistedbody
boolean
—
Whether the user is still a waitlist signup.
requiresResetbody
boolean
—
Whether the user must reset their password before signing in.
lockedUntilbody
string (date-time) | null
—
ISO-8601 timestamp of a temporary, self-healing lockout — the failed-attempt ceiling sets it and it expires on its own. Pass null to unlock the account immediately (audited as `user.unlocked`).
publicMetadatabody
object
—
REPLACES the whole public metadata bag. Management-plane write, end-user read.
privateMetadatabody
object
—
REPLACES the whole private metadata bag. Management plane only.
unsafeMetadatabody
object
—
REPLACES the whole unsafe metadata bag. End-user writable in a later phase; never authoritative.
stagebody
stringone of: new, contacted, replied, engaged, converted, lost
—
Move the person to this relationship stage. A real change publishes `users/user.stage-changed`; a patch that changes ONLY the stage deliberately does not also publish `users/user.updated`, so a workflow that writes stages does not wake itself.
tagsbody
string[]
—
REPLACES the whole tag list — send every tag you want stored, not just the new ones. Trimmed, lowercased and de-duplicated on write.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
POST/users/users/:userId/ban

Ban an end user by stamping `bannedAt` and revoking their live sessions. Banned users are refused at sign-in. Banning an already-banned user refreshes the stamp.

MCP tool: users_users_ban

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The opaque user id returned by users_users_create or _list.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
GET/users/users/:userId/factors

List the MFA factors an end user has enrolled: `totp` (an authenticator app) and `backup_codes` (single-use recovery codes), each with its status (`unverified` until enrollment was confirmed — an unverified factor gates NOTHING), when it was created and confirmed, and when it was last used. Backup codes also report `remaining`, the number still unspent; a user at 0 can no longer use that factor and should regenerate. NO SECRET IS EVER RETURNED — not the TOTP secret, not a backup code, not a digest of one. A user with no factors returns an empty list, which also means their sign-in completes at `aal1`.

MCP tool: users_factors_list

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user whose enrolled factors to list.

Response

Returns object.

FieldTypeAlwaysDescription
factors
object[]
•
Every second factor this user has — types and status only, never material.
POST/users/users/:userId/factors/reset

OPERATOR RESET of an end user's MFA, for the case the normal recovery cannot serve: the user lost their authenticator AND their backup codes. This does NOT remove anything now. It SCHEDULES the removal behind this product's mandatory delay (config `mfa.resetDelayHours`, default 72) and immediately emails the user's primary address an abort link; if that mailbox is alive and its owner clicks, nothing happens. When the delay elapses, EVERY factor is deleted, every backup code with it, and EVERY SESSION of that user is revoked (a session that reached `aal2` must not outlive the factor that earned it) — after which the user signs in with their first factor alone and enrolls again. Every step is audited (`factor.reset_scheduled` / `_cancelled` / `_applied`) with the `reason` you pass, so do the identity proofing FIRST and write down what it was: this tool is a supervised account takeover, and the trail is what makes it reviewable.

MCP tool: users_factors_reset

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user whose second factor is being reset.
reasonbody
string
•
What identity proofing was done and under which ticket. Recorded verbatim in the audit row — this is the whole point of the tool being audited.

Response

Returns object.

FieldTypeAlwaysDescription
requestId
string (uuid)
•
Id of the scheduled reset. The same value arrives on `users/mfa.reset-settled`, which is what finishes this step.
scheduledFor
string (date-time)
•
When the removal happens, if nobody aborts it first (ISO 8601). The delay is this product's, not a fixed one.
GET/users/users/:userId/passkeys

List the passkeys (WebAuthn credentials) an end user has registered: the credential's id, the name the user gave it, its transports (`internal` is a platform passkey on the device itself, `usb`/`nfc`/`ble` a security key, `hybrid` a phone used from another device), the authenticator model's `aaguid`, whether the credential is `backedUp` (synced by the platform, so it survives losing the device), its sign counter and when it was last used. NO KEY MATERIAL IS EVER RETURNED — the public key is deliberately absent: nothing on this plane needs it, and it is a stable cross-site fingerprint for the device holding it. A user with passkeys signs in without a password, and reaches `aal2` in one gesture whenever their authenticator verified them.

MCP tool: users_passkeys_list

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user whose passkeys to list.

Response

Returns object.

FieldTypeAlwaysDescription
passkeys
object[]
•
The credentials this user has registered, newest first. Empty when they have none.
DELETE/users/users/:userId/passkeys/:passkeyId

REVOKE ONE passkey — the support path for a lost or stolen device. It takes effect IMMEDIATELY and permanently: that credential can never sign in again, and the user re-registers from a device they still hold. It is REFUSED (`status: 'failed'`, code `passkey_last_way_in`) when this credential is the account's only remaining way in — no other identity and no verified identifier — because removing it would strand the account forever; add another way in first. It cannot CREATE a passkey: there is no admin-side registration in this toolkit at all, since a credential an operator could enroll is one they could keep. Every revoke is audited (`passkey.deleted`) with the `reason` you pass, so do the identity proofing FIRST and write down what it was.

MCP tool: users_passkeys_revoke

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user whose passkey is being revoked.
passkeyIdpath
string (uuid)
•
Which credential to revoke — the `id` from `users_passkeys_list`.
reasonbody
string
•
Why this credential is being revoked, and under which ticket. Recorded verbatim in the audit row — this is the whole point of the tool being audited.

Response

Returns object.

FieldTypeAlwaysDescription
error
object | null
•
Present when the revoke was refused — today, only because it was the last way in.
status
stringone of: deleted, failed
•
`deleted` when the credential is gone, `failed` when the revoke was refused.
GET/users/users/:userId/passkeys/:passkeyId

Read ONE passkey of an end user by id — the name they gave it, its transports, the authenticator model's `aaguid`, whether it is `backedUp`, its sign counter and when it was last used. The same shape users_passkeys_list returns per row. NO KEY MATERIAL IS EVER RETURNED: the public key is deliberately absent here as it is there. A credential id that belongs to another user answers 404 exactly like one that never existed.

MCP tool: users_passkeys_get

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user the credential belongs to.
passkeyIdpath
string (uuid)
•
Which credential to read — the `id` from `users_passkeys_list`.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
Id of the credential. Pass it to users_passkeys_revoke.
name
string | null
•
The name the user gave this credential, so a person can tell their devices apart. Null when they never named it.
aaguid
string | null
•
The authenticator MODEL’s identifier, which says what kind of device this is, never which one. Null when the authenticator withheld it.
backedUp
boolean
•
True when the platform syncs this credential, so it survives losing the device. False means it lives on that device alone.
createdAt
string
•
When the credential was registered (ISO 8601).
signCount
integer
•
The authenticator’s own counter as of the last assertion. 0 when the authenticator does not count.
lastUsedAt
string | null
•
When it last signed in (ISO 8601). Null when it never has.
transports
string[]
•
How this authenticator can be reached. Empty when the browser reported nothing — the vocabulary is the platform’s and grows, so it is not an enum.
POST/users/users/:userId/primary-identifier

OPERATOR OVERRIDE of a user's primary email address, for the case the normal flow cannot serve: the old address is unreachable (dead mailbox, imported user), so the user cannot approve the change themselves. This does NOT swap the address now. It SCHEDULES the swap behind this product's mandatory delay (config `emailChange.overrideDelayHours`, default 72) and immediately emails the OLD address an abort link; if that mailbox is alive and its owner clicks, nothing happens. When the delay elapses the address is swapped, verified, and EVERY SESSION of that user is revoked. Every step is audited (`email_change.override_scheduled` / `_cancelled` / `_applied`) with the `reason` you pass, so do the identity proofing first and write down what it was. Use users_users_update for profile fields; this tool is only for the identity a user signs in with.

MCP tool: users_users_set_primary_identifier

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The user whose primary email address is being replaced.
emailbody
string
•
The address to make primary. Must not already belong, verified, to another user of this product.
reasonbody
string
•
What identity proofing was done and under which ticket. Recorded verbatim in the audit row — this is the whole point of the tool being audited.

Response

Returns object.

FieldTypeAlwaysDescription
requestId
string (uuid)
•
Id of the scheduled change. It appears in the audit rows and in the abort link mailed to the old address.
scheduledFor
string (date-time)
•
When the swap becomes applicable, if nobody aborts it first (ISO 8601). The delay is this product's `emailChange.overrideDelayHours`, 72 by default.
GET/users/users/:userId/touches

List a person's logged interactions, newest first — by when they were LOGGED, so a backdated touch still appears on the first page. Filter by channel. Paginate with the opaque `nextCursor`.

MCP tool: users_touches_list

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user whose touches to list.
channelquery
stringone of: message, call, meeting, note, email
—
Only touches on this channel. Omit for all of them.
limitquery
integer
—
Page size, 1-100. Default 50.
cursorquery
string
—
Opaque cursor from a previous response's `nextCursor`. Omit for the first page.

Response

Returns object.

FieldTypeAlwaysDescription
items
object[]
•
The page of touches, newest first.
nextCursor
string | null
•
Pass as `cursor` for the next page. Null when this was the last one.
POST/users/users/:userId/touches

Log an interaction with a person — a message, a call, a meeting, a note, or an email sent OUTSIDE the platform. Email this toolkit sent is NOT logged here; read those from the mail toolkit. `occurredAt` is when it HAPPENED and may be backdated; omit it to mean now. A 'note' or a 'meeting' takes direction 'none'. Who logged it is taken from your identity and can never be set from the body.

MCP tool: users_touches_create

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user this interaction was with.
channelbody
stringone of: message, call, meeting, note, email
•
How the person was touched: 'message', 'call', 'meeting', 'note' (an internal observation nobody sent) or 'email' sent OUTSIDE the platform. Email this toolkit sent is not logged here — read those from the mail toolkit.
directionbody
stringone of: outbound, inbound, none
•
Which way it went: 'outbound' (you reached them), 'inbound' (they reached you) or 'none'. A 'note' or a 'meeting' must be 'none'.
occurredAtbody
string (date-time)
—
When it happened (ISO 8601). Omit to mean now — pass it when logging after the fact, which may be any time in the past.
bodybody
string
•
What was said or done, free text, 1-8192 characters. Personal data: it is whatever an operator typed about this person.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this touch. Pass it to users_touches_update or _delete.
body
string
•
What was said or done, free text, 1-8192 characters. Personal data: it is whatever an operator typed about this person.
userId
string (uuid)
•
The end user this interaction was with.
actorId
string
•
Id of the operator or agent that logged it, verbatim.
channel
stringone of: message, call, meeting, note, email
•
How the person was touched: 'message', 'call', 'meeting', 'note' (an internal observation nobody sent) or 'email' sent OUTSIDE the platform. Email this toolkit sent is not logged here — read those from the mail toolkit.
actorType
stringone of: operator, agent
•
Who logged it: 'operator' (a human) or 'agent' (a workflow or agent principal).
createdAt
string (date-time)
•
When the touch was logged (ISO 8601).
direction
stringone of: outbound, inbound, none
•
Which way it went: 'outbound' (you reached them), 'inbound' (they reached you) or 'none'. A 'note' or a 'meeting' must be 'none'.
productId
string
•
Product this touch belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the touch was last edited (ISO 8601).
occurredAt
string (date-time)
•
When the interaction happened (ISO 8601) — not when it was logged.
DELETE/users/users/:userId/touches/:touchId

Discard a touch you logged. A touch somebody ELSE logged answers 404, exactly as a touch that does not exist does. Pass `force: true` to remove it anyway, which is recorded in the audit trail along with the original author. There is no undo.

MCP tool: users_touches_discard

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user the touch belongs to.
touchIdpath
string (uuid)
•
The touch to delete, from users_touches_list.
forcequery
boolean
—
Act on a touch somebody else logged. Without it a touch you did not author answers 404, exactly as a touch that does not exist does — the refusal must not tell you the row is there. Every forced call is recorded in the audit trail.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of the touch that was deleted.
userId
string (uuid)
•
The end user it belonged to.
deleted
booleanone of: true
•
Always true — a failure answers 404 instead.
PATCH/users/users/:userId/touches/:touchId

Correct a touch you logged — its text, when it happened, or both. A touch somebody ELSE logged answers 404, exactly as a touch that does not exist does: the refusal must not tell you the row is there. Pass `force: true` to edit it anyway, which is recorded in the audit trail along with the original author.

MCP tool: users_touches_update

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The end user the touch belongs to.
touchIdpath
string (uuid)
•
The touch to edit, from users_touches_list.
bodybody
string
—
Replacement text, 1-8192 characters. Omit to leave it unchanged.
occurredAtbody
string (date-time)
—
Correct when it happened (ISO 8601). Omit to leave it unchanged.
forcebody
boolean
—
Act on a touch somebody else logged. Without it a touch you did not author answers 404, exactly as a touch that does not exist does — the refusal must not tell you the row is there. Every forced call is recorded in the audit trail.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this touch. Pass it to users_touches_update or _delete.
body
string
•
What was said or done, free text, 1-8192 characters. Personal data: it is whatever an operator typed about this person.
userId
string (uuid)
•
The end user this interaction was with.
actorId
string
•
Id of the operator or agent that logged it, verbatim.
channel
stringone of: message, call, meeting, note, email
•
How the person was touched: 'message', 'call', 'meeting', 'note' (an internal observation nobody sent) or 'email' sent OUTSIDE the platform. Email this toolkit sent is not logged here — read those from the mail toolkit.
actorType
stringone of: operator, agent
•
Who logged it: 'operator' (a human) or 'agent' (a workflow or agent principal).
createdAt
string (date-time)
•
When the touch was logged (ISO 8601).
direction
stringone of: outbound, inbound, none
•
Which way it went: 'outbound' (you reached them), 'inbound' (they reached you) or 'none'. A 'note' or a 'meeting' must be 'none'.
productId
string
•
Product this touch belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the touch was last edited (ISO 8601).
occurredAt
string (date-time)
•
When the interaction happened (ISO 8601) — not when it was logged.
POST/users/users/:userId/unban

Lift a ban by clearing `bannedAt`. This removes the sign-in ban set by users_users_ban; previously revoked sessions stay revoked. Idempotent.

MCP tool: users_users_unban

Parameters

NameInTypeRequiredDescription
userIdpath
string (uuid)
•
The opaque user id returned by users_users_create or _list.

Response

Returns object.

FieldTypeAlwaysDescription
id
string (uuid)
•
Id of this end user. Pass it to any users_users_* tool.
kind
stringone of: account, contact
•
What kind of person this row is: 'account' (can sign in) or 'contact' (known to the Product, no credential, never signed in). A contact is hidden from the default users_users_list view and is never counted as a retained user.
name
string | null
•
The user's display name. Null when nobody set one.
tags
string[]
•
Free labels on this person, stored trimmed, lowercased and de-duplicated.
stage
stringone of: new, contacted, replied, engaged, converted, lost
•
The relationship stage, in funnel order: new, contacted, replied, engaged, converted, lost. A real change publishes `users/user.stage-changed`.
bannedAt
string (date-time) | null
•
When the user was banned (ISO 8601). Null while they are not banned.
erasedAt
string (date-time) | null
•
When this user's personal data was purged for good (ISO 8601). Null while it is intact.
imageUrl
string | null
•
URL of the user's avatar. Null when there is none.
testUser
boolean
•
Whether this is a test fixture — excluded from metering and from the default listing.
createdAt
string (date-time)
•
When the user was created (ISO 8601).
deletedAt
string (date-time) | null
•
When the user was soft-deleted (ISO 8601). Null while the account is live.
productId
string
•
Product this user belongs to. Never an input — it comes from the calling identity.
updatedAt
string (date-time)
•
When the user was last changed (ISO 8601).
mergedFrom
string (uuid) | null
•
Set when you asked for an id that was merged into this account — it is the id you asked for, and this is the row it now lives in. Null on every ordinary read.
waitlisted
boolean
•
Whether this user is still on the waitlist and cannot sign in yet.
identifiers
object[]
•
Every identifier of this user, the primary one flagged. To bind the primary address itself, use `primaryEmail` — `identifiers.0` means "the first", not "the primary".
lastTouchAt
string (date-time) | null
•
When this person was last touched — the most recent logged interaction (ISO 8601). Null when nobody has logged one. This is when the interaction HAPPENED, not when it was logged.
lockedUntil
string (date-time) | null
•
When a failed-attempt lockout expires (ISO 8601). Null when the user is not locked out.
lastSignInAt
string (date-time) | null
•
When the user last signed in (ISO 8601). Null if they never have.
primaryEmail
string | null
•
The user's primary email address, as typed. Null when they have no primary identifier. Bind this rather than `identifiers.0.value`.
requiresReset
boolean
•
Whether the user must set a new password at their next sign-in.
publicMetadata
object
•
Metadata the end user may read; the only bag projected into their access token. Capped at 1.2 KB.
unsafeMetadata
object
•
Metadata the end user may write themselves. Treat it as untrusted.
privateMetadata
object
•
Metadata for the management plane only. Never reaches the end user or a token.
mergedIntoUserId
string (uuid) | null
•
The user this person was merged INTO, when they were merged. Null for everybody else — follow it to find where their records went.
primaryIdentifierId
string (uuid) | null
•
Id of the primary identifier row. Null when there is no primary identifier.
emailChangeLockedUntil
string (date-time) | null
•
Until this instant the user may not start another email change, because somebody reverted one (ISO 8601). Null for a user who never reverted one.
GET/users/users/export

Export the end users of this product as pages of full records (profile, identifiers, metadata bags), using the same filters as users_users_list. Password hashes are NEVER included — neither a PHC hash nor an imported foreign one. Exporting credentials is deferred to a later phase, behind explicit elevated approval; until that gating exists, a hash cannot leave this toolkit. Every call is AUDITED (action `users.export`, with the filter and the row count), so this is not a silent read: use it for migrations, backups and subject-access requests, not as a listing shortcut. Filter by `kind`, or pass `includeContacts: true`, to export accounts and contacts in one pass — contacts are excluded by default, the same rule this export already applies to waitlist signups and test users. Page through with `nextCursor`. Identifiers include contact channels (`phone`, `telegram`, `linkedin`, `x`) alongside emails; a channel is always `unverified`.

MCP tool: users_users_export

Parameters

NameInTypeRequiredDescription
queryquery
string
—
Case-insensitive substring match against the user's identifiers (email) and name.
statusquery
stringone of: active, banned, waitlisted
—
Filter by status: 'active', 'banned' or 'waitlisted'. Omit for all.
includeDeletedquery
boolean
—
Include soft-deleted users. Default false.
kindquery
stringone of: account, contact
—
Filter by kind: 'account' or 'contact'. Omit for accounts only, unless `includeContacts` is true.
includeContactsquery
boolean
—
Include CONTACTS alongside accounts, so one pass exports both. Default false — the same rule this export already applies to waitlist signups and test users.
limitquery
integer
—
Rows per page, 1-500. Default 100.
cursorquery
string
—
Opaque cursor from the previous page's `nextCursor`. Omit for the first page.

Response

Returns object.

FieldTypeAlwaysDescription
count
integer
•
—
users
object[]
•
—
exportedAt
string
•
—
nextCursor
string | null
•
—
POST/users/users/import

Bulk-import end users into this product, up to 500 rows per call — batch client-side beyond that. Hashes only: pass `password.phcHash` for an argon2id PHC string, or `password.foreignHash` + `foreignHashAlgo` (bcrypt/argon2/pbkdf2/scrypt) for a hash from the system you are migrating off. A foreign hash is stored INERT — nothing verifies it until sign-in, when it is checked once and rehashed — so set `requiresReset: true` on rows whose source hash you do not trust. IDEMPOTENT by canonical identifier: a row whose email already exists VERIFIED in this product is skipped, not duplicated, so re-running the same file is safe. Imported identifiers default to verified (unlike users_users_create) — pass `verified: false` per identifier for addresses the source system never confirmed. Returns a PER-ROW report ({row, status, userId, reason}) plus counts: one malformed row never fails the batch. Emits ONE batch event (`users/user.imported`, with counts) rather than a per-user created event, and writes ONE audit row. Rows may be CONTACTS (`kind: "contact"` — known to the Product, no credential, never signed in); a contact row carrying `password`, `waitlisted` or `requiresReset` comes back as a FAILED ROW with `contact_cannot_hold_credentials`, never as a rejected batch. The result and the `users/user.imported` event carry a `contacts` count of the rows actually created. Contact channels (`phone`, `telegram`, `linkedin`, `x`) import as UNVERIFIED regardless of this tool's `verified: true` default — nothing here can prove a phone or a handle, so there is no confirmation to carry over. A row whose channel value does not fit its type comes back as a FAILED ROW with `invalid_identifier`, never as a rejected batch.

MCP tool: users_users_import

Parameters

NameInTypeRequiredDescription
usersbody
array
•
The users to import, at most 500 per call — batch client-side beyond that. Each row: identifiers (email), optional profile, optional password HASH, optional flags.

Response

Returns object.

FieldTypeAlwaysDescription
failed
integer
•
How many rows could not be imported at all.
created
integer
•
How many users were created.
results
object[]
•
One row per submitted row, in the order you sent them.
skipped
integer
•
How many rows were passed over — an address already in the directory, typically.
contacts
integer
•
How many of the CREATED users were contacts (`kind: 'contact'`). A row that was skipped or failed is not counted.
requested
integer
•
How many rows you submitted.
POST/users/users/invite

Invite somebody to this product by email. Creates the user immediately with an UNVERIFIED identifier and NO credential — they appear in users_users_list straight away but cannot sign in until they accept — then mails a one-time invite link. Accepting the link sets their first password and verifies the address; no session comes from the link itself. IDEMPOTENT by address: re-inviting returns the SAME user (`created: false`), and whether it re-mails is a cooldown decision, so pressing the button twice cannot flood a mailbox (`mailed: false` means the invite is live but no email went out this time). Refuses an address that already belongs to a signed-up user. Requires a verified sender in this product's mail toolkit (config `email.from`); without one nothing is sent and `mailed` is false.

MCP tool: users_users_invite

Parameters

NameInTypeRequiredDescription
emailbody
string
•
The address to invite. Stored canonicalised (lowercase, NFC) plus raw for display.
namebody
string
—
The invitee's display name.
publicMetadatabody
object
—
Metadata the end user may READ and that may flow into the JWT.
privateMetadatabody
object
—
Metadata visible to the management plane only. Never exposed to the end user.

Response

Returns object.

FieldTypeAlwaysDescription
mailed
boolean
•
False when the re-invite cooldown or the mail toolkit refused the send. The invite is still live.
userId
string (uuid)
•
The invited user, created immediately and listed straight away — they simply cannot sign in until they accept.
created
boolean
•
False when this address had already been invited; the same user is returned.
expiresAt
string (date-time)
•
When the invite link stops working (ISO 8601).
POST/users/waitlist/funnel

Count this product's waitlist by funnel stage. `pending` is people waiting, `unconfirmed` is double-opt-in signups whose address never confirmed (they are NEVER invited), `invited` have a live invite, `registered` accepted it and are now full users, `withdrawn` asked to be removed. One consistent snapshot, not five separate reads.

MCP tool: users_waitlist_funnel

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
total
integer
•
—
invited
integer
•
—
pending
integer
•
—
withdrawn
integer
•
—
registered
integer
•
—
unconfirmed
integer
•
—
POST/users/waitlist/get

Read ONE waitlist entry by user id — its funnel status, the address it joined with, where the signup came from (referrer/UTM), the consent proof recorded for it, and the timestamp of every stage it passed. The same shape users_waitlist_list returns per row. A user who is not on this product's waitlist answers 404.

MCP tool: users_waitlist_get

Parameters

NameInTypeRequiredDescription
userIdbody
string (uuid)
•
The waitlisted user to read — the `userId` from `users_waitlist_list`.

Response

Returns object.

FieldTypeAlwaysDescription
status
stringone of: pending, invited, registered, withdrawn
•
Where in the funnel this entry is: `pending`, `invited`, `registered` or `withdrawn`.
userId
string (uuid)
•
The waitlisted user. Every users_users_* tool takes this id.
createdAt
string (date-time)
•
When the entry joined the waitlist (ISO 8601).
invitedAt
string (date-time) | null
•
When the invite was sent (ISO 8601). Null while the entry is pending.
identifier
string
•
The email address that signed up.
attribution
object
•
Where the signup came from — the campaign, referrer and landing page recorded at the time.
confirmedAt
string (date-time) | null
•
When the address confirmed a double opt-in (ISO 8601). Null when it never did.
withdrawnAt
string (date-time) | null
•
When the address asked to be taken off the list (ISO 8601). Null when it did not.
consentProof
object
•
What was recorded to show this address consented: when, from where, and under which wording.
registeredAt
string (date-time) | null
•
When the invitee finished signing up (ISO 8601). Null until they do.
inviteTokenId
string | null
•
Id of the live invite token. Null while the entry has not been invited.
POST/users/waitlist/import

Import a LEGACY waitlist into this product. Each row becomes a directory user in state `waitlisted` (no credential — a waitlist signup has nothing to sign in with) plus a WaitlistEntry whose consent proof is stored VERBATIM: the policy version, the timestamp and the source the OLD system recorded, never the import time. Rides the Phase 1 bulk import, so it is IDEMPOTENT by canonical identifier — re-running the same file skips instead of duplicating — and reports PER ROW ({row, status, userId, reason}) so one bad row never fails the batch. Addresses that already have an entry are skipped `already_on_list`; addresses on the suppression list (they withdrew) are skipped `suppressed` and are NOT resurrected. SENDS NO EMAIL: an import is a migration, not a signup gesture. Imported addresses land UNVERIFIED, and by default confirmed — pass `confirmed: false` for rows the source system never reached, which then count as `unconfirmed` and are never invited. Up to 500 rows per call.

MCP tool: users_waitlist_import

Parameters

NameInTypeRequiredDescription
entriesbody
object[]
•
The legacy signups to import, at most 500 per call — batch client-side beyond that.

Response

Returns object.

FieldTypeAlwaysDescription
failed
integer
•
How many could not be imported at all.
results
object[]
•
One row per submitted row, in the order you sent them.
skipped
integer
•
How many were already on the list or otherwise passed over.
imported
integer
•
How many became waitlist entries.
requested
integer
•
How many rows you submitted.
POST/users/waitlist/invite

Promote waitlisted users by sending them the STANDARD invite email — the same one-time invite link `users_users_invite` sends, so accepting it sets their first password, verifies their address and turns the entry into `registered`. Batched: pass up to 100 user ids. Entries that already registered, that belong to another product, or whose address is on the suppression list (they withdrew) are SKIPPED with a reason rather than mailed. Requires a verified sender in this product's mail toolkit (config `email.from`).

MCP tool: users_waitlist_invite

Parameters

NameInTypeRequiredDescription
userIdsbody
string[]
•
The waitlisted users to promote, up to 100 per call. Each gets the STANDARD invite email; accepting it is what creates their credential.

Response

Returns object.

FieldTypeAlwaysDescription
invited
integer
•
How many of the requested users were invited.
results
object[]
•
One row per user you asked for, in the order you sent them.
skipped
integer
•
How many were passed over — see each row's `reason`.
POST/users/waitlist/list

List this product's waitlist entries, newest first. Each row is a directory user in state `waitlisted` PLUS the feature-local funnel state: where the signup came from (referrer/UTM), the consent proof recorded for it, and its status. Filter by `status` to work one stage of the funnel, or by `confirmed: false` to find double-opt-in signups whose address never confirmed. Paginate with `cursor`.

MCP tool: users_waitlist_list

Parameters

NameInTypeRequiredDescription
statusbody
stringone of: pending, invited, registered, withdrawn
—
Filter by funnel status: 'pending', 'invited', 'registered' or 'withdrawn'. Omit for all.
confirmedbody
boolean
—
Double opt-in only: false returns entries whose address never confirmed. Omit for both.
limitbody
integer
—
Entries per page, 1-100. Default 25.
cursorbody
string
—
Opaque cursor from the previous page's `nextCursor`. Omit for the first page.

Response

Returns object.

FieldTypeAlwaysDescription
entries
object[]
•
The page of waitlist entries, newest first.
nextCursor
string | null
•
Pass back as `cursor` to fetch the next page. Null when this page is the last.
GET/users/webhooks

List this product's webhook endpoints, newest first — url, subscribed events, paused state and the visible prefix of the current signing secret. The secret itself is never here.

MCP tool: users_webhooks_list

Parameters

No parameters.

Response

Returns object.

FieldTypeAlwaysDescription
webhooks
object[]
•
—
POST/users/webhooks

Register an outbound webhook endpoint for this product. Deliveries are signed with the Standard Webhooks wire format (webhook-id / webhook-timestamp / webhook-signature). The signing secret is returned EXACTLY ONCE — it is derived, never stored, so a lost secret is rotated with users_webhooks_rotate_secret, never recovered.

MCP tool: users_webhooks_create

Parameters

NameInTypeRequiredDescription
urlbody
string
•
Where deliveries are POSTed. HTTPS only, no embedded credentials.
eventsbody
string[]one of: user.created, user.updated, user.deleted, session.revoked
•
Which events to deliver. `user.created`, `user.updated` and `user.deleted` mirror the directory; `session.revoked` fires on sign-out, ban, erase and refresh-token reuse.
descriptionbody
string
—
A label only humans read — which system this endpoint feeds.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
—
url
string
•
—
active
boolean
•
—
events
string[]one of: user.created, user.updated, user.deleted, session.revoked
•
—
secret
string
•
—
createdAt
string
•
—
updatedAt
string
•
—
description
string | null
•
—
secretPrefix
string
•
—
DELETE/users/webhooks/:id

Delete a webhook endpoint, its signing secrets and its delivery log. Immediate and final; deliveries already in flight stop.

MCP tool: users_webhooks_delete

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the webhook endpoint.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
—
deleted
booleanone of: true
•
—
GET/users/webhooks/:id

One webhook endpoint. Another product’s endpoint and one that never existed answer identically.

MCP tool: users_webhooks_get

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the webhook endpoint.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
—
url
string
•
—
active
boolean
•
—
events
string[]one of: user.created, user.updated, user.deleted, session.revoked
•
—
createdAt
string
•
—
updatedAt
string
•
—
description
string | null
•
—
secretPrefix
string
•
—
PATCH/users/webhooks/:id

Change a webhook endpoint: its url, its subscribed events, its description, or `active` to pause and resume delivery without losing the endpoint or its secret.

MCP tool: users_webhooks_update

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the webhook endpoint.
urlbody
string
—
Where deliveries are POSTed. HTTPS only, no embedded credentials.
eventsbody
string[]one of: user.created, user.updated, user.deleted, session.revoked
—
Which events to deliver. `user.created`, `user.updated` and `user.deleted` mirror the directory; `session.revoked` fires on sign-out, ban, erase and refresh-token reuse.
descriptionbody
string | null
—
—
activebody
boolean
—
false pauses delivery without losing the endpoint or its secret.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
—
url
string
•
—
active
boolean
•
—
events
string[]one of: user.created, user.updated, user.deleted, session.revoked
•
—
createdAt
string
•
—
updatedAt
string
•
—
description
string | null
•
—
secretPrefix
string
•
—
GET/users/webhooks/:id/deliveries

This endpoint's recent deliveries, newest first: event type and id, status (pending / succeeded / failed), attempt count, the last response status and the last error. This is where 'my webhook never arrived' is answered.

MCP tool: users_webhooks_deliveries_list

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the webhook endpoint whose deliveries to list.
limitquery
integer
—
—

Response

Returns object.

FieldTypeAlwaysDescription
deliveries
object[]
•
—
POST/users/webhooks/:id/rotate-secret

Mint a NEW signing secret for this endpoint and return it exactly once. The previous secret keeps verifying for 24 hours — both signatures ride the webhook-signature header — so a receiver can swap its copy without dropping a delivery.

MCP tool: users_webhooks_rotate_secret

Parameters

NameInTypeRequiredDescription
idpath
string (uuid)
•
Id of the webhook endpoint.

Response

Returns object.

FieldTypeAlwaysDescription
id
string
•
—
url
string
•
—
active
boolean
•
—
events
string[]one of: user.created, user.updated, user.deleted, session.revoked
•
—
secret
string
•
—
createdAt
string
•
—
updatedAt
string
•
—
description
string | null
•
—
secretPrefix
string
•
—
Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select