# Authentication and keys

> API keys are Polar license keys that start with fq_live_. How to get one, send it, keep it server-side and rotate it.

Source: https://www.framequery.com/docs/authentication (updated 2026-10-13)

## Sending your key

Every API endpoint except the keyless discovery endpoints takes your key in one of two headers:

```bash
curl https://api.framequery.com/v1/api/jobs \
  -H "Authorization: Bearer $FRAMEQUERY_API_KEY"

# Same key, alternative header
curl https://api.framequery.com/v1/api/jobs \
  -H "X-API-Key: $FRAMEQUERY_API_KEY"
```

A missing, invalid, revoked or expired key returns `401` with code [`UNAUTHORIZED`](https://www.framequery.com/docs/errors#UNAUTHORIZED). The SDKs read the key from the `FRAMEQUERY_API_KEY` environment variable when you do not pass one.

These endpoints need no key at all, so you can explore prices and formats before you sign up: [`GET /openapi.json`](https://api.framequery.com/v1/api/openapi.json), [`GET /pricing`](https://api.framequery.com/v1/api/pricing), [`GET /pricing/estimate`](https://api.framequery.com/v1/api/pricing/estimate?durationSeconds=3600&mode=all) and [`GET /formats`](https://api.framequery.com/v1/api/formats).

## Getting a key

Keys are [Polar](https://polar.sh) license keys. Polar is our billing provider, and it issues a key with the prefix `fq_live_` when you buy API credits:

1. During the public beta, [request access by email](mailto:support@framequery.com?subject=FrameQuery%20API%20beta%20access); we switch accounts on one by one. Once yours is on, sign in and open the [API keys page](https://www.framequery.com/profile/api-keys).
2. Buy a credit pack ($10, $50 or $100). Checkout runs on Polar.
3. Open the Polar customer portal with the **Open Polar portal** button on the API keys page to see the full key. The API keys page itself lists your keys with the secret masked.

There is no API endpoint that creates keys, and no way to create one with another key. Keys carry no scopes during the beta: every valid key has full access to its account's jobs, results and balance, and all keys of an account share its [rate limits](https://www.framequery.com/docs/rate-limits) and credits.

Verify a key and see your balance in one call:

```bash
curl https://api.framequery.com/v1/api/quota \
  -H "Authorization: Bearer $FRAMEQUERY_API_KEY"
```

## Keep keys server-side

A key can spend your credits, so treat it like a password:

- Call the API from your backend, a job runner or a script. Never put a key in browser JavaScript, a mobile app or a public repository.
- Read it from an environment variable or a secrets manager, not from source code.
- The TypeScript SDK refuses to construct a client with a key inside a browser unless you pass `dangerouslyAllowBrowser: true`. Do not.

## Rotating a key

Rotate a key whenever it may have leaked, and on a regular schedule:

1. On the [API keys page](https://www.framequery.com/profile/api-keys), click **Rotate** next to the key.
2. The new key is shown once. Copy it into your secret store straight away.
3. The old key stops working. Requests that still use it start returning `401 UNAUTHORIZED`, everywhere within about a minute (validated keys are cached for up to 60 seconds).

Because rotation is immediate, deploy the new key first if you can: rotate, update your secret store, redeploy, and expect a brief window of `401`s from anything still holding the old key.

## Dashboard-only endpoints

Key and webhook-secret management is done from the FrameQuery dashboard and is authenticated with your signed-in session, not with an API key. These endpoints appear in the OpenAPI document under the **Keys** and **Webhooks** tags for completeness; an `fq_live_` key cannot call them.

| Endpoint | What the dashboard uses it for |
|---|---|
| `GET /keys` | List your keys: `id`, masked `displayKey`, `status` (`granted`, `revoked`, `disabled`), `expiresAt`, `createdAt` |
| `POST /keys/{id}/rotate` | Rotate one key; the response holds the new full `key`, once |
| `GET /keys/portal` | A short-lived Polar customer-portal URL (keys, receipts, credit purchases) |
| `GET /webhooks/secret` | Reveal your `whsec_` webhook signing secret (created on first use) |
| `POST /webhooks/secret/rotate` | Rotate the signing secret (the old one keeps signing for 24 hours) |

## Failure modes

| Situation | What you get |
|---|---|
| No key, malformed key, revoked or expired key | `401 UNAUTHORIZED` |
| Many invalid keys from one IP address | That address gets `401` for 10 minutes without the key being checked |
| Account suspended | `403 ACCOUNT_SUSPENDED` |
| Our billing provider is unreachable | Reads keep working for a while; job creation returns `503 SERVICE_UNAVAILABLE` with `Retry-After` |
| Valid key, no credits | Job creation returns `402 INSUFFICIENT_CREDITS` |
