Authentication
API keys, the scopes they carry, and what a refused request looks like.
# 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.
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:
curl https://api.feintsecurity.com/v1/tokens \
-H "Authorization: Bearer $FEINT_API_KEY"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.
curl https://api.feintsecurity.com/v1/tokens \
-H "Authorization: Bearer $FEINT_API_KEY" \
-H "X-Feint-Client: cli_4f1c9a2b7e3d4c5a8b6e0f1d2c3b4a59"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.
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.