> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peeker.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Bearer keys, sandbox vs live, key rotation, and what an unauthorized response looks like.

Every Partner API request needs a bearer key. You get a live key (`pk_live_…`) and a sandbox key (`pk_test_…`) from the [Partner portal](https://app.peeker.com/settings/api). Sandbox completes orders instantly with synthetic data and never bills. Switch to live once shapes match.

## Get a key

<Steps>
  <Step title="Open the Partner portal">
    Go to [Settings → API](https://app.peeker.com/settings/api).
  </Step>

  <Step title="Create a key">
    Pick **New key**, label it, and choose a [permission preset](#permission-presets). You'll see
    the secret once - copy it now.
  </Step>

  <Step title="Pick the environment">
    `pk_live_…` runs against production. `pk_test_…` runs against sandbox. Same base URL:
    `https://api.peeker.ai/partner/v1`.
  </Step>
</Steps>

<Snippet file="sandbox-tip.mdx" />

## Send the key on every request

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl https://api.peeker.ai/partner/v1/me \
  -H "Authorization: Bearer pk_test_<your-key>"
```

The header format is exactly `Authorization: Bearer <key>`. No other auth schemes are accepted.

## Verify with `GET /me`

`GET /me` returns the partner record and the key's environment + preset. It's the cheapest possible health check - use it as your first call.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl https://api.peeker.ai/partner/v1/me \
  -H "Authorization: Bearer pk_test_<your-key>"
```

Response:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
	"data": {
		"api_key": {
			"environment": "test",
			"permission_preset": "full"
		},
		"partner": {
			"id": "org_01HZX0OG1A2B3C4D5E6F7G8H",
			"name": "Acme Resellers"
		}
	}
}
```

A `200` with `api_key.environment: "test"` confirms you're hitting sandbox routes.

## Permission presets

Keys are scoped at creation time. The active preset shows up on `GET /me` as `api_key.permission_preset`.

| Preset    | Reads         | Writes                                                                                                 |
| --------- | ------------- | ------------------------------------------------------------------------------------------------------ |
| `full`    | All resources | Everything - users, bundles, domains, orders, swaps, imports, forwarding.                              |
| `limited` | All resources | Domain availability and quote estimates only. No order creation, swaps, users, bundles, or forwarding. |
| `read`    | All resources | None - every write returns `403 forbidden`.                                                            |

To change a key's preset, create a new key with the preset you want and revoke the old one - presets are locked at creation.

## Rotate a key without downtime

<Steps>
  <Step title="Create the replacement">
    Open the Partner portal → API → **New key**. The new key is valid immediately.
  </Step>

  <Step title="Roll your deployment">
    Push the new key to your environment. Both keys work in parallel during the rollout window.
  </Step>

  <Step title="Revoke the old key">
    Once traffic has moved over, revoke the old key. Revocation is immediate - any in-flight request
    signed with the old key returns `401 unauthorized`.
  </Step>
</Steps>

<Warning>
  Revoke immediately if a key leaks. There is no soft-revoke window - the next request after
  revocation fails with `401 unauthorized`.
</Warning>

## Track requests with `Peeker-Request-Id`

Every response - success or error - includes a `Peeker-Request-Id` header. Log it. When you contact support, this is what we look up.

```http theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
HTTP/1.1 200 OK
Peeker-Request-Id: req_01HZX0E5K7N9P2Q4R6S8T0U1V3
Content-Type: application/json
```

## What an unauthorized response looks like

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
	"error": {
		"code": "unauthorized",
		"message": "API key is missing, malformed, or revoked"
	}
}
```

Status: `401 Unauthorized`. Common causes: missing `Authorization` header, malformed prefix, or revoked key.

A `403 forbidden` response means the key is valid but doesn't have permission for the action. Use a `full` key (or rotate the existing one to `full`) for write endpoints.

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
	"error": {
		"code": "forbidden",
		"message": "API key does not have permission for order_create"
	}
}
```
