# Authentication

> How do I authenticate, and what is the difference between live, test and keyless sandbox access?

Source: https://docs.betterjobs.cc/getting-started/authentication/

Every request except the sandbox sends an API key as a Bearer token:

```http
Authorization: Bearer bj_live_...
```

A missing or invalid key returns `401 unauthorized`. See [Errors](https://docs.betterjobs.cc/platform/errors.md#unauthorized).

## Three ways in

| Access          | Key           | Data                                                                  | Credits                | Use it for                                                               |
| --------------- | ------------- | --------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------ |
| Keyless sandbox | None          | Fixed illustrative data                                               | Never charged          | A first call, demos, docs examples. Only `POST /v1/sandbox/jobs/search`. |
| Test key        | `bj_test_...` | Sandbox data                                                          | Never charged          | Development, CI, integration tests.                                      |
| Live key        | `bj_live_...` | Live results from the BetterJobs index and the providers on your plan | Charged per unique job | Production.                                                              |

All three use the same base URL, `https://api.betterjobs.cc/v1`, and the same request and response shapes. Switching from test to live is a key change, not a code change.

> Sandbox data is not real
>
> Sandbox and test responses use fictional companies on `.example` domains. Do not draw conclusions about coverage or freshness from them.

## Send the key

Read the key from an environment variable. Also pin the API version with `BetterJobs-Version`, so new versions never change your responses. See [Versioning](https://docs.betterjobs.cc/platform/versioning.md).

**curl**

```bash
export BETTERJOBS_API_KEY="bj_live_..."


curl https://api.betterjobs.cc/v1/account \
  -H "Authorization: Bearer $BETTERJOBS_API_KEY" \
  -H "BetterJobs-Version: 2026-10-01"
```

**Python**

```python
import os
import requests


session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {os.environ['BETTERJOBS_API_KEY']}",
    "BetterJobs-Version": "2026-10-01",
})


account = session.get("https://api.betterjobs.cc/v1/account")
account.raise_for_status()
print(account.json()["plan"], account.json()["credits_remaining"])
```

**TypeScript**

```ts
const apiKey = process.env.BETTERJOBS_API_KEY;
if (!apiKey) throw new Error('BETTERJOBS_API_KEY is not set');


const resp = await fetch('https://api.betterjobs.cc/v1/account', {
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'BetterJobs-Version': '2026-10-01',
  },
});
if (!resp.ok) throw new Error(`BetterJobs ${resp.status}`);
const account = await resp.json();
console.log(account.plan, account.credits_remaining);
```

`GET /v1/account` is free. It returns your plan, credits left, when they reset, the providers your key can use and your rate limit. Use it as a health check after you set or rotate a key. See [Get your account](/api/operations/getaccount/).

## Keep keys server-side

A live key spends your credits. Anyone who has it can run up your bill.

- Never put a key in browser JavaScript, a mobile app, a public repo or a shared spreadsheet.
- Call BetterJobs from your backend. If a frontend needs job data, proxy the request through your server and keep the key there.
- No-code tools such as Clay, n8n, Make and Zapier store the key in their server-side connection or header settings. Put it there, not in a cell or a URL.
- Set `waterfall.max_credits` on every search, so even a leaked key cannot drain your account in one request.

> Never put the key in a URL
>
> Keys go in the `Authorization` header only. URLs end up in logs, browser history and referrer headers.

## Rotate a key

Rotate on a schedule and whenever someone with access leaves.

1. Create a new key in your dashboard. The old one keeps working.
2. Deploy the new key to every place that uses the old one.
3. Call `GET /v1/account` with the new key to confirm it works.
4. Revoke the old key in your dashboard.

If a key leaks, revoke it first and rotate after. Then check `GET /v1/billing/ledger` for charges you do not recognise. Each entry names the `request_id` and `operation` that charged it. See [Get the billing ledger](/api/operations/getledger/).

## Agents and MCP

The MCP server takes the same key as a Bearer token, or OAuth where your client supports it. See [MCP server](https://docs.betterjobs.cc/agents/mcp-server.md).

## Related

- [Quickstart](https://docs.betterjobs.cc/getting-started/quickstart.md)
- [Rate limits](https://docs.betterjobs.cc/platform/rate-limits.md)
- [Idempotency](https://docs.betterjobs.cc/platform/idempotency.md)
