Facts reviewed: 2026-10-20

Use the SDK for browser tracking. Use these HTTP APIs for server jobs, integrations, and trusted AI tools. The API base is `https://api.owleye.dev`; unlike the browser SDK, an HTTP client needs a complete URL.

## Choose the right credential

| Credential | Purpose | Where to get it | Where it belongs |
| --- | --- | --- | --- |
| Public Tracking ID (`owl_…`) | Browser SDK events and rules | App Overview or Settings | Browser code |
| Ingestion API key, `events:write` | Send server events | App **API Keys** | Your server or secret store |
| Developer key, `stats:read` | Query aggregate reports | **Settings → Developer mode** | Your server |
| Developer key, `events:read` | Read bounded event exports | **Settings → Developer mode** | Your server |
| Developer key, `ai:prompt` | Ask the Owleye Analytics AI analyst | **Settings → Developer mode** | Your server or trusted tool executor |

These secret key families are separate and cannot be substituted for each other. A key is scoped to one app. Use the matching public Tracking ID in requests; a different app cannot be accessed just by changing the URL or JSON.

Send either header:

```http
Authorization: Bearer YOUR_SECRET_KEY
```

```http
x-owleye-api-key: YOUR_SECRET_KEY
```

Do not send secrets in URL query strings, browser bundles, SDK options, public notebooks, or LLM prompts. CORS is not granted on these server-to-server secret-key interfaces. Console session endpoints are internal to the console and are not an automation API.

## Available endpoints

| Method | Path | Required scope | Guide |
| --- | --- | --- | --- |
| POST | `/v1/events/api` | `events:write` | [Server ingestion](/docs/api/ingestion/) |
| GET | `/v1/developer/sites/{siteId}/stats` | `stats:read` | [Analytics queries](/docs/api/analytics/) |
| GET | `/v1/developer/sites/{siteId}/events` | `events:read` | [Event pagination](/docs/api/analytics/#read-events) |
| POST | `/v1/developer/sites/{siteId}/prompt` | `ai:prompt` | [AI integrations](/docs/api/ai/) |

Browse endpoints, parameters, and response models in the [OpenAPI explorer](/docs/api/openapi/), or download the [OpenAPI JSON schema](/openapi.json). Browser SDK transport uses `/v1/events` and `/v1/rules` with its public Tracking ID; prefer the SDK over implementing that browser protocol yourself.

## Access and limits

Developer APIs require the app owner to enable both **Developer mode** and **API access**. Pro permits `stats:read`; Business and Custom permit all three developer scopes. Current plan, ownership, expiry, revocation, and toggles are checked on each request.

Developer API requests share an organization allowance of 60 requests/minute on Pro or 600 on Business/Custom, with a separate source-IP ceiling of 600/minute. These are refilling token buckets, not a promise of full burst capacity every minute. Ingestion has separate limits and consumes the app's event allowance.

| Status | Action |
| --- | --- |
| 400 | Correct the request fields or bounds |
| 401 | Check key, expiry, revocation, and developer toggles |
| 403 | Check app scope, role, plan, and feature access |
| 409 | Resolve a duplicate/conflicting request; do not blindly retry |
| 413 | Reduce the request body size |
| 429 | Wait for `Retry-After` and reduce concurrency |
| 5xx | Retry eligible reads with bounded backoff; verify ingestion before replaying events |

Successful developer responses use `Cache-Control: no-store`. Keep analytics out of shared public caches. Store newly created keys immediately: their secrets are only shown once. See [developer access setup](/docs/developer-access/).

## Console request protection

Console routes have a shared **120-request/minute per-IP** budget checked before session-token validation, SQLite session lookup, or app-membership lookup. This covers current and legacy Console routes, including the session endpoint. Anonymous and forbidden requests consume the same budget; changing cookies, paths, query strings, or source ports does not create a new one. Only configured trusted proxies can supply a forwarded client IP.

The existing **60-request/minute per-session-and-IP** limit still applies after authentication. Both are refilling token buckets: the IP bucket holds at most 120 requests and refills at two per second. Users behind one public IP share the early budget. Exhaustion returns 429 with `Retry-After` before database work; responses remain `private, no-store`. The early limiter has a separate bounded pool of 4,000 tracked IP buckets so anonymous traffic cannot consume the authenticated session limiter's storage. CORS preflights do not access the database or consume this request budget. Limits are process-local, as with other API limits.

## Public metadata caching

Successful GET and HEAD responses from `/version` and `/v1/openapi.json` use `Cache-Control: public, max-age=3600, must-revalidate`. Browsers and shared caches may reuse this deployment metadata for one hour. These unversioned URLs are not marked immutable, so a deployment can remain unseen in an existing cached copy for up to one hour. Error responses, health endpoints, login configuration and authenticated data keep their existing `private, no-store` policy. Client caching reduces normal repeated requests; clients can ignore it, so it is not an abuse rate limit.

## Health endpoints

The hosted API limits each of `/health`, `/health/live`, and `/health/ready` to **one request per IP per endpoint every 120 seconds**, with no burst allowance. GET and HEAD share the same budget; query strings do not create a new budget. Excess requests return **429** with `Retry-After`. Loopback probes follow the same limit. Forwarded client IPs are accepted only from configured trusted proxies.

All three endpoints share their own in-memory snapshot across clients for **60 seconds per API process**. Concurrent cache misses run one probe per endpoint; disconnecting a client does not cancel the shared refresh. Failed checks are cached too and return **503**. A database-backed probe has a three-second timeout. `/health/live` does not query a database; `/health` checks SQLite; `/health/ready` checks SQLite, ClickHouse, and hosted retention readiness.

Responses expose `X-OwlEye-Health-Age` in seconds. HTTP responses remain `Cache-Control: private, no-store`: caching happens inside the API, not in browsers or a CDN. An expired snapshot refreshes on the next eligible request; this is not a background monitor or a guarantee of detecting failures within one minute. A single monitoring IP must poll each endpoint no more often than every two minutes and obey `Retry-After` (use 121 seconds to allow timing margin). A process restart clears snapshots and rate buckets. Limits are process-local; multiple replicas need shared or edge enforcement.

Console login capabilities use the database-free `/v1/auth/config` endpoint, with its separate 60-per-minute IP budget, so health polling limits do not hide sign-in methods.