The admin endpoints are the admin panel, for the people who run a propfirm and
for our own staff. They are part of the [app API](/docs/api/apps/overview) and
take a user token, never an API key, because every one of them answers the
question of what this particular person may see and do.

They live under `/v1/admin`, beside `/v1/me`, and they are being added screen by
screen. What is here today is the frame the app draws around those screens.

## Who may use them

A token for the app `admin`, holding `admin:read` and, for anything that
changes something, `admin:write`. Those two scopes are only granted to a
[sign-in](/docs/api/apps/sign-in) with `"app": "admin"`. A token for Propper
Mail that asks for them signs in without them, and answers
`403 insufficient_scope` here.

The account has to help run the environment, by the same test as the admin
panel on the web: the firm's owner, our own staff, or somebody whose role
grants at least one permission. Anybody else is refused at sign-in already,
with `403 admin_access_required`, so an app never ends up holding a token for a
panel it cannot open.

## Which firm

A request acts in the environment its account belongs to, or in the one named
by the `Propper-Workspace` header, as [everywhere else](/docs/api/apps/overview#acting-in-another-environment).

Our own staff belong to the platform. An endpoint that works on one firm, such
as [your sidebar](/docs/api/admin/sidebar), answers them
`400 workspace_required` until they name one. That is on purpose: without a
firm, staff can see the rows of every firm at once, and a forgotten header
should be an error rather than a list of everybody's traders.

An endpoint of the platform itself answers anybody else `403 platform_required`.

## Permissions

Each endpoint asks for the permission its screen on the web asks for, worked
out the same way: an owner holds everything, and a permission that implies
another counts as both. Its page names it at the top and in its errors. Without
it the answer is `403 forbidden`, with `required_permission` naming what was
missing:

```json
{
    "error": {
        "type": "permission_error",
        "code": "forbidden",
        "message": "Your role does not allow this.",
        "required_permission": "view_payouts"
    },
    "meta": { "request_id": "req_01kz4bkr1aj1gv63d8fv3133ey" }
}
```

Draw the app from [the profile](/docs/api/admin/profile), which answers every
permission there is, and expect this anyway when a role changes between the
call and the tap.

## Everything is in the activity log

Every screen read in a firm is written to that firm's activity log, as a page
viewed on the web is, with `source` set to `app`. A firm's audit trail should
not go quiet because somebody read it on a phone. Asking for the same page
again within thirty seconds is one entry, so pulling to refresh does not flood
it. The profile is not logged: an app asks for it every time it starts, which
says nothing about what anybody looked at.