Skip to content

Error Groups API ​

Error groups are deduplicated exception records created by the Error Events API. Each group tracks how often and how recently a particular error occurs, how often per day, and how often in each release. Groups have a status lifecycle — Open, Resolved, or Ignored — that developers use to triage their error queue. A group can also be promoted to a Bug issue in one action, pre-filled from the error's details and bidirectionally linked.

Requires the Error Tracking feature

These endpoints are only available when the Error Tracking feature is enabled for your workspace. If it is disabled, requests return 403 with "feature_restricted": true.

Permissions ​

ActionRequired PermissionScope
List and readError Groups: ReadWithin accessible projects
ResolveError Groups: UpdateWithin accessible projects
IgnoreError Groups: UpdateWithin accessible projects
ReopenError Groups: UpdateWithin accessible projects
Change the status of many groupsError Groups: UpdateChecked per project, within accessible projects
Promote to issueError Groups: UpdateWithin accessible projects

Admins and project owners bypass all permission checks for project-scoped resources.

Endpoints ​

MethodEndpointDescription
GET/api/error-groupsList error groups across every project you can access
GET/api/projects/{projectId}/error-groupsList a project's error groups
GET/api/projects/{projectId}/error-groups/{errorGroupId}Get one error group with its history
POST/api/projects/{projectId}/error-groups/{errorGroupId}/resolveMark a group as Resolved
POST/api/projects/{projectId}/error-groups/{errorGroupId}/ignoreMark a group as Ignored
POST/api/projects/{projectId}/error-groups/{errorGroupId}/reopenReopen a group to Open
POST/api/error-groups/bulk-statusSet up to 500 groups, from any of your projects, to one status
POST/api/projects/{projectId}/error-groups/{errorGroupId}/promoteCreate a Bug issue from the error group

Error Group Resource ​

The detail endpoint and all four POST endpoints return the error group resource.

FieldTypeDescription
idintegerUnique identifier
project_idintegerThe project this group belongs to
environmentstringDeployment environment (e.g. production)
releasestring | nullThe release of the group's first event, as the client sent it; null when that event had none
last_releasestring | nullThe release of the group's latest event; null when that event had none. A group that has not reported since this field was added reads null until its next event
exception_classstringFully-qualified exception class
latest_messagestringMost recent exception message
latest_stack_tracestringMost recent stack trace
previous_exceptionsobject[]The latest event's previous exceptions, outermost first (see The latest event); an empty list when it sent none
exception_codestring | nullThe latest event's exception code
runtimeobjectThe latest event's runtime, as {name, version}; each field is a string or null
frameworkobjectThe latest event's framework, as {name, version}; each field is a string or null
memory_peak_bytesinteger | nullThe latest event's peak memory use, in bytes
memory_limit_bytesinteger | nullThe latest event's memory limit, in bytes; null when unlimited
context_kindstring | nullWhere the latest event ran: route, job or command; null when it sent no context
context_labelstring | nullA short label for the latest event's context (see Context label); null when it sent no context
contextobject | nullThe latest event's context: its kind and every field of that kind, each null when not sent (see Error Events); null when it sent no context
occurrence_countintegerTotal number of times this error has been reported
daily_countsinteger[]Occurrences per UTC day over the last 30 days: 30 entries, oldest day first and today last. A day without occurrences counts 0
releasesobject[]Occurrences per release, in the order the releases first appeared (see below)
affected_usersinteger | nullHow many distinct users hit the group within the retention window (default 90 days), counted from their keyed user hashes (see Affected users and hosts); null when it has none
hostsobject[]The 10 hosts with the most occurrences, most first (see below); an empty list when no event named a host
statusinteger0 = Open, 1 = Resolved, 2 = Ignored
resolved_atstring | nullISO 8601 timestamp when the group was last resolved. A regression keeps it; Ignore and Reopen clear it
resolved_by_idinteger | nullID of the user who last resolved the group. A regression keeps it; Ignore and Reopen clear it
regressed_atstring | nullISO 8601 timestamp of the event that reopened the Resolved group (see Regression); null when the group is not regressed. Resolve, Ignore and Reopen clear it
reopen_countintegerHow often an event reopened the group after it was resolved. It never goes down
linked_issue_idinteger | nullID of the Bug issue created from this group via promote; null until promoted
first_seen_atstringISO 8601 timestamp of the first occurrence
last_seen_atstringISO 8601 timestamp of the most recent occurrence
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp

Each releases entry:

FieldTypeDescription
releasestringThe release, as the client sent it
occurrence_countintegerOccurrences in this release
first_seen_atstringISO 8601 timestamp of the first occurrence in this release
last_seen_atstringISO 8601 timestamp of the latest occurrence in this release

Each hosts entry:

FieldTypeDescription
namestringThe host, as the client sent it
countintegerOccurrences on this host

The latest event ​

previous_exceptions, exception_code, runtime, framework, memory_peak_bytes, memory_limit_bytes, context_kind, context_label and context come from the group's latest event, like latest_message and latest_stack_trace. Every event replaces all nine. A field the latest event did not send reads null, with two exceptions: previous_exceptions reads an empty list, and runtime and framework are always objects whose name and version read null when not sent. An event whose route pattern was a URL leaves the three context fields null (see Error Events). kendo-error-tracker v0.1 sends none of them; v0.2.0 sends each one it can read. See Error Events for what a client sends.

Each previous_exceptions entry:

FieldTypeDescription
exception_classstringFully-qualified exception class
messagestringThe exception message
stack_tracestringThe stack trace, as the client sent it; may be empty

Context label ​

kendo builds context_label when the event arrives:

  • route: the method and the pattern, e.g. GET /orders/{order}.
  • job: the name after its last \, so App\Jobs\SendDigestJob reads SendDigestJob. A name without \ is used whole.
  • command: the name, e.g. reports:send.

A field the context left out is left out of the label, so a context with only its kind has an empty label.

Counting per day and per release ​

Every ingested event adds one to its group's count for that UTC day and, when the event carries a release, to its count for that release. An event without a release counts on its day only.

  • Day counts are kept for 90 days, the error-group retention window. Older days count 0.
  • Release counts are kept as long as the group exists.
  • Counting started when these fields were added. A group that is older keeps its occurrence_count, but its days before that count 0 and its earlier events are in no release. So daily_counts and releases can add up to less than occurrence_count.

Affected users and hosts ​

An event may carry a host and a user_hash (see Error Events). The hash takes the place of the user id, and the API never returns a hash: only the count.

  • affected_users counts the distinct user hashes the group received within the error-group retention window (default 90 days). Each hash keeps the time it was last seen, and one not seen within the window no longer counts, even while the group keeps firing. A group that never received a hash, or whose hashes have all expired, reads null, not 0: kendo-error-tracker v0.1 sends no hash, v0.2.0 sends one only for a request with a signed-in user, and an error outside a request has no user.
  • hosts lists the hosts with the most occurrences, at most 10, most first and then by name. A group can have more hosts than it returns. A host not seen within the retention window is deleted, so a machine an autoscaler retired drops off. A host's count covers its events since its row was created: a host that comes back after its row was deleted starts again from 1.
  • Counting started when these fields were added: an older group reads null and no hosts until an event carries them.

Status Lifecycle ​

        ┌─────────────────────────────────┐
        │                                 ▼
     Open (0) ──resolve──► Resolved (1)  ◄── reopen
        ▲                       │
        │                    ignore
        │                       │
     reopen              Ignored (2)
        └──────────────────────►
  • Open — the default state for every new error group, and the state of a regressed group
  • Resolved — a developer marked the error as fixed; resolved_at and resolved_by_id are set
  • Ignored — the error is acknowledged but not actionable; resolved_at and resolved_by_id are cleared

All three transition endpoints are idempotent — calling resolve on an already-Resolved group returns 200 with unchanged state. One exception: reopen on a regressed group that is already Open clears its regressed mark.

Regression ​

A regressed group is an Open group with regressed_at set: it was resolved, and the error came back. Regressed is not a separate status value.

  • Resolve remembers the group's last_release at that moment.
  • An event from that same release keeps the group Resolved. It is counted as usual.
  • An event from another release, or with no release, reopens the group: status becomes 0 (Open), regressed_at is set to the time of the event, and reopen_count goes up by one. resolved_at and resolved_by_id keep who resolved the group and when.
  • A group resolved while its latest event had no release regresses on any event.
  • Later events on a regressed group count as usual. They do not change regressed_at or reopen_count.
  • Ignored groups are not affected by new events.
  • Resolve, Ignore and Reopen clear regressed_at. reopen_count never goes down.

Two events that arrive at the same moment regress a group once.

List Error Groups ​

GET /api/projects/{projectId}/error-groups

GET /api/error-groups

The project endpoint returns that project's groups. The global endpoint returns the groups of every project you can access, and takes three optional query parameters: project_id, environment and limit (1–500). Both return at most 500 groups, the most recently seen first, in a {data, meta} envelope; meta.truncated is true when more groups exist.

Each list item carries the resource fields above, regressed_at and reopen_count included, except:

  • latest_stack_trace, releases, hosts and the latest event's previous_exceptions, exception_code, runtime, framework, memory_peak_bytes, memory_limit_bytes and context are left out. Read them from the detail endpoint. context_kind, context_label and affected_users are included.
  • latest_message is cut to 500 characters and ends in … when it was longer.
  • daily_counts covers the last 14 days: 14 entries, oldest day first and today last.

The example shows a regressed group: it was resolved at v1.4.1 and came back in v1.4.2.

bash
curl https://{tenant}.kendo.dev/api/projects/1/error-groups \
  -H "Authorization: Bearer your-token"
json
{
  "data": [
    {
      "id": 42,
      "project_id": 1,
      "environment": "production",
      "release": "v1.4.1",
      "last_release": "v1.4.2",
      "exception_class": "RuntimeException",
      "latest_message": "Undefined array key \"user_id\"",
      "context_kind": "route",
      "context_label": "GET /orders/{order}",
      "occurrence_count": 7,
      "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
      "affected_users": 3,
      "status": 0,
      "resolved_at": "2026-06-18T10:00:00+00:00",
      "resolved_by_id": 3,
      "regressed_at": "2026-06-19T08:10:00+00:00",
      "reopen_count": 1,
      "linked_issue_id": null,
      "first_seen_at": "2026-06-15T09:00:00+00:00",
      "last_seen_at": "2026-06-19T14:00:00+00:00",
      "created_at": "2026-06-15T09:00:00+00:00",
      "updated_at": "2026-06-19T14:00:00+00:00"
    }
  ],
  "meta": {"truncated": false, "count": 1, "limit": 500}
}

Get an Error Group ​

GET /api/projects/{projectId}/error-groups/{errorGroupId}

Returns the full error group resource, with its stack trace, its last 30 daily_counts, its releases, its affected_users and its hosts.

Resolve an Error Group ​

POST /api/projects/{projectId}/error-groups/{errorGroupId}/resolve

Transitions the group to Resolved. Sets resolved_at to the current time and resolved_by_id to the authenticated user's ID, remembers the group's last_release for regression detection, and clears regressed_at. No-ops if the group is already Resolved.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-groups/42/resolve \
  -H "Authorization: Bearer your-token"
json
{
  "id": 42,
  "project_id": 1,
  "environment": "production",
  "release": "v1.4.1",
  "last_release": "v1.4.2",
  "exception_class": "RuntimeException",
  "latest_message": "Undefined array key \"user_id\"",
  "latest_stack_trace": "#0 app/Http/Controllers/OrderController.php(42): handle()",
  "previous_exceptions": [],
  "exception_code": "0",
  "runtime": {"name": "php", "version": "8.5.1"},
  "framework": {"name": "laravel", "version": "13.1.0"},
  "memory_peak_bytes": 44040192,
  "memory_limit_bytes": 268435456,
  "context_kind": "route",
  "context_label": "GET /orders/{order}",
  "context": {"kind": "route", "method": "GET", "pattern": "/orders/{order}", "name": "orders.show", "action": "OrderController@show", "response_status": 500},
  "occurrence_count": 7,
  "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
  "releases": [
    {"release": "v1.4.1", "occurrence_count": 3, "first_seen_at": "2026-06-15T09:00:00+00:00", "last_seen_at": "2026-06-17T16:20:00+00:00"},
    {"release": "v1.4.2", "occurrence_count": 4, "first_seen_at": "2026-06-19T08:10:00+00:00", "last_seen_at": "2026-06-19T14:00:00+00:00"}
  ],
  "affected_users": 3,
  "hosts": [
    {"name": "web-1", "count": 5},
    {"name": "web-2", "count": 2}
  ],
  "status": 1,
  "resolved_at": "2026-06-19T14:30:00+00:00",
  "resolved_by_id": 3,
  "regressed_at": null,
  "reopen_count": 1,
  "linked_issue_id": null,
  "first_seen_at": "2026-06-15T09:00:00+00:00",
  "last_seen_at": "2026-06-19T14:00:00+00:00",
  "created_at": "2026-06-15T09:00:00+00:00",
  "updated_at": "2026-06-19T14:30:00+00:00"
}

Ignore an Error Group ​

POST /api/projects/{projectId}/error-groups/{errorGroupId}/ignore

Transitions the group to Ignored. Clears resolved_at, resolved_by_id and regressed_at. No-ops if the group is already Ignored.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-groups/42/ignore \
  -H "Authorization: Bearer your-token"
json
{
  "id": 42,
  "project_id": 1,
  "environment": "production",
  "release": "v1.4.1",
  "last_release": "v1.4.2",
  "exception_class": "RuntimeException",
  "latest_message": "Undefined array key \"user_id\"",
  "latest_stack_trace": "#0 app/Http/Controllers/OrderController.php(42): handle()",
  "previous_exceptions": [],
  "exception_code": "0",
  "runtime": {"name": "php", "version": "8.5.1"},
  "framework": {"name": "laravel", "version": "13.1.0"},
  "memory_peak_bytes": 44040192,
  "memory_limit_bytes": 268435456,
  "context_kind": "route",
  "context_label": "GET /orders/{order}",
  "context": {"kind": "route", "method": "GET", "pattern": "/orders/{order}", "name": "orders.show", "action": "OrderController@show", "response_status": 500},
  "occurrence_count": 7,
  "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
  "releases": [
    {"release": "v1.4.1", "occurrence_count": 3, "first_seen_at": "2026-06-15T09:00:00+00:00", "last_seen_at": "2026-06-17T16:20:00+00:00"},
    {"release": "v1.4.2", "occurrence_count": 4, "first_seen_at": "2026-06-19T08:10:00+00:00", "last_seen_at": "2026-06-19T14:00:00+00:00"}
  ],
  "affected_users": 3,
  "hosts": [
    {"name": "web-1", "count": 5},
    {"name": "web-2", "count": 2}
  ],
  "status": 2,
  "resolved_at": null,
  "resolved_by_id": null,
  "regressed_at": null,
  "reopen_count": 1,
  "linked_issue_id": null,
  "first_seen_at": "2026-06-15T09:00:00+00:00",
  "last_seen_at": "2026-06-19T14:00:00+00:00",
  "created_at": "2026-06-15T09:00:00+00:00",
  "updated_at": "2026-06-19T14:35:00+00:00"
}

Reopen an Error Group ​

POST /api/projects/{projectId}/error-groups/{errorGroupId}/reopen

Transitions the group back to Open. Clears resolved_at, resolved_by_id and regressed_at. On a regressed group that is already Open it clears the same three fields. No-ops on any other Open group.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-groups/42/reopen \
  -H "Authorization: Bearer your-token"
json
{
  "id": 42,
  "project_id": 1,
  "environment": "production",
  "release": "v1.4.1",
  "last_release": "v1.4.2",
  "exception_class": "RuntimeException",
  "latest_message": "Undefined array key \"user_id\"",
  "latest_stack_trace": "#0 app/Http/Controllers/OrderController.php(42): handle()",
  "previous_exceptions": [],
  "exception_code": "0",
  "runtime": {"name": "php", "version": "8.5.1"},
  "framework": {"name": "laravel", "version": "13.1.0"},
  "memory_peak_bytes": 44040192,
  "memory_limit_bytes": 268435456,
  "context_kind": "route",
  "context_label": "GET /orders/{order}",
  "context": {"kind": "route", "method": "GET", "pattern": "/orders/{order}", "name": "orders.show", "action": "OrderController@show", "response_status": 500},
  "occurrence_count": 7,
  "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
  "releases": [
    {"release": "v1.4.1", "occurrence_count": 3, "first_seen_at": "2026-06-15T09:00:00+00:00", "last_seen_at": "2026-06-17T16:20:00+00:00"},
    {"release": "v1.4.2", "occurrence_count": 4, "first_seen_at": "2026-06-19T08:10:00+00:00", "last_seen_at": "2026-06-19T14:00:00+00:00"}
  ],
  "affected_users": 3,
  "hosts": [
    {"name": "web-1", "count": 5},
    {"name": "web-2", "count": 2}
  ],
  "status": 0,
  "resolved_at": null,
  "resolved_by_id": null,
  "regressed_at": null,
  "reopen_count": 1,
  "linked_issue_id": null,
  "first_seen_at": "2026-06-15T09:00:00+00:00",
  "last_seen_at": "2026-06-19T14:00:00+00:00",
  "created_at": "2026-06-15T09:00:00+00:00",
  "updated_at": "2026-06-19T14:40:00+00:00"
}

Change the Status of Many Error Groups ​

POST /api/error-groups/bulk-status

Sets up to 500 error groups to one status in one request. The groups may come from several projects. Each group changes exactly as the resolve, ignore or reopen endpoint would change it: Resolve records who resolved the group, when, and at which release; Resolve, Ignore and Reopen clear regressed_at. A group that is already in the requested status (and is not regressed) is left as it is.

Request body ​

FieldTypeRequiredDescription
error_group_idsinteger[]Yes1 to 500 group IDs, each at most once
statusintegerYesThe status to set: 0 = Open (reopen), 1 = Resolved, 2 = Ignored

Which groups change ​

A group changes when you can access its project and hold Error Groups: Update on that project, the permission the single endpoints need. Admins and the project owner pass. Every other ID is refused: an ID that names no group, a group in a project you cannot access, and a group in a project where you lack the permission. The answer does not say which of the three applies, so a refusal never tells you whether a group exists in another project.

A partly refused request still changes the groups you may change. All changes are written in one transaction: if the write fails, no group changes and the request answers 500.

Response ​

200 with two fields:

  • data: every group you may change, after the change, as list items: the same fields as List Error Groups, with the last 14 daily_counts and affected_users, and without latest_stack_trace, releases, hosts and the latest event's fields other than context_kind and context_label. A group that was already in the requested status is listed too. The order is not fixed.
  • refused_ids: the refused IDs, in the order you sent them.
bash
curl -X POST https://{tenant}.kendo.dev/api/error-groups/bulk-status \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{"error_group_ids": [42, 57, 99], "status": 1}'
json
{
  "data": [
    {
      "id": 42,
      "project_id": 1,
      "environment": "production",
      "release": "v1.4.1",
      "last_release": "v1.4.2",
      "exception_class": "RuntimeException",
      "latest_message": "Undefined array key \"user_id\"",
      "context_kind": "route",
      "context_label": "GET /orders/{order}",
      "occurrence_count": 7,
      "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
      "affected_users": 3,
      "status": 1,
      "resolved_at": "2026-06-19T15:10:00+00:00",
      "resolved_by_id": 3,
      "regressed_at": null,
      "reopen_count": 1,
      "linked_issue_id": null,
      "first_seen_at": "2026-06-15T09:00:00+00:00",
      "last_seen_at": "2026-06-19T14:00:00+00:00",
      "created_at": "2026-06-15T09:00:00+00:00",
      "updated_at": "2026-06-19T15:10:00+00:00"
    }
  ],
  "refused_ids": [57, 99]
}
json
{
  "message": "The error groups field must not have more than 500 items.",
  "errors": {
    "error_group_ids": ["The error groups field must not have more than 500 items."]
  }
}

Create Issue from Error Group ​

POST /api/projects/{projectId}/error-groups/{errorGroupId}/promote

Creates a new Bug issue pre-filled from the error group and links the two bidirectionally. The issue title is set to {ExceptionClass}: {latest message} (truncated to 255 characters); the description includes the stack trace, occurrence count, environment, first/last seen timestamps, and a back-link to the error group.

Important: Promoting an error group does not change its status — the error stays Open after a Bug is filed (it is not fixed yet). linked_issue_id and status are independent facts.

Returns 422 if the error group already has a linked_issue_id set — to surface an existing linked issue, read linked_issue_id from the resource before calling this endpoint.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-groups/42/promote \
  -H "Authorization: Bearer your-token"
json
{
  "id": 42,
  "project_id": 1,
  "environment": "production",
  "release": "v1.4.1",
  "last_release": "v1.4.2",
  "exception_class": "RuntimeException",
  "latest_message": "Undefined array key \"user_id\"",
  "latest_stack_trace": "#0 app/Http/Controllers/OrderController.php(42): handle()",
  "previous_exceptions": [],
  "exception_code": "0",
  "runtime": {"name": "php", "version": "8.5.1"},
  "framework": {"name": "laravel", "version": "13.1.0"},
  "memory_peak_bytes": 44040192,
  "memory_limit_bytes": 268435456,
  "context_kind": "route",
  "context_label": "GET /orders/{order}",
  "context": {"kind": "route", "method": "GET", "pattern": "/orders/{order}", "name": "orders.show", "action": "OrderController@show", "response_status": 500},
  "occurrence_count": 7,
  "daily_counts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4],
  "releases": [
    {"release": "v1.4.1", "occurrence_count": 3, "first_seen_at": "2026-06-15T09:00:00+00:00", "last_seen_at": "2026-06-17T16:20:00+00:00"},
    {"release": "v1.4.2", "occurrence_count": 4, "first_seen_at": "2026-06-19T08:10:00+00:00", "last_seen_at": "2026-06-19T14:00:00+00:00"}
  ],
  "affected_users": 3,
  "hosts": [
    {"name": "web-1", "count": 5},
    {"name": "web-2", "count": 2}
  ],
  "status": 0,
  "resolved_at": null,
  "resolved_by_id": null,
  "regressed_at": null,
  "reopen_count": 1,
  "linked_issue_id": 301,
  "first_seen_at": "2026-06-15T09:00:00+00:00",
  "last_seen_at": "2026-06-19T14:00:00+00:00",
  "created_at": "2026-06-15T09:00:00+00:00",
  "updated_at": "2026-06-19T15:00:00+00:00"
}
json
{
  "message": "This error group is already linked to an issue."
}

Common Errors ​

StatusCause
401Token is missing, invalid, revoked, or expired
403Missing the Error Groups permission the endpoint needs (Read to list or get, Update for the rest), or the Error Tracking feature is disabled
404Error group not found, or does not belong to the specified project

See Also ​