API conventions
Log Lens exposes JSON endpoints on the same origin as its dashboard. Every endpoint is a query string on the deployment root, selected by the api parameter.
GET /?api=errors
Base URL
Examples are relative because the paths never change with your deployment. LOG_LENS_URL is empty by default — the base URL is derived from whatever host the request actually arrived on, so the same install works unchanged on Herd, php -S, a container, or behind a reverse proxy, with no config change per environment. Set LOG_LENS_URL explicitly only to pin a canonical URL (e.g. behind a proxy, or so a CLI script always knows where to reach the API):
export LOG_LENS_URL="https://logs.example.com"
curl --get "$LOG_LENS_URL/" --data-urlencode "api=summary"
The app parameter
Every endpoint except ?api=applications is scoped to one application. Pass its id with app:
GET /?api=errors&app=billing
Omitting app selects the first registered application. There is no cross-application value — one request reads or writes exactly one application's database, which may be SQLite, Postgres, or MySQL depending on how that install is configured (see database drivers); the API is identical either way.
Content types
| Aspect | Convention |
|---|---|
| Response body | application/json on both success and error |
| Request body | Content-Type: application/json for POST, PATCH, PUT, DELETE |
| Timestamps | YYYY-MM-DD HH:MM:SS, stored and returned in UTC |
| Booleans | Query values accept true, false, 1, 0 |
Errors return a single-field envelope:
{ "error": "date must use YYYY-MM-DD." }
Authentication
Authentication is optional. When LOG_LENS_TOKEN (or auth.token in config) is non-empty, every request must present that key; when it is empty the API is unauthenticated — the local-first default. Send the key as either header:
curl --get "$LOG_LENS_URL/" \
--data-urlencode "api=summary" \
--header "X-Log-Lens-Token: $LOG_LENS_TOKEN"
# Authorization: Bearer $LOG_LENS_TOKEN is also accepted.
A missing or wrong key returns 401.
Cross-origin protection
State-changing methods (POST, PATCH, PUT, DELETE) are rejected with 403 when a browser sends an Origin header that doesn't match the request host. Non-browser clients — curl, cron, the Claude and Codex skills — send no Origin and pass through unchanged. Safe methods (GET, HEAD, OPTIONS) are never checked.
Three routes authenticate themselves and are exempt from both the API-key and cross-origin checks above, by design:
| Route | How it authenticates itself |
|---|---|
?api=ingest | Its own per-application ingest key, independent of LOG_LENS_TOKEN — see HTTP ingest. |
?api=linear-webhook | An HMAC signature over the raw request body, verified against a webhook secret — see Linear integration. |
?api=health | Nothing — it's an unauthenticated readiness probe, but only ever returns non-sensitive facts (versions, booleans), never application data. |
Every other endpoint goes through the normal guard.
Endpoints
Grouped by area; each links to its full parameter/response reference.
Core (always available)
| Method | Endpoint | Reference |
|---|---|---|
| GET | ?api=health | Readiness probe (versions, driver/schema checks, config warnings). |
| GET/POST | ?api=applications | Applications & modules |
| GET/POST | ?api=modules | Applications & modules |
| GET | ?api=summary / ?api=sources | Summary & sources |
| GET | ?api=errors / ?api=groups / ?api=issues (POST) | Issue search & creation |
| GET | ?api=error / ?api=group / ?api=source | Issue detail & raw source |
| POST | ?api=issue-status / ?api=bulk-issues | Status & bulk actions |
| POST | ?api=assign-issue · GET ?api=assignable-users | Status & bulk actions — Laravel-hosted only, see below |
| CRUD | ?api=tags · POST ?api=issue-tags | Tags |
| CRUD | ?api=connectors · -test/-preview/-sync/-runs | Connectors |
| GET/PUT | ?api=ingestion-settings | Ingestion & maintenance |
| POST | ?api=import-incoming / ?api=reindex | Ingestion & maintenance |
| GET/POST | ?api=log-deletion-preview / ?api=delete-logs / ?api=processed-retention | Ingestion & maintenance |
| GET/POST | ?api=plugins | Read/toggle a plugin's enabled state — see Plugins overview. |
Plugins (each 404s while its plugin is disabled — see Plugins overview)
| Method | Endpoint | Reference |
|---|---|---|
| CRUD | ?api=alert-channels / ?api=alert-rules · POST ?api=alert-test · GET ?api=alert-events | Alerting |
| POST | ?api=ingest · GET/POST ?api=ingest-settings | HTTP ingest |
| GET/POST/DELETE | ?api=deploys · GET/PUT ?api=release-settings · GET ?api=issue-releases · CRUD ?api=source-maps | Release tracking |
| GET/PUT | ?api=linear-settings · POST ?api=linear-test/-sync/-webhook | Linear integration |
| GET | ?api=access-analytics | Access-log analytics |
Assignment is a Laravel-only capability
?api=assign-issue and ?api=assignable-users only do anything when Log Lens is mounted inside a Laravel host app (see the Laravel package) that supplies an assignable-users callable and per-application roles. The standalone install has no user table of its own — there's nobody to assign to, so assignable-users returns an empty list and assignment is effectively a no-op. This isn't a missing feature in standalone; it's a deliberate scope decision, since a host app already has real users and standalone deliberately doesn't invent its own. See Roles & assignment for the full identity/authorization model.
Related
- Query Log Lens from the JSON API
- API key authentication
- Cross-origin (CSRF) protection
- Database drivers — SQLite (default), Postgres, and MySQL are all supported; every endpoint above behaves identically regardless of which one an application runs on.
- API status codes