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

# Server API Keys

> Authenticate server-to-server calls with x-app-api-key and optionally lock your app to key-only access

<Info>
  This recipe is for partners calling the AGG API **from their own backend** (Node,
  Go, Python, anywhere that isn't the end-user's browser). Browser-based
  integrations do not need an API key — they're authenticated by their
  [allowed origins](/recipes/bot-protection) and the end-user's session token.
</Info>

<Warning>
  An API key is a secret. Never embed it in browser bundles, source maps,
  public repositories, or logs. Treat it like a database password.
</Warning>

## When You Need One

Send `x-app-api-key` from your backend when:

* You call any AGG endpoint from server code (no `Origin` header, no user session).
* You want per-key rate-limit quota instead of the shared browser-IP quota.
* You've enabled [`requireApiKey`](#locking-your-app-to-key-only-access) on your app.

The header is always accepted but never required by default — turning on `requireApiKey`
is how you make it mandatory for your app.

## Creating a Key

Open the admin dashboard, select the target app, and go to **Credentials → API Keys
→ Create new key**. Pick a scope:

| Scope        | Can do                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------ |
| `read`       | Read-only access to your app's public endpoints.                                                 |
| `read_write` | Read + partner-admin write operations on your app (members, settings, etc., excluding security). |

The key value is shown **once** in the creation dialog. Copy it immediately and
store it in your secret manager — AGG only stores a SHA-256 hash and cannot
display the raw value again.

Keys have the format `agg_<appId>_<64 hex>`. The `appId` is embedded so the key
is bound to a specific app; using it against a different app's `x-app-id` is
rejected with 403.

## Sending It

### From `@agg-build/sdk`

```ts theme={null}
import { createAggClient } from "@agg-build/sdk";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: process.env.AGG_APP_ID!,
  apiKey: process.env.AGG_API_KEY!, // server-side only
});

// Every call now includes x-app-api-key automatically.
await client.getOrderbooks({ venueMarketIds: ["..."] });
```

### Raw HTTP

```http theme={null}
GET /app/config HTTP/1.1
Host: api.agg.market
x-app-id: your-app-id
x-app-api-key: agg_your-app-id_<64 hex>
```

A key that's missing, malformed, unknown, revoked, expired, or bound to a
different app is rejected with 401 / 403. The server returns identical
`"Invalid API key"` messages for unknown and wrong-app cases — don't rely on
the error text to distinguish them.

## Locking Your App to Key-Only Access

By default the key is optional — requests without one proceed as before. If
your integration is strictly server-side and you want AGG to hard-reject
unauthenticated calls, flip **Require API key** in the admin dashboard's
**Settings** view for that app.

Once enabled:

* Any request to your app without a valid `x-app-api-key` returns 401 with
  `"This app requires x-app-api-key for all requests."`
* Browser-SDK calls from your app will start failing — they don't carry a key.
  Only turn this on for server-only apps.
* The toggle can only be flipped by a signed-in admin. A `read_write` API key
  cannot call the endpoint that changes it (defense against a compromised key
  locking you out of your own app).

<Warning>
  Enabling `requireApiKey` on a browser-facing app will break the integration.
  If in doubt, leave it off — the origin allowlist already protects browser
  callers from cross-site misuse.
</Warning>

## Rate Limits

A validated `x-app-api-key` moves you into a dedicated per-key budget:

* **Default** — shared with your app's general quota (`RATE_LIMIT_APP_MAX`,
  18 000 requests/minute on the platform today).
* **Custom** — AGG can raise or lower an individual key's budget on request;
  once set, that key no longer shares IP-level throttling.

Keyed requests respond with `429 Too Many Requests` and a `Retry-After: <seconds>`
header when the per-key bucket is exhausted.

## Rotating a Key

1. Create a new key in the admin dashboard.
2. Deploy your backend with the new key.
3. Confirm traffic is using the new key (check `lastUsedAt` in the admin).
4. Revoke the old key.

Revoked and expired keys fail fast with 401 — there is no grace period.
