API: HTTP ingest
Optional plugin. Enable per application in Settings → Plugins; ingest and ingest-settings 404 while disabled. See HTTP ingest for how this fits the rest of the pipeline, and Capture SDKs for the Laravel/PHP/browser clients that use this endpoint.
Authentication
Its own per-application ingest key — independent of LOG_LENS_TOKEN and exempt from the dashboard's token/same-origin guard, since a browser SDK or headless worker has no dashboard credentials. Present it as:
- header
X-Log-Lens-Ingest-Key: <key>, or Authorization: Bearer <key>, or&key=<key>in the query string.
Missing/wrong key → 401. Get or rotate it via ?api=ingest-settings.
POST /?api=ingest
curl -X POST "$LOG_LENS_URL/?api=ingest&app=my-app" \
-H "X-Log-Lens-Ingest-Key: llk_…" -H "Content-Type: application/json" \
-d '{"events":[{"message":"Payment capture failed","severity":"ERROR","exception_class":"RuntimeException","module":"billing","context":{"order":123}}]}'
Send one event object, an array, or {"events":[…]} — up to 500 per request.
| Field | Notes |
|---|---|
message | Required. |
severity | EMERGENCY…DEBUG; defaults to ERROR. |
exception_class | Optional; improves grouping and is shown as the exception. |
stack | Optional stack trace; drives the source frame and stack view. |
context | Optional object; stored as the issue context. |
environment | Defaults to production. |
channel | Logical stream name; defaults to app. |
module | Optional module slug/name; created if missing. |
occurred_at | ISO-8601 or Y-m-d H:i:s; defaults to now (UTC). |
release | Optional version/commit — see Release tracking. |
fingerprint | Optional custom grouping key; events sharing it group into one issue regardless of message. |
tags | Optional string array; each becomes a Log Lens tag. |
request / user | Optional objects, shown in the issue's Context tab. |
breadcrumbs | Optional array rendered as a timeline. |
server_name, runtime | Optional strings shown as context chips. |
Identical events group into one issue with multiple occurrences, exactly like repeated log lines (a custom fingerprint overrides this). Response is 202 with {accepted, received, dropped}. Runs the standard finalize path, so enabled alerts fire on pushed errors too.
Rate limiting
Sliding window, default 1000 events / 60s (ingest.rate_max_events / ingest.rate_window_seconds) — the previous window's count is weighted by remaining overlap, so there's no ~2× burst at a boundary. Over budget: excess events are dropped (still 202, with a dropped count); window fully exhausted: 429 with retry_after (seconds). An optional secondary per-IP limit (ingest.rate_max_events_per_ip, off by default) bounds a single visitor's impact when the browser SDK's key is public. Current effective limit shown in Settings → Plugins → HTTP ingest.
GET / POST /?api=ingest-settings
GET → {ingest_url, key, key_header, rate_limit}. POST {"action":"regenerate"} rotates the key — the old one stops working immediately.
Related
- Capture SDKs — Laravel auto-reporter, generic PHP client, browser SDK; all three post here.
- HTTP ingest — why this is the same pipeline as file-based logs, not a separate system.