Get a user's sign-in state

GET /v1/users/{user_id}/sign-in-state · permission users:read

Returns what your support team needs when a user cannot sign in: the user's open account lockouts, the failure counts that are still running on each sign-in path, and the last successful authentication. The call only reads; nothing is changed and no audit record is written. To remove the lockouts, use POST /v1/users/{user_id}:unlock.

The response never carries the counter's internal subject, an email address, an IP address, a device or a session id. For the user's sessions, use GET /v1/users/{user_id}/sessions.

Organization scope

Pass org_id to build a member diagnostics view for an organization admin. The key then also needs the organizations:read permission; without it the request fails with 403 insufficient_permissions before anything is read. If the user is not a member of that organization (no invited, active or suspended membership), the request fails with 404 not_found, the same response as for an unknown user. If the user is a member, last_sign_in comes only from sessions in the context of that organization, and failures is always an empty array, because failure counters are not attributed to any organization. Locks are still returned, since a lock stops the account's sign-in in every context. Without org_id the response is unchanged.

Request

Parameter In Required Description
user_id path yes User id, prefixed usr_.
org_id query no Organization id, prefixed org_. Limits the view to that organization; see Organization scope. Needs organizations:read.

Response

200 OK.

{
  "locked": true,
  "locked_until": "2026-10-20T10:00:00Z",
  "locks": [
    {"id": "lck_0a1b2c3d4e5f6g7h8j9k0m1n2p", "path": "password",
     "locked_at": "2026-10-20T09:45:00Z", "expires_at": "2026-10-20T10:00:00Z"}
  ],
  "failures": [
    {"path": "totp", "count": 3, "window_ends_at": "2026-10-20T09:55:00Z"}
  ],
  "last_sign_in": {"authenticated_at": "2026-10-19T18:12:04Z", "amr": ["pwd", "otp"], "country": "DE"}
}
Field Description
locked true when at least one sign-in path is locked.
locked_until When the last open lockout ends; null without a lockout.
locks Open lockouts, oldest first, at most one per path (password, totp, sms_2fa, email_2fa, otp, recovery_code).
failures Failed attempts since the last success or the last lockout, per path, while the counting window runs. After window_ends_at the count starts over. The password window is the environment's brute_force.window_minutes.
last_sign_in The most recent successful authentication among the sessions still kept: authenticated_at (a re-authentication in the same session moves it forward), the authentication methods (amr, RFC 8176) and the session's last known country (null when unknown). null when no session is kept: the user never signed in, or the last session ended more than 30 days ago.

Errors

Every error is an application/problem+json body with a stable code.

HTTP code When
404 not_found The user does not exist in this environment (or the id is malformed), or org_id is given and the user is not a member of that organization.
401 unauthenticated The API key is missing, invalid or revoked.
403 insufficient_permissions The key lacks the users:read permission, or org_id is given and the key lacks organizations:read.
429 rate_limited The rate limit was exceeded; honor Retry-After.
503 service_unavailable A dependency is temporarily unavailable; retry later.

Example

curl https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p/sign-in-state \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"

CLI: keenauth users sign-in-state usr_0a1b2c3d4e5f6g7h8j9k0m1n2p