# Authentication and API keys

Create API keys for the Simple Feedback API, choose their scopes, limit them to one venue, and learn when and why a key stops working.

Every request to the API carries an API key as a bearer token:

```bash
curl "$SF_API/v1/me" -H "Authorization: Bearer $SF_KEY"
```

There are no sessions, cookies or refresh tokens. A key works until it expires, is revoked, or loses the access it
was created with.

## Create a key

Owners and admins of an organization create keys in the dashboard under **Settings → API keys**. A key has:

- a **name**, so people know what uses it, for example "Reservations sync (production)"
- one or more **scopes**, which decide what it can do
- optionally a **venue**, which limits it to that venue's surveys
- optionally an **expiry** of 1 to 3650 days; without one it never expires

The token is shown once, right after you create the key.

> **Warning:** Only a hash of the token is stored, so a lost token can't be recovered, only replaced by a new key.
> Keep it in your secret store, never in source code or a URL.

An API key can't create other keys. Otherwise one leaked key could create new keys that revoking it wouldn't remove.

## Token format

```bash
sfk_live_V1StGXR8Z5jd_<43-character secret>
```

The parts are `sfk`, the environment (`live` in production, `test` elsewhere), a 12-character public prefix and the
secret. The prefix is safe to log and is what the dashboard shows in the key list; the secret is not. The environment
marker only helps people tell where a leaked token came from. Nothing checks it.

## Scopes

| Scope                | Allows                                                                                          |
| -------------------- | ----------------------------------------------------------------------------------------------- |
| `surveys:read`       | List venues, list and read surveys, get a survey's link and QR code                             |
| `surveys:write`      | Create, change and delete surveys                                                               |
| `responses:read`     | Read responses and statistics                                                                   |
| `insights:read`      | Read the stored AI analysis of a survey                                                         |
| `insights:write`     | Run a new AI analysis, which uses your plan's AI quota                                          |
| `qr:read`            | List saved QR codes and read their scan statistics                                              |
| `qr:write`           | Create, change and delete [saved QR codes](https://simplefeedback.de/developer/docs/distributing-a-survey.md#create-a-saved-qr-code)        |
| `access-codes:read`  | List [single-use codes](https://simplefeedback.de/developer/docs/single-use-codes.md) with their status, get the link and QR code of a code |
| `access-codes:write` | Create single-use codes and revoke unused ones                                                  |
| `files:write`        | [Upload a logo or background image](https://simplefeedback.de/developer/docs/building-a-survey.md#add-a-header-footer-and-logo) for surveys |

**`:write` does not include `:read`.** A key that needs both gets both. An integration that only pushes surveys
shouldn't be able to read what respondents answered.

Give each integration its own key with the fewest scopes it needs. When a call needs a scope the key lacks, it
answers `403 INSUFFICIENT_SCOPE` and names the missing scope in `details.requiredScope`.

To change a key's scopes or venue later, use **Change permissions** next to the key. The token stays the same and the
new scopes apply from its next request. Only a live key can be changed, not a revoked or expired one.

## Keys for one venue

A key created for a venue only reaches that venue. Lists leave out the other venues' surveys, a survey of another
venue answers `403`, and a survey created without `venueId` goes to the key's venue. Use venue keys when an
integration belongs to one location, for example a single shop's till.

## Check a key

`GET /v1/me` needs no scope and describes the key:

```json
{
  "organizationId": "934c6a69-ed8e-4110-ba37-644a84047341",
  "tier": "BUSINESS",
  "scopes": ["surveys:read", "responses:read"],
  "venueId": null,
  "prefix": "V1StGXR8Z5jd"
}
```

It is the quickest way to tell a wrong token (`401`) from a missing scope or plan (`403`).

## A key acts for the person who created it

A key carries the permissions of the member who created it, checked again on every request:

- If their role is reduced, the key is reduced with it at once. A key created by an owner who is now a viewer can
  read but no longer write.
- If they leave the organization, or their account is suspended or deleted, the key stops working with
  `401 API_KEY_REVOKED`.
- If a venue key's creator loses access to that venue, or an organization-wide key's creator keeps only venue roles,
  the key stops working the same way.

Leaving, suspension and deletion also revoke the key for good. Adding the person back or reactivating the account
doesn't bring the key back; create a new one.

> **Warning:** Create the keys for a production integration from an account that outlives individual people, not
> from the personal login of whoever set it up.

An owner or admin can create keys, which is worth knowing before you make someone an admin: a key works outside the
dashboard for whoever holds the token. It can never do more than its creator, though, and owners and admins see and
can revoke every key of the organization.

## Revoke a key

Revoke a key in **Settings → API keys**. Its next request answers `401 API_KEY_REVOKED`. Creating, changing and revoking
keys is recorded in the organization's audit log with the prefix, never the token.

## Password changes

Changing or resetting a password signs the account out everywhere but leaves its API keys alone, so a routine
password change doesn't take your integrations offline.

If you reset the password because someone else may have been in the account, also revoke the keys they might have
created in **Settings → API keys**, and create new ones for your integrations.

## Plan and organization

On top of the key itself, every request needs:

- an active organization, otherwise `403 ORG_SUSPENDED`
- a plan with API access, otherwise `403 API_ACCESS_NOT_IN_TIER`

Both are checked on every request, so changes take effect immediately. The order of all checks is in
[Errors](https://simplefeedback.de/developer/docs/errors.md).
