---
title: Admin API
tags: [api, admin, reference]
---

# Admin API

> **Last updated:** 2026-05-30 — break-glass endpoints added, scanner/status auth fixed, reload/probe routes now implemented, confirmation threshold history implemented, resolver role added

There is no single `/api/admin` namespace — admin-only endpoints are scattered across the service routers. This page catalogs them in one place. All require `Bearer JWT` with `req.user.role === 'admin'` unless explicitly noted otherwise. The two enforcement patterns are:

- Middleware: `authorizeRoles('admin')` after `authenticateToken` (used by the dispute, data-cleanup, blog routers).
- Inline check inside the handler: `if (req.user.role !== 'admin') return 403` (used by user, points, payment routes).

> [!note] Resolver role
> The `resolver` role was added (commit `fce8a19`). Resolvers have access to the dispute-triage endpoints (`assign`, `status`, `resolve`, `statistics`) only. All other admin endpoints remain `admin`-only.

## User management

See full descriptions in [[User API]].

> **Path note:** The frontend uses `/api/users/admin/*` (plural — legacy `userRoutes`). The singular `/api/user/admin/*` group (new `userController`) **is also mounted** (`app.ts`). Since backend `14c231e` (v2.8.50) the plural group delegates `toggle-status` and `dependencies` to the new controller so every frontend call routes. Prefer `/api/users/admin/*` for user-management calls.

| Endpoint | Action |
| --- | --- |
| `POST /api/users/admin/create` | Create user with role/status |
| `DELETE /api/users/admin/:userId` | Soft delete user — sets `status='deleted'` (admins cannot delete each other) |
| `PATCH /api/users/admin/:userId/status` | Activate / suspend |
| `PATCH /api/users/admin/:userId/toggle-status` | Flip active flag |
| `PATCH /api/users/admin/:userId/role` | Change role |
| `GET /api/users/admin/list` | Paginated directory + stats |
| `GET /api/users/admin/:userId/dependencies` | Pre-delete dependency check |
| `GET /api/users/admin/stats` | Aggregate user analytics |
| `GET /api/users/admin/:userId` | Full user detail (admin view) |
| `PUT /api/users/admin/:userId` | Mass update user |
| `PUT /api/users/admin/update/:email` | Mass update by email |
| `PATCH /api/users/admin/:userId/password` | Force password reset (wipes refresh tokens) |
| `POST /api/users/admin/:userId/resend-verification` | Resend verification email (legacy route — uses 8-digit codes) |

> **Verification code length:** The endpoint `POST /api/users/admin/:userId/resend-verification` is served by the legacy userRoutes and generates **8-digit** codes. The new userController generates 6-digit codes and is reached via a different path. Both coexist; the legacy route takes precedence for this path.

**✅ FIXED (frontend `d7a2a86` / `6fe1328`, v2.8.50–51):** the old PUT-verb and status-value mismatches are gone — `updateUserStatus` sends `PATCH` with `{ isActive: boolean }` (the field the legacy plural route reads).

**Soft delete + email release (backend `378f8f6`, v2.8.51):** `DELETE /api/users/admin/:userId` soft-deletes (sets `status='deleted'`) **and releases the email** (renamed to `deleted_<legacyId>_<email>`) so the address can be reused. Create/register also lazily free emails still held by accounts deleted before this fix. Soft-deleted users are excluded from the admin list and all stats (backend `14c231e`).

## Listing / marketplace moderation

See [[Marketplace API]]. Admins can use most marketplace endpoints with elevated privileges (e.g. delete any purchase request, override offer status). Specific admin-only actions:

| Endpoint | Action |
| --- | --- |
| `PUT /api/marketplace/offers/:id/status` | Direct status mutation including admin overrides |
| `POST /api/marketplace/purchase-requests/:id/release-payment` | Force escrow release |
| `PATCH /api/marketplace/purchase-requests/:id/status` (any → any) | Override request state machine |

Template approval is implicit: admins use the same template CRUD endpoints with override privileges.

## Dispute mediation

See [[Dispute API]].

| Endpoint | Action |
| --- | --- |
| `POST /api/disputes/:id/assign` | Assign moderator |
| `PATCH /api/disputes/:id/status` | Update status |
| `POST /api/disputes/:id/resolve` | Final decision (buyer / seller / split) |
| `GET /api/disputes/statistics` | Admin dashboard data |

## Manual payment operations

See [[Payment API]].

| Endpoint | Action |
| --- | --- |
| `POST /api/payment/payments/cleanup-pending` | Delete stale pending payments |
| `POST /api/payment/payments/:id/fetch-tx` | Re-query chain for missing tx hash |
| `POST /api/payment/payments/auto-fetch-missing` | Batch tx-hash backfill |
| `POST /api/payment/:id/release` | Build escrow-release tx |
| `POST /api/payment/:id/release/confirm` | Confirm release tx hash |
| `POST /api/payment/:id/refund` | Build refund tx |
| `POST /api/payment/:id/refund/confirm` | Confirm refund tx hash |
| `POST /api/payment/shkeeper/payout` | Create payout task |
| `GET /api/payment/shkeeper/webhook-stats` | Webhook telemetry |
| `POST /api/payment/decentralized/admin-payout` | Direct admin-wallet payout |

**⚠️ Path correction:** Release/refund routes do **not** include a `/shkeeper/` segment. The correct paths are `/api/payment/:id/release`, `/api/payment/:id/release/confirm`, etc. (Previously documented incorrectly as `/api/payment/shkeeper/:id/…`.)

## Derived destinations & sweep

Frontend page: `/dashboard/admin/derived-destinations`. Backend registers 7 endpoints under `/api/payment/derived-destinations/*` with admin auth.

| Endpoint | Action |
| --- | --- |
| `GET /api/payment/derived-destinations` | List all derived destination addresses |
| `POST /api/payment/derived-destinations/sweep/trigger` | Trigger a sweep across all destinations |
| `POST /api/payment/derived-destinations/sweep/trigger/:id` | Trigger sweep for a single destination |
| `GET /api/payment/derived-destinations/sweep/cron/status` | Get sweep cron job status |
| `POST /api/payment/derived-destinations/sweep/cron/start` | Start the sweep cron job |
| `POST /api/payment/derived-destinations/sweep/cron/stop` | Stop the sweep cron job |
| `GET /api/payment/derived-destinations/sweep/history` | Sweep history log |

> Frontend action functions: `getDerivedDestinations`, `triggerSweep`, `triggerSingleSweep`, `getSweepCronStatus`, `startSweepCron`, `stopSweepCron`.

## Points (admin)

See [[Points API]].

| Endpoint | Action |
| --- | --- |
| `POST /api/points/admin/add` | Manually grant / deduct points for a user |

## Data cleanup

Router: [`backend/src/services/admin/dataCleanupRoutes.ts`](../../backend/src/services/admin/dataCleanupRoutes.ts). Mounted under `/api/admin/cleanup/*`. The router applies `authenticateToken` + `authorizeRoles('admin')` to every endpoint.

### GET /api/admin/cleanup/stats

**Description:** Per-collection document counts and sizes.
**Response 200:** `{ success, data: { collections: [{ name, count, sizeBytes }] } }`

### GET /api/admin/cleanup/collections

**Description:** List collections that can be cleaned and the supported flags.
**Response 200:** `{ success, data: { collections, options } }`

### POST /api/admin/cleanup/clean

**Description:** Bulk delete records. Defaults to `dryRun: true` and `keepAdmins: true`.
**Request body:**
```ts
{
  collections?: string[];        // default ["all"]
  dryRun?: boolean;              // default true
  keepAdmins?: boolean;          // default true
  olderThanDays?: number;        // optional age filter
  confirm?: "DELETE_ALL_DATA";   // required for actual deletion
}
```
**Response 200:** `{ success, data: { deletedCounts, dryRun } }`

### DELETE /api/admin/cleanup/user/:userId

**Description:** Cascade-delete all data for a specific user (GDPR). Requires `?confirm=DELETE_USER_DATA` for real execution.
**Query params:** `dryRun=true|false`, `confirm=DELETE_USER_DATA`

### POST /api/admin/cleanup/temp

**Description:** Purge temporary data older than N hours (verification codes, file temp uploads).
**Request body:** `{ olderThanHours?: number }` (default 24)

### POST /api/admin/cleanup/seed-templates

**Description:** Re-runs the request templates seeder (production safe; idempotent).

### POST /api/admin/cleanup/seed-all

**Description:** Seeds users, addresses, and templates in dependency order. Used to bootstrap a fresh staging environment.

## Scanner / monitoring

### GET /api/admin/scanner/status

**Description:** Returns the current state of the AMN Pay Scanner. Proxies to `AMN_SCANNER_URL/scanner/status`.
**Auth required:** Bearer JWT (`admin`) — `authenticateToken` + `authorizeRoles('admin')` were added in commit `1d881c5`. The previously documented unauthenticated access gap (ISSUE-006) is closed.

### POST /api/admin/scanner/webhooks/retry

**Description:** Trigger a retry of failed/pending scanner webhooks.
**Auth required:** Bearer JWT (`admin`)
**Request body:** `{ intentId?: string }` — omit to retry all pending.

## Settings

### AML settings

> **⚠️ RUNTIME-ONLY PERSISTENCE:** `PATCH /api/admin/settings/aml` updates `process.env` at runtime only. Changes are **lost on server restart**. There is no frontend page for these endpoints.

| Endpoint | Auth | Action |
| --- | --- | --- |
| `GET /api/admin/settings/aml` | admin | Read current AML settings |
| `PATCH /api/admin/settings/aml` | admin | Update AML settings (runtime only — not persisted to disk or DB) |

**AML providers available:**

- **Chainalysis** — cloud API provider (requires `CHAINALYSIS_API_KEY`). Enabled via `AML_PROVIDER=chainalysis`.
- **OFAC SDN local** — downloads the US Treasury SDN XML list once per 24 hours and checks addresses locally. No API key required. Enabled via `AML_PROVIDER=ofac`. Added in commit `31343d1` (Task #10). List is fetched from `OFAC_SDN_URL` (defaults to `https://www.treasury.gov/ofac/downloads/sdn.xml`).

The active provider is selected at startup via `AML_PROVIDER`. `PATCH /api/admin/settings/aml` can switch the provider at runtime but the change is not persisted.

### Confirmation thresholds

Frontend page exists. Endpoints require admin auth.

| Endpoint | Action |
| --- | --- |
| `GET /api/admin/settings/confirmation-thresholds` | Get current confirmation thresholds for all chains |
| `PATCH /api/admin/settings/confirmation-thresholds/:chainId` | Update threshold for a specific chain |
| `GET /api/admin/settings/confirmation-thresholds/history` | Last 50 threshold change events (populated with `changedBy` user email/name) |

> **History route:** `GET /api/admin/settings/confirmation-thresholds/history` is now implemented (commit `27fb15a`). It reads from the `ConfigSettingHistory` collection, keyed as `confirmation_threshold:<chainId>`.

### Break-glass (Trezor bypass)

Three endpoints manage the break-glass mode, which disables the Trezor safekeeping requirement for escrow release/refund for up to 1 hour. All changes fire a Telegram alert.

| Endpoint | Action |
| --- | --- |
| `GET /api/admin/settings/break-glass` | Read current break-glass status (active, expiresAt, activatedBy) |
| `POST /api/admin/settings/break-glass` | Activate break-glass for 1 hour |
| `DELETE /api/admin/settings/break-glass` | Cancel break-glass before it expires |

> [!warning] In-memory state
> Break-glass state is stored in-memory only (`breakGlassRoutes.ts`). A server restart always clears it, which is intentional. The `isBreakGlassActive()` helper is exported and consumed by the Trezor safekeeping middleware.

## Payments awaiting confirmation

Frontend page exists.

| Endpoint | Auth | Action |
| --- | --- | --- |
| `GET /api/admin/payments/awaiting-confirmation` | admin | List payments pending blockchain confirmation |

## RN network registry

Frontend page exists.

| Endpoint | Auth | Action |
| --- | --- | --- |
| `GET /api/admin/rn/networks` | admin | List all registered RN networks |
| `POST /api/admin/rn/networks/reload` | admin | Reload chain + token registries from disk (no restart needed) |
| `POST /api/admin/rn/networks/probe/:chainId` | admin | On-demand on-chain probe: RPC reachability, proxy bytecode, dummy-call validity |

> All three routes are implemented (commit `5681abf`). Previous docs listed reload and probe as not implemented.

## Blog admin

Backend registers 5 blog admin endpoints, all guarded by `authorizeRoles('admin')`. Frontend has action functions calling each.

| Endpoint | Action |
| --- | --- |
| `GET /api/blog/admin/posts` | List all blog posts (admin view, includes drafts) |
| `POST /api/blog/posts` | Create a new blog post |
| `GET /api/blog/admin/posts/:id` | Get a single blog post (admin view) |
| `PUT /api/blog/posts/:id` | Update a blog post |
| `DELETE /api/blog/posts/:id` | Delete a blog post |

## Analytics

There is no dedicated analytics router. Admin dashboards stitch together:

- `GET /api/users/admin/stats` (user metrics)
- `GET /api/payment/stats` (payment aggregates — note: `'completed'` status is excluded from `successfulPayments` count)
- `GET /api/disputes/statistics` (dispute KPIs)
- `GET /api/admin/cleanup/stats` (collection sizes)
- `GET /api/payment/shkeeper/webhook-stats` (provider health)
- `GET /api/payment/shkeeper/wallet-monitor/status` (chain monitor)

## Related

- [[Admin Console Architecture]]
- [[Authorization Model]]
- [[Error Codes]]
