
# List a user's logs

`GET /v1/users/{user_id}/logs` · permission `logs:read`

Lists the log records of one user with cursor pagination, newest first. This is the data behind the dashboard's user activity view. It follows the same rules as [List logs](/docs/api/management/logs/list) but accepts only the `from`, `to`, `type` and `org_id` filters; other filters are ignored.

## Organization scope

Pass `org_id` to build a member diagnostics view for an organization admin. The key then also needs the `organizations:read` permission. 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, only records that happened in the context of that organization are returned: records whose `org_id` is that organization. The user's sign-ins outside the organization, or in the context of another organization, are never returned. Without `org_id` the view covers every record of the user in the environment.

## Request

| Parameter | In | Required | Description |
|---|---|---|---|
| `user_id` | path | yes | User id, prefixed `usr_`. |
| `limit` | query | no | Page size, 1 to 100. Default 20. |
| `after` | query | no | Opaque cursor from `list_metadata.after` (older records). Exclusive with `before`. |
| `before` | query | no | Opaque cursor from `list_metadata.before` (newer records). Exclusive with `after`. |
| `from` | query | no | RFC 3339 time; records at or after it. Must be inside the retention window. |
| `to` | query | no | RFC 3339 time; records before it. Must be later than `from`. |
| `type` | query | no | Log type prefix such as `authentication.` or `session.created`. Repeat the parameter for up to 20 values; a record that matches any of them is returned. |
| `org_id` | query | no | Organization id, prefixed `org_`. Limits the view to records of that organization; see [Organization scope](#organization-scope). Needs `organizations:read`. |

Records are always ordered by `occurred_at`, newest first; there is no `order` parameter (`order=desc` is accepted, any other value fails with `validation_failed`). A cursor is only valid with the filters it was issued for.

## Response

`200 OK`. `data` holds the records on the page; `list_metadata.after` points to older records and `list_metadata.before` to newer ones (`null` when there are none). No total count is returned. Empty fields are `null`.

`ip`, `ip_asn`, `user_agent` and `user_agent_family` are present only when the key has the `audit-events:read_ip` permission, and `identifier_hint` only when it has `users:read_pii`. Without the permission the field is left out entirely (no masked value). `ip_country` is always returned.

```json
{
  "data": [
    {
      "id": "evt_01j9xk3m7q8r2s4t6v8w0y2z4a",
      "occurred_at": "2026-10-01T08:15:42.120Z",
      "type": "authentication.password.failed",
      "outcome": "failure",
      "reason_code": "invalid_credentials",
      "user_id": "usr_0a1b2c3d4e5f6g7h8j9k0m1n2p",
      "org_id": null,
      "application_id": "app_01j9xk3m7q8r2s4t6v8w0y2z4b",
      "connection_id": "conn_01j9xk3m7q8r2s4t6v8w0y2z4c",
      "directory_id": null,
      "session_id": null,
      "identifier_hint": "a***@e***.com",
      "ip": "203.0.113.7",
      "ip_country": "DE",
      "ip_asn": 64500,
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64)",
      "user_agent_family": "Firefox",
      "risk": null,
      "auth_context": null,
      "act_chain": null,
      "details": {"attempt": 3},
      "request_id": "0192a6c1-7d3e-7a10-9b2c-3d4e5f607182",
      "correlation_id": null,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
    }
  ],
  "list_metadata": {
    "before": null,
    "after": "<opaque cursor>"
  }
}
```

## Errors

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

| HTTP | `code` | When |
|---|---|---|
| 400 | `invalid_cursor` | The cursor is malformed or was not issued for this request. |
| 400 | `invalid_filter` | The log service rejected a filter or the cursor. |
| 400 | `range_outside_retention` | `from` is earlier than the retention window of your plan. |
| 422 | `validation_failed` | A query parameter is invalid (`limit` out of range, bad timestamp, `to` not after `from`, more than 20 `type` values, malformed `org_id`, repeated parameter). |
| 401 | `unauthenticated` | The API key is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The key lacks the `logs:read` permission, or `org_id` is set and the key lacks `organizations:read`. `required_permissions` lists what the request needs. |
| 404 | `not_found` | The user id is malformed, or `org_id` is set and the user is not a member of that organization. |
| 429 | `rate_limited` | The rate limit was exceeded; honor `Retry-After`. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry later. |

## Example

```bash
curl "https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p/logs?limit=50" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"
```

Only the records of one organization:

```bash
keenauth users logs list usr_0a1b2c3d4e5f6g7h8j9k0m1n2p --org-id org_01j9xk3m7q8r2s4t6v8w0y2z4d --type authentication.
```

## Personal data

This endpoint carries personal data: environment logs. [Personal data in the KeenAuth APIs](/docs/policies/personal-data) describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.
