Skip to content

Error Events API ​

Error events are exceptions reported by your applications and external systems. kendo fingerprints each event, deduplicates identical errors into a single error group, and tracks how often and how recently each group occurs. External systems submit events using a Read & write project token.

Laravel apps: use the client library

For Laravel applications, the canonical integration is the kendo-error-tracker Composer package. It scrubs PII before sending, normalizes file paths, and reports asynchronously without blocking the request. The raw POST documented below is the language-agnostic fallback for non-Laravel systems. See the Error Tracking guide.

Requires the Error Tracking feature

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

Permissions ​

Error ingestion takes a token with the write scope: a Read & write project token, the usual choice for an external system, or a personal access token.

AuthenticationRequiredScope
Project tokenRead & writeThe single project the token is linked to
Personal access tokenThe write scopeProjects the token's user can access

The token's user (for a project token, its creator) must also be able to create error groups in the project (the Error Groups: Create permission, or admin / project-owner bypass).

Endpoints ​

MethodEndpointDescription
POST/api/projects/{projectId}/error-eventsIngest an error event

Ingest an Error Event ​

POST /api/projects/{projectId}/error-events

Submits a single exception. kendo computes a fingerprint from the exception class and a normalized stack trace, then either creates a new error group or increments the matching one. The endpoint returns 202 Accepted with an empty body — clients should not depend on a response payload.

Authenticate with a Read & write project token. A Read token gets 403.

Request Fields ​

FieldTypeRequiredDescription
environmentstringYesDeployment environment, max 255 characters (e.g. production, staging). Part of the grouping key — the same error in different environments forms separate groups
exception_classstringYesFully-qualified exception class, max 255 characters (e.g. RuntimeException)
messagestringYesHuman-readable exception message. Any length is accepted, and kendo stores the first 65,535 bytes, cut on a character boundary. May be an empty string, but the field must be present
stack_tracestringYesRaw stack trace text, max 131,072 characters. Used to compute the fingerprint. May be an empty string, but the field must be present
releasestringNoVersion or deploy identifier (e.g. a git SHA or tag). Not part of the fingerprint, so deploys of the same code deduplicate together. The group keeps its first and its latest release and counts its events per release (see Error Groups)
previous_exceptionsobject[]NoThe exceptions that caused this one, outermost first: the thrown exception's previous exception, then that one's, down to the root cause. At most 10 entries (see Previous exceptions). Not part of the fingerprint
exception_codestringNoThe exception's code as a string, max 255 characters (e.g. "0", or "42S02" for a database error)
runtimeobjectNoThe language runtime the app ran on, as {name, version} (e.g. {"name": "php", "version": "8.5.1"}). See Runtime and framework
frameworkobjectNoThe framework the app ran on, as {name, version} (e.g. {"name": "laravel", "version": "13.1.0"}). See Runtime and framework
memory_peak_bytesintegerNoPeak memory use of the process when the exception was reported, in bytes, at least 0
memory_limit_bytesintegerNoThe process's memory limit in bytes, at least 0. Send null or leave it out when the limit is unlimited (memory_limit = -1)
contextobjectNoWhere the exception ran: a route, a job or a command (see Context). Not part of the fingerprint
hoststringNoThe name of the machine that ran the code, max 255 characters (e.g. a hostname or a container id). kendo counts the group's events per host. Not part of the fingerprint
user_hashstringNoA keyed hash of the signed-in user's id: HMAC-SHA256 as 64 lowercase hex characters (see Affected users). Never the user id itself. Not part of the fingerprint

kendo-error-tracker v0.1 sends only the first five fields. v0.2.0 also sends each of the nine after them that it can read, and user_hash only for a request with a signed-in user. The nine are optional, so an event without them is accepted as before.

Previous exceptions ​

Each previous_exceptions entry:

FieldTypeRequiredDescription
exception_classstringYesFully-qualified exception class, max 255 characters
messagestringYesThe exception message, max 65,535 characters. May be an empty string, but the field must be present
stack_tracestringYesRaw stack trace text, max 131,072 characters. May be an empty string, but the field must be present

previous_exceptions must be a JSON array. An 11th entry, or an entry over these limits, gets 422 like an oversized stack_trace. Any other field in an entry is ignored.

Runtime and framework ​

runtime and framework each take an object with two optional fields. Either object may be left out, and so may either field in it.

FieldTypeRequiredDescription
namestringNoThe runtime or framework, max 255 characters (e.g. php, node, laravel)
versionstringNoIts version, max 255 characters

kendo stores the values as sent and reads no meaning into the names. Any other field in either object is ignored.

Context ​

context says where the exception ran. It takes a kind and the fields of that kind. Every field except kind is optional.

FieldTypeKindDescription
kindstringallroute, job or command. Required in a context; any other value gets 422
namestringallThe name the route, job or command is registered under, max 255 characters (e.g. orders.show, App\Jobs\SendDigestJob, reports:send). A job sends its class here, or its name where it has no class
methodstringrouteThe HTTP method, max 255 characters (e.g. GET)
patternstringrouteThe route pattern as declared, max 255 characters (e.g. /orders/{order}). Never the request URL: see below
actionstringrouteWhat handled the request, max 255 characters (e.g. OrderController@show)
response_statusintegerrouteThe HTTP status of the response, from 100 to 599
queuestringjobThe queue the job ran on, max 255 characters
attemptintegerjobWhich attempt failed, counting the first as 1
classstringcommandThe class that runs the command, max 255 characters

A field of another kind is ignored, so a job context stores no method. Any other field is ignored too.

A route keeps its pattern, never its URL. When pattern carries a scheme (https://), a host (//example.com, example.com:8000), a query string (?page=2) or a fragment (#top), kendo stores the event without its context and still answers 202. A ? inside a placeholder (/users/{user?}) or at the very end (/users/:id?) marks an optional parameter and is kept.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-events \
  -H "Authorization: Bearer your-project-token" \
  -H "Content-Type: application/json" \
  -d '{
    "environment": "production",
    "release": "v1.4.2",
    "exception_class": "Illuminate\\Database\\QueryException",
    "message": "SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, SQL: select * from `orders` where `id` = 42)",
    "stack_trace": "#0 /var/www/app/Http/Controllers/OrderController.php(42): Illuminate\\Database\\Eloquent\\Builder->firstOrFail()\n#1 {main}",
    "previous_exceptions": [
      {
        "exception_class": "PDOException",
        "message": "SQLSTATE[HY000] [2002] Connection refused",
        "stack_trace": "#0 /var/www/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php(70): PDO->__construct()\n#1 {main}"
      }
    ],
    "exception_code": "2002",
    "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",
      "method": "GET",
      "pattern": "/orders/{order}",
      "name": "orders.show",
      "action": "OrderController@show",
      "response_status": 500
    },
    "host": "web-1",
    "user_hash": "5f0e8a3c9b1d7e2f4a6c8b0d1e3f5a7c9b2d4e6f8a0c1e3f5b7d9a2c4e6f8b0d"
  }'
text
202 Accepted
(empty body)

Affected users ​

kendo counts how many distinct users an error hit without receiving their user ids. The client sends user_hash: the HMAC-SHA256 of the user's id, keyed with a secret only the app holds (kendo-error-tracker uses the app's APP_KEY), written as 64 lowercase hex characters. Leave it out when the exception ran outside a request or no user was signed in.

  • kendo stores the hash as sent. It cannot turn a hash back into a user id, and the API returns only the number of distinct hashes, never a hash.
  • Anything that is not 64 lowercase hex characters is dropped. A raw user id, an email address or an uppercase hash is not stored, and kendo still stores the event and answers 202. The error matters more than its user count.
  • The same user sends the same hash on every event, so the Error Groups API counts each user once.
  • The hash covers this one field. message, stack_trace and previous_exceptions are stored as sent, so a user id or an email address inside them is stored too: scrub them before sending. The kendo-error-tracker client does this; a raw POST must do it itself.

How Deduplication Works ​

kendo groups errors so that the same fault reported many times appears once, with an occurrence count:

  • Fingerprint — derived from the exception_class and a normalized stack_trace of the thrown exception; previous_exceptions, context, host and user_hash play no part. Host-specific path prefixes are stripped before hashing, so the same exception reported from different servers deduplicates into one group: /var/www/ and /home/<user>/ (any user) are removed, and any …/vendor/ path is collapsed to vendor/. For example, both /home/forge/app/Foo.php and /var/www/app/Foo.php normalize to app/Foo.php.
  • Fallback — if the stack trace is empty or cannot be parsed, the fingerprint falls back to the exception class plus the first non-empty line of the normalized stack trace. An event is never rejected for an unparseable trace.
  • New group — the first event with a given fingerprint creates a group with occurrence_count: 1 and first_seen_at == last_seen_at.
  • Repeat — a matching fingerprint increments occurrence_count and bumps last_seen_at. first_seen_at never changes.
  • Latest event — every event, new group or repeat, replaces the group's message, stack trace, previous exceptions, code, runtime, framework, memory and context with its own. A field the event leaves out is cleared to null (an empty list for previous_exceptions), so a group always describes its latest event.
  • Regression — a repeat on a Resolved group reopens it and marks it regressed, unless the event comes from the release the group was resolved at (see Error Groups).

Only the fields above are stored. Any additional fields in the request body are ignored — kendo deliberately does not persist request bodies, user data, or free-form context with error groups. context keeps only the fields listed for its kind, and a route only its pattern. A user_hash is kept only when it is well-formed.

Retention ​

Error groups are operational data and are pruned automatically. A group is deleted once its last_seen_at is older than the retention window (default 90 days). Active errors that keep occurring are never pruned.

A group's user hashes and hosts expire on their own: each keeps the time it was last seen, and a daily job deletes it once that is older than the same window. So a user who has not hit the error within the window no longer counts, even while the group keeps firing.

Common Errors ​

StatusCause
401Token is missing, invalid, revoked, or expired
403Token lacks the write scope, a project token is not linked to (or is inactive for) the target project, or the Error Tracking feature is disabled ("feature_restricted": true)
422Request validation failed
429Rate limit exceeded — ingestion is limited to 1,000 requests per minute per token

See Also ​