Everything else in this API acts as a propfirm. These endpoints act as a person:
they are what Propper Mail and the other apps use, and what a script would use to
do something on your own behalf rather than on your firm's.

The difference is not cosmetic. An API key belongs to an organisation and does
whatever its scopes allow. A user token belongs to somebody, and what it can do
is the narrower of two things: the scopes on the token, and the permissions that
person has in the environment. Widening the token never widens the person.

## The credential

```
ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f
     ^public id  ^secret (shown once, at sign-in)
```

Sent the same way as a key:

```
Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f
```

One token per device. Signing in on a second phone does not disturb the first,
and either can be signed out on its own from [Sessions](/docs/api/apps/sessions).

## Steps to a token

1. [Sign in](/docs/api/apps/sign-in) with an email address and a password. An
   address is unique per propfirm rather than across the platform, so the
   password decides which account it is: the one it is right for, or a
   [choice](/docs/api/apps/choose-workspace) when it is right for several.
   A client that already knows the environment can
   [resolve it](/docs/api/apps/workspace) first and send it along instead.
2. [Answer the challenge](/docs/api/apps/two-factor) with a code when the account
   has two-factor authentication.

## Acting in another environment

A token acts in the environment its account belongs to. Our own staff can also
act in every propfirm, the way they can on the web by opening its domain. Name
the environment in a header and every request acts in it, narrowed to it exactly
as a propfirm's own staff are:

```
Propper-Workspace: acmefunded
```

The value is an `id` from [the environments this account can act in](/docs/api/apps/workspaces).
For anybody else the header may only name their own environment. Anything else,
including an id that does not exist, answers `403 workspace_not_permitted`.
Leaving the header out is always the account's own environment.

## Scopes a person can hold

| Scope                 | Allows                                                       |
| --------------------- | ------------------------------------------------------------ |
| `me:read`             | Read your own profile, and list your signed-in devices       |
| `me:write`            | Sign another device out. Also grants `me:read`               |
| `mail:read`           | Read the mailboxes you have access to                        |
| `mail:write`          | Send and file mail. Also grants `mail:read`                  |
| `calendar:read`       | Read the calendars you have access to                        |
| `calendar:write`      | Make and change appointments. Also grants `calendar:read`    |
| `notifications:read`  | Read your notifications                                      |
| `notifications:write` | Mark notifications as read. Also grants `notifications:read` |
| `devices:write`       | Register this device for push notifications                  |
| `ai:read`             | Read your conversations with the assistant                   |
| `ai:write`            | Ask the assistant and manage your conversations. Also grants `ai:read` |
| `admin:read`          | Open the screens of the admin panel. Propper app only        |
| `admin:write`         | Change things in the admin panel. Also grants `admin:read`. Propper app only |

Ask for what your client actually uses. Anything else in the list is dropped
rather than refused, so a client that asks for a scope no person may hold still
signs in and simply does not get it.

`admin:read` and `admin:write` are only granted to a sign-in with
`"app": "admin"`, the [Propper app](/docs/api/admin/overview). Any other app
that asks for them signs in without them.

The propfirm scopes (`affiliates:*`, `payouts:*`, and the rest) can never be
granted to a person's device. Those belong to an API key.

What `mail:read` opens is described in [Reading mail](/docs/api/mail/overview),
and `calendar:read` in [The calendar](/docs/api/calendar/overview).
`ai:read` and `ai:write` open [the assistant](/docs/api/apps/ai-overview).
`notifications:read` opens [the notification centre](/docs/api/apps/notifications)
and [its settings](/docs/api/apps/notification-settings),
and `me:read` also signs an app onto
[a websocket channel](/docs/api/apps/realtime).

## What ends a token

- Signing out on the device, or signing it out from another one.
- Changing the account password, which ends every device except the one that
  changed it.
- Ninety days without a single request.
- The account being deactivated, or losing access to the environment.

A token that has ended answers `401` with `revoked_token` or `expired_token`.
Both mean the same thing to a client: send the person back to the sign-in screen.

## Two-factor

An account with two-factor authentication has to answer a challenge before it
gets a token. Codes from an authenticator app and recovery codes both work.

An account whose only method is a passkey cannot sign in from an app yet, and
says so with `two_factor_method_unavailable` instead of failing at the code step.
Add an authenticator app on the web and try again.