# Authentication

The Flatstack API accepts either a dashboard session or a token.

## Signing in

Sign in to the dashboard with email and password, Google or GitHub. Account owners must also set up an authenticator app (TOTP) or a passkey before they can use the dashboard. This applies however the owner signs in.

A new email login gets an email with a link to confirm the address, and can't sign in until it's opened. The link works for 1 hour and signs you in the first time it's used. Signing in with the right password before then sends a fresh link. Google and GitHub logins arrive confirmed.

Forgot your password? Ask for a reset link at `/forgot-password` in the dashboard. It works once, for 1 hour. Resetting signs out every session, and we email you to say it happened. Owners still need their second factor to sign in afterwards.

Sign-up, sending a new confirmation link and asking for a reset all answer the same whether or not an address has a login, so they can't be used to find out who has one. If someone signs up with an address that already has a login, its owner gets an email instead.

Each address gets at most 5 confirmation and 5 reset emails an hour; past that, requests succeed but send nothing. Each IP can make 5 of these requests in any 15 minutes and 3 sign-in attempts in 10 seconds; past that, `429`.

| Request | What it does |
| --- | --- |
| `POST /auth/sign-up/email` | `{ "name", "email", "password", "callbackURL"? }`. Sends the confirmation email. No session yet. |
| `POST /auth/send-verification-email` | `{ "email", "callbackURL"? }`. Sends a new confirmation link if the address has an unconfirmed login. |
| `GET /auth/verify-email?token=` | Confirms the address and, the first time, signs in. |
| `POST /auth/request-password-reset` | `{ "email" }`. Sends a reset link if the address has a login. |
| `POST /auth/reset-password` | `{ "token", "newPassword" }`. At least 12 characters. |

Until checkout completes, a new login can only see the plan picker and checkout. Every other request returns `403` with the code `checkout_required`. A login that never completes checkout is deleted after 7 days.

If you belong to more than one account, send the `fs-account` header with the account ID.

## Tokens

Send a token as `Authorization: Bearer <token>`.

| Kind | Prefix | Belongs to | Scopes it can carry |
| --- | --- | --- | --- |
| Personal access token | `fs_pat_` | You, in one account | Any |
| Deploy token | `fs_dep_` | One project | `deploy`, `env:read`, `logs:read`, `data:read` |

Scopes: `projects:read`, `projects:write`, `deploy`, `env:read`, `env:write`, `logs:read`, `data:read`, `data:write`. A personal access token can only carry scopes your role has, and is limited by your role as it is now (see [Teams and roles](teams.md)).

Every token expires: after 90 days unless you choose otherwise, and after 365 days at most. The token is shown once when you create it; Flatstack stores only a hash. Revoke a token at any time and it stops working immediately.

| Request | What it does |
| --- | --- |
| `GET /v1/tokens` | Lists your account's tokens (never the token values). |
| `POST /v1/tokens` | Creates a personal access token: `{ "name", "scopes", "expiresInDays"? }`. |
| `POST /v1/projects/{projectId}/deploy-tokens` | Creates a deploy token for one project. |
| `DELETE /v1/tokens/{tokenId}` | Revokes a token. |

Creating and revoking tokens needs a dashboard session, not a token. Each is recorded in the audit log.

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No session, or the token is unknown, expired or revoked. |
| 403 | `checkout_required` | The login has no account yet. Choose a plan and finish checkout. |
| 403 | `two_factor_required` | Account owners need TOTP or a passkey. Set one up in your profile. |
| 403 | `forbidden` | Your role, or the token's scopes, don't allow this request. |