> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neobank.proofof.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Operator console

> Operators are view-and-suspend only. They authenticate through a separate Auth0 client with MFA and reach a dedicated set of /v1/ops/* routes.

# Operator console

The operator console is the oversight surface for staff at a launching bank. An operator
can inspect operations, mandates, and audit events, and can suspend a mandate. An operator
cannot create payments, transfers, mandates, or cards — no such endpoint exists under
`/v1/ops/*`. The operator role is **view and suspend only** by design.

## Operator identity

Operators authenticate through a **separate Auth0 client** from customers
(`NEOBANK_AUTH0_OPS_CLIENT_ID`), and a successful operator login must carry the `operator`
role in its identity token. The API rejects an operator login that lacks the role, and
rejects an operator identity presented through the customer application:

```python apps/api/src/neobank/api.py theme={null}
if transaction.operator and "operator" not in roles:
    raise Forbidden("The operator identity does not carry the operator role.")
if not transaction.operator and "operator" in roles:
    raise Forbidden("Use the dedicated operator login for operations access.")
```

Operator login requires **multifactor authentication**. The `/v1/session/login` endpoint
sets the MFA authentication-context-class reference whenever `operator=true` is passed.
The callback merges the ID token's `amr` claim with the namespaced `step_up_methods`
claim the post-login Action sets to `["passkey"]` for passkey-primary logins — Auth0 records passkeys
as `performed_amr: ["phr"]` in tenant logs only, never in the token's `amr` — and
rejects a step-up or operator transaction whose combined methods miss the accepted
set (`mfa`, `webauthn`, `passkey`, `phr`):

```python apps/api/src/neobank/api.py theme={null}
if transaction.step_up and not has_step_up_authentication(authentication_methods):
    raise Forbidden("Auth0 did not confirm multifactor authentication.")
```

See [Authentication](integrate/authentication) for the full BFF and step-up flow.

## Operator endpoints

Every route under `/v1/ops/*` is gated by `require_role("operator")`. The available
endpoints are:

| Method | Path                                    | Purpose                                                             |
| ------ | --------------------------------------- | ------------------------------------------------------------------- |
| `GET`  | `/v1/ops/operations`                    | List recent operations, newest first (`limit`, max 200)             |
| `GET`  | `/v1/ops/mandates`                      | List recent mandates, newest first (`limit`, max 200)               |
| `POST` | `/v1/ops/mandates/{mandate_id}/suspend` | Disable a mandate locally and queue upstream revocation             |
| `GET`  | `/v1/ops/audit`                         | List audit events, optional `customer_id` filter (`limit`, max 200) |
| `GET`  | `/v1/ops/openapi.json`                  | Operator-only OpenAPI document                                      |
| `GET`  | `/v1/ops/docs`                          | Operator-only Swagger UI                                            |

The two documentation endpoints are operator-gated and `include_in_schema=False`, so they
do not appear in the public Swagger at `/docs`. They exist so an operator can inspect the
live contract and try endpoints against their own session without leaving the authenticated
context.

There is deliberately **no** operator endpoint to create a payment, transfer, mandate, or
card; to execute a mandate; or to resume a mandate. Suspension immediately moves the mandate
to `revocation_pending`, cancels unsent reservations, and queues the upstream grant revoke.
It cannot return to `active`. Confirmed read evidence moves it to `revoked`; a definitive
failure moves it to `revocation_failed`, while uncertain submission remains disabled for
operator review. See [Agent mandates](integrate/agent-mandates) for the full state flow.

## Authenticated API docs in production

Production disables the unauthenticated documentation routes, but registers guarded
equivalents at **`/docs`** and **`/openapi.json`**. Any authenticated principal — customer
session, operator session, or agent bearer token — can load them; signed-out requests
receive `401`. The page fetches the caller's CSRF token from `GET /v1/session` and attaches
`X-CSRF-Token` to unsafe try-it-out requests, so `POST` calls run against the caller's own
session. Both routes stay `include_in_schema=False` and never appear in the exported
contract. In development the standard unauthenticated FastAPI docs serve the same paths.

## Web entry point

The web app exposes the console at **`/ops`**. An operator who signs in is redirected
there and kept off the customer surface:

```tsx apps/web/src/components/app-shell.tsx theme={null}
const isOperator = session.roles.includes("operator");
if (isOperator && pathname !== "/ops") return <Navigate to="/ops" replace />;
```

The operator sidebar shows only the Operations entry; the customer navigation (Send,
Mandates, Cards) is hidden. A customer identity cannot reach `/ops` — the role check runs
in both the API and the shell.

## What an operator does here

* **Triage a stuck operation.** Open `/v1/ops/operations`, find the operation by recency,
  and read its `state`, `failure_code`, and `submission_uncertain` flag. Cross-reference
  the `request_id` against [Audit](operate/audit) to see who initiated it.
* **Stop a runaway mandate.** `POST /v1/ops/mandates/{id}/suspend` moves the mandate to
  `revocation_pending`, which blocks further mandate executions by every client and rule
  before the worker attempts upstream revocation.
* **Investigate attribution.** `/v1/ops/audit` filtered by `customer_id` shows every
  sensitive action with its full actor chain, including which authorized client executed a
  mandated payment.

<Warning title="Operator actions are themselves audited">
  `POST /v1/ops/mandates/{id}/suspend` writes an audit event attributed to the operator.
  Suspension is not reversible. There is no `/v1/ops/mandates/{id}/unsuspend` or
  customer-facing resume endpoint.
</Warning>

## Related

* [Audit](operate/audit) — the audit trail operators read alongside this console.
* [Authentication](integrate/authentication) — Auth0 clients, MFA, and the step-up flow.
* [Agent mandates](integrate/agent-mandates) — who can execute a mandate, and what
  suspension stops.
