> ## Documentation Index
> Fetch the complete documentation index at: https://jephalabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Signer Lifecycle

> Pro API and CLI reference for AWS KMS signer lifecycle administration

# Signer Lifecycle Reference

Signer lifecycle administration is a Pro capability. It manages KMS-backed
signer rotation, inventory, allowance evidence, retirement gates, and the
forensic history of those actions. The shared schema, signer adapters, and
movement-time signer safety remain Free.

## API

All mutation endpoints require the `admin` role. Rotation history is readable
by `admin` and `operator`. The shared HTTP client signs each request; configure
`QTG_KEY_ID`, `QTG_HMAC_SECRET_FILE` (or `QTG_HMAC_SECRET`), and `QTG_API_URL`
before using the CLI or the expected-identity request below.

| Method | Path                                              | Purpose                                                  |
| ------ | ------------------------------------------------- | -------------------------------------------------------- |
| POST   | `/v3/admin/signers/{signer_key}/rotate`           | Validate or perform a signer rotation.                   |
| POST   | `/v3/admin/signers/{signer_key}/inventory`        | Scan native and ERC-20 balances.                         |
| POST   | `/v3/admin/signers/{signer_key}/allowances`       | Report non-zero ERC-20 allowances.                       |
| POST   | `/v3/admin/signers/{signer_key}/retire`           | Retire a deprecated signer after all gates pass.         |
| GET    | `/v3/admin/signers/{signer_key}/rotation-history` | Read current state and chronological lifecycle evidence. |

Rotation accepts the replacement signer and reason. `--drill` returns
`outcome: "drilled"`, persists drill evidence, and does not change signer
bindings; a state-changing rotation requires the impact acknowledgement.
Inventory persists evidence by default, and `--no-persist` is the explicitly
read-only form. The allowances command persists audit/snapshot evidence.

Retirement requires the signer to be deprecated, balances at or below the
configured token-unit dust ceiling, no nonterminal node reference, and an
allowance acknowledgement only when non-zero allowances deliberately remain.
There is no force flag. A retirement gate denial is HTTP 409 with
`outcome: "denied"` in the error body; the CLI raises `QtgHttpError`, exits
nonzero, and does not print a successful 200 response.

## CLI

The `qtg signer` group is the HTTP-backed CLI for the lifecycle endpoints:

```bash theme={null}
qtg signer rotate <old_signer_key> --replace-with <new_signer_key> --reason "scheduled rotation" --drill
qtg signer inventory <signer_key> --chains ethereum,arbitrum --no-persist
qtg signer allowances <signer_key> --chains ethereum,arbitrum
qtg signer retire <signer_key> --dust-threshold-usd 1.0
qtg signer history <signer_key>
```

Use `--ack-allowances` only for a documented exceptional residual allowance.
`dust_threshold_usd` is currently a raw token-unit ceiling, not a USD
conversion; drain to zero in production.

## Enrollment and identity gate

Add replacement KMS signers through `MG_EXTRA_SIGNERS_JSON`, then restart the
Pro application so its runtime registration and `sync_registry_to_db()`
materialization run. Never enroll a runtime signer with direct SQL. Verify the
active replacement through `GET /v3/signers`, then use the admin-only
`PUT /v3/signers/{signer_key}/expected-identity` endpoint to pin the identity.

For every drill and live rotation, the server automatically resolves the
replacement signer's fresh runtime identity and compares its canonical value
with the persisted `expected_signer_address`. This signer I/O occurs outside the
lifecycle database transaction. The locked lifecycle path then revalidates the
operator pin, runtime signer continuity, and canonical identity equality before
impact, audit, rotation-event, metadata, or authority-remap work.

`observed_signer_address` is diagnostic and non-authoritative. It and
`identity_state: "match"` remain useful roster and health-refresh evidence, but
neither can authorize a drill or live rotation. The fresh manual probe in the
runbook remains defense in depth; the CLI does not compare identities locally.

Identity denials are HTTP 409 responses containing only a stable error code,
without addresses or backend exception text:

| Error                                 | Meaning                                                                |
| ------------------------------------- | ---------------------------------------------------------------------- |
| `signer_expected_identity_missing`    | no operator pin exists                                                 |
| `signer_expected_identity_invalid`    | the persisted pin is malformed or zero                                 |
| `signer_runtime_identity_unavailable` | fresh identity cannot be resolved or runtime signer continuity is lost |
| `signer_runtime_identity_invalid`     | the fresh runtime value is malformed or zero                           |
| `signer_identity_mismatch`            | canonical expected and fresh identities differ                         |

## Compact request and response contracts

All bodies below require an admin HMAC key. Malformed bodies receive FastAPI
422 validation responses; the endpoint-specific errors below are the common
current contracts. FastAPI serializes each `HTTPException` body beneath the
top-level `detail` key, and `QtgHttpError.body` sees that full response JSON.

### Rotate

```json theme={null}
{
  "replace_with": "<NEW_SIGNER_KEY>",
  "reason": "scheduled rotation <YYYY-QN>",
  "drill": true,
  "ack_impact": true
}
```

With `drill: true`, success returns `outcome: "drilled"`, an impact report,
`audit_event_id`, and `signer_rotation_event_id`; it persists drill evidence
without changing signer bindings. With `drill: false` and
`ack_impact: true`, success returns `outcome: "changed"` with the same
evidence identifiers and performs the atomic rotation. A non-drill request
without acknowledgement returns 400 with
`{"detail": {"error": "ack_impact_required"}}`; failed preconditions return
409 with `{"detail": {"error": "validation_failed", "cause": "<DETAIL>"}}`.
A replacement health failure returns 409 with
`{"detail": {"error": "new_signer_health_check_failed"}}`. Identity failures
return one of the following code-only bodies:

```json theme={null}
{"detail": {"error": "signer_expected_identity_missing"}}
{"detail": {"error": "signer_expected_identity_invalid"}}
{"detail": {"error": "signer_runtime_identity_unavailable"}}
{"detail": {"error": "signer_runtime_identity_invalid"}}
{"detail": {"error": "signer_identity_mismatch"}}
```

### Inventory

```json theme={null}
{
  "chains": ["ethereum"],
  "persist": false
}
```

Success returns signer/address/check time, balances, rotation state, and
`audit_event_id` plus `signer_rotation_event_id` when persistence is
enabled. `persist: false` is the read-only form and leaves those evidence IDs
null; an unknown signer returns 404 with
`{"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}`.

### Allowances

```json theme={null}
{
  "chains": ["ethereum"]
}
```

Success returns only non-zero allowances and always persists its audit/snapshot
evidence, including `audit_event_id` and `signer_rotation_event_id`.
`unsupported_spender_not_checked` signals a partial scan where configured
spender coverage was unavailable. An unknown signer returns 404 with
`{"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}`.

### Retire

```json theme={null}
{
  "ack_allowances": false,
  "dust_threshold_usd": 1.0
}
```

Success returns `outcome: "retired"` with `audit_event_id` and
`signer_rotation_event_id`. A hard gate denial returns HTTP 409 with
`{"detail": {"outcome": "denied", "failed_gate": "<GATE>"}}`, plus the
denial reason and nonterminal nodes when relevant; an unknown signer returns
404 with `{"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}`.

## Secret-safe evidence

Record signer keys, addresses, outcomes, audit IDs, and sanitized snapshots as
needed. Do not record KMS credentials, HMAC secrets, `MG_EXTRA_SIGNERS_JSON`,
raw provider responses, or secret-bearing URLs.

For the complete operator procedure, evidence expectations, exception handling,
and post-cutover AWS KMS deletion, use the [KMS signer rotation runbook](/pro/runbooks/kms-signer-rotation).
