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.