Source: https://dinakar-pageloop-knowledge-base.docs-staging.pageloop.ai/api-reference/authentication

# Authentication

# Authentication

Kestrel uses bearer API keys. There are no OAuth flows and no session cookies.

```bash
curl https://api.kestrel.dev/v1/events \
  -H "Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc"
```

## Key types

| Prefix | Name        | Can read      | Can publish | Safe in a browser |
| ------ | ----------- | ------------- | ----------- | ----------------- |
| `pk_`  | Publishable | Metadata only | No          | Yes               |
| `sk_`  | Secret      | Everything    | Yes         | No                |
| `rk_`  | Restricted  | Per-scope     | Per-scope   | No                |

Test-mode keys carry an extra segment — `sk_test_…` — and operate on an isolated dataset.
Events published with a test key are never delivered to production subscriptions.

> [!WARNING]
>
> A secret key grants full account access. If one leaks, roll it from
> **Settings → API keys** immediately; rolling takes effect within 30 seconds.

## Restricted keys

Restricted keys carry an explicit scope list. Requesting an endpoint outside the scope
returns `403 insufficient_scope`.

#### scopes (path, string\[], required)

Any of `events:read`, `events:write`, `subscriptions:read`, `subscriptions:write`,
`deliveries:read`, `deliveries:replay`.

#### apiVersion (path, string)

Pins every response and every delivered payload to a version. See
[Migrating to v2](/guides/migrating-to-v2).

#### expiresAt (path, string)

RFC 3339 timestamp. Omit for a key that never expires. Maximum lifetime is 365 days.

## Required headers

#### Authorization (header, string, required)

`Bearer <key>`. Basic auth is not supported.

#### Content-Type (header, string, required)

`application/json` on any request with a body.

#### Idempotency-Key (header, string)

Optional on `POST`. A UUID you generate. Repeating a request with the same key inside
**24 hours** returns the original response instead of publishing twice.

## Errors

| Status | Code                 | Meaning                                                |
| ------ | -------------------- | ------------------------------------------------------ |
| 401    | `missing_api_key`    | No `Authorization` header.                             |
| 401    | `invalid_api_key`    | Key is malformed, rolled, or expired.                  |
| 403    | `insufficient_scope` | Restricted key lacks the scope.                        |
| 429    | `rate_limited`       | See [Limits and quotas](/reference/limits-and-quotas). |

Errors share one envelope:

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided has been rolled.",
    "requestId": "req_01HZXA7C4M"
  }
}
```

> [!TIP]
>
> Log `requestId` on every failure. Support can trace a request end-to-end from it.
