Appearance
Error Tracking
kendo collects exceptions from your applications, fingerprints each one, and deduplicates identical errors into a single error group on your project — with an occurrence count and first/last-seen timestamps. This guide wires an application up to report into kendo end to end: mint a token, send errors, and triage them.
For Laravel applications, use the canonical client library, kendo-error-tracker. For any other language or framework, post raw HTTP to the Error Events API.
Requires the Error Tracking feature
Error ingestion is only available when the Error Tracking feature is enabled for your workspace. If it is disabled, requests return 403 with "feature_restricted": true.
Why a client library, not raw HTTP?
The package is more than convenience — it enforces a contract a hand-rolled POST can't:
- Source-side PII scrubbing. Messages and stack traces are scrubbed before they leave your server. JWTs,
Bearertokens, BSNs (Dutch citizen service numbers), and email addresses are each replaced with a[REDACTED:<kind>]marker. Scrubbing has to happen at the source and consistently — a rawPOSTships whatever the exception happened to carry. - Exact path normalization. Each stack frame's absolute path has your app's own
base_path()stripped, so the same exception thrown from/var/www/html/app/Foo.phpand/home/forge/app/Foo.phpboth arrive asapp/Foo.phpand fingerprint into one group. - Async, swallow-on-failure delivery. Reports dispatch to the queue with 0 retries; a failed send logs locally and is never requeued.
report()never throws and never blocks the request that hit the error — error tracking can't become the thing that slows down or breaks your app.
This is why allied projects should not POST raw HTTP when a client exists: the scrubbing contract would drift, and PII could leak server-to-server. The raw endpoint remains available for languages the client doesn't cover — see Raw HTTP below.
1. Mint a project token
Error ingestion authenticates with a project token carrying the error-events:write ability:
- Open the project and go to Settings (
/projects/{projectId}/settings). - Scroll to the API Tokens section.
- Click Create Token, name it (e.g.
Production error reporter), and set the Ability toError tracking. - Click Create and copy the token — it is shown only once.
The token is bound to that one project. Used against a different project's route, the server rejects it (422); the client swallows that like any other failure.
2. Install the client (Laravel)
bash
composer require script-development/kendo-error-trackerThe ErrorTrackerServiceProvider is auto-discovered through Laravel package discovery — no manual registration. Publish the config if you want to tune it:
bash
php artisan vendor:publish --tag=error-tracker-configConfigure
Set the environment variables (the config reads ERROR_TRACKER_*):
dotenv
ERROR_TRACKER_KENDO_URL=https://script.kendo.dev
ERROR_TRACKER_PROJECT=7
ERROR_TRACKER_TOKEN=your-project-token
ERROR_TRACKER_ENVIRONMENT=production
ERROR_TRACKER_RELEASE=v1.2.3| Env var | Config key | Description | Default |
|---|---|---|---|
ERROR_TRACKER_KENDO_URL | kendo_url | Base URL of your kendo tenant — https://{tenant}.kendo.dev | — |
ERROR_TRACKER_PROJECT | project | The kendo project id that owns the errors (the {project} route key — kendo binds it by id) | — |
ERROR_TRACKER_TOKEN | token | A project token with the error-events:write ability (sent as a Bearer token) | — |
ERROR_TRACKER_ENVIRONMENT | environment | Deploy environment label sent with each event | APP_ENV |
ERROR_TRACKER_RELEASE | release | Optional release identifier (git sha / version tag) | null |
ERROR_TRACKER_SYNC | sync | false queues the report; true POSTs inline | false |
ERROR_TRACKER_CONNECT_TIMEOUT | connect_timeout | Seconds to wait while connecting to the kendo host | 2 |
ERROR_TRACKER_TIMEOUT | timeout | Total seconds to wait for the POST | 5 |
ERROR_TRACKER_PROJECT is the numeric project id, not its code — it is the {project} segment in {kendo_url}/api/projects/{project}/error-events.
Report exceptions
Report from your application's exception handler. In Laravel 11+ (bootstrap/app.php):
php
use ScriptDevelopment\KendoErrorTracker\ErrorTracker;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (Throwable $e): void {
app(ErrorTracker::class)->report($e);
});
})Or from a classic App\Exceptions\Handler::report():
php
public function report(Throwable $e): void
{
app(\ScriptDevelopment\KendoErrorTracker\ErrorTracker::class)->report($e);
parent::report($e);
}That single call is the whole integration. report() is swallow-on-failure: it never throws and never blocks the request, so it is safe to call from inside your own exception handler.
Dispatch modes
- Async (default).
report()dispatches a queued job (0 retries) — a failed POST logs to the localerror_logand is never requeued, so error tracking never amplifies load during an outage. - Sync. Set
ERROR_TRACKER_SYNC=trueto POST inline in the request that reported. Both modes swallow every failure.
Raw HTTP (non-Laravel)
For systems the client doesn't cover, post directly to the Error Events API. You are then responsible for scrubbing PII and normalizing paths yourself — the server applies a best-effort path-prefix fallback, but it can't scrub message contents or know your deploy root the way the client does.
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": "RuntimeException",
"message": "Undefined array key \"user_id\"",
"stack_trace": "#0 /var/www/app/Http/Controllers/OrderController.php(42): handle()\n#1 {main}"
}'A successful ingest returns 202 Accepted with an empty body. See the Error Events API for the full field reference, validation limits, and error codes.
What gets sent
Both paths POST the same body to {kendo_url}/api/projects/{project}/error-events:
json
{
"environment": "production",
"release": "v1.2.3",
"exception_class": "RuntimeException",
"message": "<scrubbed exception message>",
"stack_trace": "<scrubbed, path-normalized stack trace>"
}release is omitted when unset. No request, user, or context fields are sent, and any extra fields in the body are ignored — kendo deliberately does not persist request bodies or arbitrary context with error groups.
Where errors land — and triage
Ingested events become error groups on the project. Identical errors (same exception class and normalized top stack frame) collapse into one group with an occurrence count, so a fault firing thousands of times shows up once. Each group tracks first_seen_at, last_seen_at, and the latest message and stack trace.
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.
See Also
- Error Events API — Endpoint reference, fields, deduplication, and error codes
- Project Tokens — Create and manage
error-events:writetokens - Sending Reports — Submit user-facing feedback reports the same way
kendo-error-tracker— Client library source and changelog