# Your Tenant's Quorum

Four eyes on your administrators' sensitive actions: a change to users and roles, credentials and secrets, or custody waits — nothing done — until another administrator of your tenant approves it with their password.

## Overview

- **A covered action** — creating, deleting or changing another user, an API key or secret, a webhook or its secret, a server credential, your MPC recovery key, your institution callback or MPC co-signer, a Gas Station funding wallet — **answers `202`** instead of doing it:

```json
{
  "state": "awaiting_quorum",
  "requestId": "4e2b9c17-8a3d-4f60-b5e1-7c9d0a2f6b38"
}
```

- **Another administrator approves it** with [Approve](https://docs.aureahub.com/docs/quorum-request-approve.md) and their own password; the action then runs at once, as the administrator who asked for it. **The one who asked reads its answer once** with [Read the Answer](https://docs.aureahub.com/docs/quorum-request-result.md) — a new API secret, a webhook's secret: shown to them only.
- Every request — held, or only recorded — is listed under **Quorum** in the API Reference, with its decisions.
- Each decision is written to your tenant's [audit trail](https://docs.aureahub.com/docs/audit-events.md) [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md), as `admin_quorum.*`.

## Modes

Every tenant's quorum records today (`log`); holding actions (`enforce`) is on request [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md):

| Mode | A covered action of one of your administrators |
| --- | --- |
| off | Done at once, as before; nothing recorded. |
| log | **Every tenant today.** Done at once, as before, and recorded as a request in state `observed` — so you see who did what before turning it on. |
| enforce | **On request. Held**: `202` `awaiting_quorum`, nothing done until another administrator approves it. A tenant on the institutional security profile is in `enforce` by its profile. |

- **Never locked out.** When no other active administrator of your tenant could approve the action, it is done at once and recorded as `observed`, as in `log` (its `mode` reads `enforce`).
- A request's `mode` is the one in force when it was asked.

## Covered Actions

| Route | action |
| --- | --- |
| POST /v1/users/ | `users.tenant_user.create_own` |
| PUT /v1/users/{id} — another user's account | `users.account.update` |
| DELETE /v1/users/{id} — another user's account | `users.account.delete` |
| POST /v1/admin/tenants/{tenantId}/users | `users.tenant_user.create` |
| DELETE /v1/admin/tenants/{tenantId}/users/{userId} | `users.tenant_user.delete` |
| POST, DELETE /v1/admin/tenants/{tenantId}/users/{userId}/email-change | `users.email_change.start`, `users.email_change.cancel` |
| POST /v1/admin/tenants/{tenantId}/users/{userId}/password-reset POST /v1/auth/admin/send-reset | `users.password_reset.send`, `users.password_reset.send_support` |
| DELETE /v1/admin/tenants/{tenantId}/users/{userId}/mfa — another user's second factor | `users.mfa.reset` |
| DELETE /v1/admin/tenants/{tenantId}/users/{userId}/passkeys — a user's passkeys ([Reset a User's Passkeys](https://docs.aureahub.com/docs/passkeys-reset.md)) | `users.passkeys.reset` |
| POST /v1/admin/tenants/{id}/rotate-key POST /v1/admin/tenants/{id}/rotate-secret | `credentials.api_key.rotate`, `credentials.api_secret.rotate` |
| PUT, DELETE /v1/admin/tenants/{id}/webhooks/{environment} POST …/webhooks/{environment}/rotate-secret | `credentials.webhook.save`, `credentials.webhook.delete`, `credentials.webhook_secret.rotate` |
| POST /v1/admin/tenants/{id}/webhook-endpoints PATCH, DELETE …/webhook-endpoints/{endpointId} POST …/webhook-endpoints/{endpointId}/rotate-secret | `credentials.webhook_endpoint.create`, `.change`, `.delete`, `credentials.webhook_endpoint_secret.rotate` |
| POST /v1/admin/tenants/{id}/server-credentials PATCH …/server-credentials/{credentialId} | `credentials.server_credential.create`, `credentials.server_credential.update` |
| PUT /v1/tenant/mpc-recovery-key — where your tenant has the recovery kit ([Register a Recovery Key](https://docs.aureahub.com/docs/mpc-recovery-key-register.md)) | `custody.mpc_recovery_key.register` |
| PUT /v1/admin/tenants/{id}/institution-callback/{environment} ([Change the Settings](https://docs.aureahub.com/docs/institution-callback-put.md)) | `policy.institution_callback.change` |
| PUT /v1/admin/tenants/{id}/mpc-cosigner ([Change the Co-Signer Settings](https://docs.aureahub.com/docs/mpc-cosigner-put.md)) — a new HMAC secret is in the answer read once | `custody.mpc_cosigner.change` |
| POST /v1/gas-station/funding-wallets POST /v1/admin/tenants/{id}/gas-station/funding-wallets | `gas_station.funding_wallet.create_own`, `gas_station.funding_wallet.create` ([Create a Funding Wallet](https://docs.aureahub.com/docs/gas-station-funding-wallet-create.md)) |

- One's own account is never held: your own password or email change ([Update User](https://docs.aureahub.com/docs/users-update.md)) goes through at once.
- **What can only be refused is refused at once, never held** — no `202`, no request recorded: a reset of your own second factor while second factors are in use for your account (`403` `MFA_RESET_SELF`), a reset on an account you may not act on (`403`) or of a user who doesn't exist (`404`) — [Reset a User's Factor](https://docs.aureahub.com/docs/mfa-reset.md) —, a recovery key that cannot be registered (`400`), another user's password or email set directly where your tenant refuses administrators' direct edits (`403` `PASSWORD_RESET_LINK_REQUIRED` or `EMAIL_CHANGE_REQUEST_REQUIRED` — [Update User](https://docs.aureahub.com/docs/users-update.md)), and a Gas Station funding wallet on a chain the station does not serve (`400`) or one your tenant already has on that chain (`409`). A passkey reset answers the same way ([Reset a User's Passkeys](https://docs.aureahub.com/docs/passkeys-reset.md)): of your own passkeys (`403` `PASSKEY_RESET_SELF`), of an account you may not act on (`403`) or of a user who doesn't exist (`404`), refused at once.
- Policy sets and the [address book](https://docs.aureahub.com/docs/guide-address-book.md) have their own approvals, on their own routes.

## Who Decides

- **Another active administrator of your tenant whose role could take the action itself**: `owner`, `tenantadmin` or `non_signing_admin` for users, your MPC recovery key and the Gas Station's funding wallets; those and `security_admin` for credentials, secrets, webhooks, your institution callback and your MPC co-signer. Never who asked, never an auditor, never another tenant's — and never Aurea's administrators.
- The roles that read the requests: `owner`, `tenantadmin`, `non_signing_admin`, `auditor`, `security_admin` and `security_auditor`.

## Approve, Reject, Cancel

- [Approve](https://docs.aureahub.com/docs/quorum-request-approve.md) and [Reject](https://docs.aureahub.com/docs/quorum-request-reject.md) take the deciding administrator's own `password` — checked as the sign-in checks it: a wrong one answers `403` `QUORUM_PASSWORD_INVALID` and counts as a failed sign-in (`429` with `Retry-After` where Aurea throttles failed sign-ins for your tenant) — and an optional `reason` (up to 500 characters).
- **Approved, the action runs now**, as who asked, with their role as it is now: `state` becomes `executed` (`resultStatus`: the HTTP status it answered) or `failed` (`error`: why — for example `QUORUM_REQUESTER_INACTIVE` when who asked is no longer an active administrator whose role takes the action, or the action's own refusal). The approval answers the request, never the action's body.
- **Rejected** or **cancelled** — [Cancel](https://docs.aureahub.com/docs/quorum-request-cancel.md) is who asked, with an optional `reason` —: nothing runs, and the held input is deleted.
- **Expired**: a request not decided by its `expiresAt` — 24 hours after it was asked, by default — ends, and nothing runs.

## Read the Answer

- Once the action ran, who asked reads its answer with [Read the Answer](https://docs.aureahub.com/docs/quorum-request-result.md): `{ "statusCode", "body" }` — what the call would have answered, a new key or secret included (`secretAnswer` `true`).
- **Once**: the answer is deleted as it is read; a second read answers `404` `QUORUM_NO_RESULT`. Unread, it is deleted after a while — 24 hours after the action ran, by default. `resultAvailable` tells whether it waits.

## A Request's States

```text
awaiting_quorum ──approve──► approved ──► executed | failed
       ├──reject──► rejected
       ├──cancel──► cancelled
       └──expiresAt passes──► expired
observed: recorded only (log, or no other administrator could approve): it was done at once
```

- An approved action whose end was not recorded within 15 minutes is `failed` with `QUORUM_OUTCOME_UNKNOWN` (`The approved action did not record its end: it may or may not have run`): check what it changed before asking again.

## Refusals

The usual envelope, the code in `details.code`. Approve and Reject check in this order:

| Status | details.code | message |
| --- | --- | --- |
| 404 | QUORUM_REQUEST_NOT_FOUND | `Quorum request not found` — also another tenant's request |
| 403 | QUORUM_OWN_REQUEST | `The requester cannot decide its own request: another administrator of the tenant approves or rejects it` |
| 403 | QUORUM_NOT_TENANT_APPROVER | `Only another active administrator of the tenant whose role could take this action decides it` |
| 429 | — | `Too many failed password attempts. Try again in 5 minutes.`, with `details.retryAfterSeconds` and `Retry-After` — where Aurea throttles failed sign-ins for your tenant |
| 403 | QUORUM_PASSWORD_INVALID | `The password is not correct` |
| 409 | QUORUM_NOT_AWAITING | `The request is rejected` (its state), with `details.state` |
| 409 | QUORUM_EXPIRED | `The request has expired` |

- `403` `QUORUM_NOT_REQUESTER`: `Only the requester cancels a request`, `Only the requester reads the answer`.
- `404` `QUORUM_NO_RESULT`: `The answer was already read`, or `No answer is waiting for this request`.
- `400` `QUORUM_CURSOR_INVALID`: `The cursor is not one this list gave`.

---

Web version: https://docs.aureahub.com/#guide-tenant-quorum
