# Authentication

API keys, the scopes they carry, and what a refused request looks like.

The API authenticates with keys, not sessions. A key is created in the console, belongs to the organization rather than to the person who made it, and carries a fixed set of scopes chosen when it is created.

## Creating a key

In the console, **API keys → Create key**. Give it a name that says what will hold it (`ci-deploy`, `siem-poller`) and tick the scopes it needs. The secret begins with `ak_` and is shown once; Feint cannot show it again. Admins and members can create and delete keys; a read-only seat can use the console but not manage keys.

Keys are managed only in the console. There is no endpoint for creating, listing or deleting them.

## Sending it

Every request carries the key as a bearer token:

```bash title="curl"
curl https://api.feintsecurity.com/v1/tokens \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python title="Python"
import os
import requests

r = requests.get(
    "https://api.feintsecurity.com/v1/tokens",
    headers={"Authorization": f"Bearer {os.environ['FEINT_API_KEY']}"},
)
```

A console session is not accepted by the API, and a key is not accepted by the console. They are separate credentials for separate surfaces.

## Acting as a client

For MSSP partners. A key belongs to your organization and acts as it by default. To act as one of your clients instead, add its id as the `X-Feint-Client` header; `GET /v1/clients` lists the ids. Every endpoint honours the header, and every canary and event comes back with a `client` naming whose it is.

```bash title="curl"
curl https://api.feintsecurity.com/v1/tokens \
  -H "Authorization: Bearer $FEINT_API_KEY" \
  -H "X-Feint-Client: cli_4f1c9a2b7e3d4c5a8b6e0f1d2c3b4a59"
```

```python title="Python"
import os
import requests

headers = {"Authorization": f"Bearer {os.environ['FEINT_API_KEY']}"}
clients = requests.get("https://api.feintsecurity.com/v1/clients", headers=headers).json()
for client in clients:
    alerts = requests.get(
        "https://api.feintsecurity.com/v1/alerts",
        headers={**headers, "X-Feint-Client": client["id"]},
    ).json()
```

An id that is not one of your live clients is a `404`, whatever it names.

## Scopes

| Scope | Grants |
|---|---|
| `alerts:read` | View alerts generated by canary use |
| `tokens:read` | List canary tokens and their placement metadata |
| `tokens:write` | Request, rotate and revoke canary tokens |


Scopes do not imply one another. A key with only `tokens:write` can issue, rotate and revoke canaries but cannot list them; a key that polls for alerts needs only `alerts:read`. Give each holder the least it needs:

| Holder | Scopes |
|---|---|
| A CI job that plants a canary in each new environment | `tokens:write` |
| A script that rotates canaries on a schedule | `tokens:write`, `tokens:read` |
| A SIEM or on-call integration reading alerts | `alerts:read` |

The scope each endpoint needs is listed in the [overview](https://feintsecurity.com/docs#endpoints).

## When a request is refused

| Status | `detail` | Why |
|---|---|---|
| `401` | `A valid API key is required.` | No `Authorization` header, or the key is not one Feint issued. |
| `401` | `This API key is no longer valid.` | The key was deleted in the console. |
| `403` | `This API key is missing the 'tokens:write' scope.` | The key is valid but was not given the scope this endpoint needs. |
| `404` | `No such client.` | `X-Feint-Client` names no live client of your organization. |

Feint confirms each key's standing with the key store and caches the answer for 30 seconds, so a deleted key stops working within that time.

Match on the status code, not the message; the wording may change.

## Keeping keys safe

- Store a key in your CI system's masked variables or a secrets manager, never in the repository.
- One key per holder, named for it, so that deleting one key stops one thing.
- To rotate, create a new key, move the holder over, then delete the old one.
- If a key leaks, delete it in the console. Anything using it is refused within 30 seconds.
