List events

GET /v1/events · permission events:read

Returns the environment's events in order, oldest first. Each event is the same document, with the same id, that webhooks deliver. Events are kept for 30 days on every plan.

Use this endpoint when your system cannot receive webhooks, or to fill a gap after an outage: keep the last next_cursor you processed and continue from it.

The environment always comes from the API key; there is no environment parameter.

Request

Parameter In Required Description
after query no next_cursor from the previous response, or latest to receive only events recorded from now on. Without it the list starts at the oldest retained event.
types query no Comma-separated event types, at most 20, such as user.created,user.deleted. Without it every type is returned.
org_id query no Only events of this organization (org_…).
limit query no Page size, 1 to 100. Default 20.
payload query no thin (default) or full. full also requires the users:read permission. Until the full payload is available, full returns the thin payload.
purpose query no tail (default) or webhooks_listen. webhooks_listen is accepted only in development and staging environments.

Response

200 OK:

Field Description
data Events in order. Every event has a seq that increases within the environment.
next_cursor Always present. Pass it as after in the next request, even when data is empty.
has_more true when more events follow; request again right away. When false, wait before polling again (5 to 60 seconds).

The cursor does not depend on types or org_id: you can change the filter and continue from the same position. A cursor works only in the environment that issued it.

{
  "data": [
    {
      "id": "evt_01j9xk3m7q8r2s4t6v8w0y2z4a",
      "type": "connection.deleted",
      "schema_version": 1,
      "environment_id": "env_01j9xk3m7q8r2s4t6v8w0y2z4e",
      "occurred_at": "2026-11-03T08:15:42.120Z",
      "recorded_at": "2026-11-03T08:15:42.140Z",
      "seq": 4182,
      "data": {
        "object": {"id": "conn_01j9xk3m7q8r2s4t6v8w0y2z4f", "status": "deleted"},
        "object_version": 2
      },
      "context": {"actor": {"type": "system"}, "request_id": "0192a6c1-7d3e-7a10-9b2c-3d4e5f607182"}
    }
  ],
  "next_cursor": "eyJ2IjoxfQ.c2lnbmF0dXJl",
  "has_more": false
}

Example: poll for new events

cursor=latest
while true; do
  page=$(curl -sS -G https://api.keenauth.com/v1/events \
    -H "Authorization: Bearer $KEENAUTH_API_KEY" \
    --data-urlencode "after=$cursor" --data-urlencode "limit=100")
  echo "$page" | jq -c '.data[]'
  cursor=$(echo "$page" | jq -r '.next_cursor')
  [ "$(echo "$page" | jq -r '.has_more')" = "true" ] || sleep 10
done

The CLI does the same with keenauth events tail.

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 issued in another environment.
401 unauthenticated The API key is missing, invalid or revoked.
403 insufficient_permissions The key lacks events:read, or payload=full without users:read.
403 test_feature_not_in_production purpose=webhooks_listen in a production environment.
422 validation_failed A parameter is invalid or repeated; errors[].pointer names it.
429 rate_limited The rate limit was exceeded; honor Retry-After.
503 service_unavailable A dependency is temporarily unavailable; retry later.

Personal data

This endpoint carries personal data: events. Personal data in the KeenAuth APIs 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.