Admin API
Admin endpoints are authenticated via an httpOnly cookie named admin_token, issued by POST /api/v1/admin/auth/login. There is no Bearer token support — all admin requests must carry the cookie.
Response casing
Admin responses are snake_case. Request bodies are parsed with dual-accept: both snake_case (canonical) and camelCase (deprecated) keys are accepted during the transition. New integrations should use snake_case.
Authentication
Login
POST /api/v1/admin/auth/login
Content-Type: application/json
{"password": "your-admin-password"}Rate-limited to 5 attempts per 15 minutes. On success sets the admin_token cookie (httpOnly, SameSite=Strict, Secure in production, 24h max-age, path /).
Response 200:
{ "ok": true }Error 401: { "error": "Invalid password", "code": "INVALID_PASSWORD" }
Logout
POST /api/v1/admin/auth/logout
Cookie: admin_token=<jwt>Clears the admin_token cookie.
Response 200: { "ok": true }
Dashboard stats
GET /api/v1/admin/stats
Cookie: admin_token=<jwt>Response 200:
{
"today": {
"order_count": 42,
"completed_count": 38,
"total_received": "3820.500000",
"success_rate": "90.48"
},
"week": {
"order_count": 310,
"completed_count": 287,
"total_received": "28450.120000",
"success_rate": "92.58"
},
"total": {
"order_count": 1420,
"completed_count": 1305,
"total_received": "284500.000000",
"success_rate": "91.90",
"merchant_count": 5
},
"address_pool": {
"total": 120,
"free": 98,
"allocated": 22
}
}All monetary values are strings. success_rate is a fixed-precision percentage string.
Daily revenue (last 7 days)
GET /api/v1/admin/stats/daily
Cookie: admin_token=<jwt>Fixed 7-day window. There is no days query parameter.
Response 200:
[
{ "date": "2026-04-03", "total_received": "1200.000000" },
{ "date": "2026-04-04", "total_received": "980.500000" }
]Incidents (latest 10)
GET /api/v1/admin/incidents
Cookie: admin_token=<jwt>Returns the 10 most recent webhook_failed and sweep_failed events across all orders.
Response 200:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-04-08T12:34:56.000Z",
"event_type": "webhook_failed",
"payload": { "reason": "HTTP 500", "attempt": 10 },
"order_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"order_status": "callback_failed",
"external_id": "order_123",
"merchant_name": "My Shop"
}
]
}Merchants
List merchants
GET /api/v1/admin/merchants
Cookie: admin_token=<jwt>Response 200:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Shop",
"api_key_prefix": "pw_live_xxxx",
"is_active": true,
"created_at": "2026-01-01T00:00:00.000Z",
"order_count": 142
}
]
}Create merchant
POST /api/v1/admin/merchants
Cookie: admin_token=<jwt>
Content-Type: application/json
{
"name": "New Shop",
"webhook_secret": "a-long-random-string"
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | 1–255 chars |
webhook_secret | string | yes | 8–255 chars. Used to HMAC-sign outgoing webhooks for this merchant |
Dual-accept: webhookSecret is also accepted but deprecated.
Response 201:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "New Shop",
"api_key": "pw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"api_key_prefix": "pw_live_xxxx",
"webhook_secret": "a-long-random-string",
"is_active": true,
"created_at": "2026-01-01T00:00:00.000Z"
}One-time display
api_key is returned only on creation. It cannot be retrieved again — store it immediately. Only api_key_prefix is exposed on subsequent reads.
Error 400: { "error": "Invalid request body", "code": "INVALID_BODY" }
Get merchant detail
GET /api/v1/admin/merchants/:id
Cookie: admin_token=<jwt>Returns the merchant record, aggregated stats (order_count, completed_count, callback_failed_count, active_order_count, total_received, last_order_at, success_rate, incident_count), the 10 most recent orders, and the 10 most recent incidents for this merchant.
Error 404: { "error": "Merchant not found", "code": "NOT_FOUND" }
Update merchant
PUT /api/v1/admin/merchants/:id
Cookie: admin_token=<jwt>
Content-Type: application/json
{
"name": "Renamed Shop",
"is_active": false,
"webhook_secret": "new-secret-if-rotating"
}All fields optional; at least one must be present. Dual-accept: isActive / webhookSecret also accepted.
Response 200:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Renamed Shop",
"api_key_prefix": "pw_live_xxxx",
"is_active": false,
"created_at": "2026-01-01T00:00:00.000Z"
}Errors:
400 INVALID_BODY— validation failed400 EMPTY_UPDATE— no fields supplied404 NOT_FOUND— merchant id does not exist
Suspended merchants (is_active: false) cannot create new orders; existing orders continue processing.
Rotate API key
POST /api/v1/admin/merchants/:id/rotate-api-key
Cookie: admin_token=<jwt>Generates a new API key; the old key is immediately invalidated.
Response 200:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Shop",
"api_key": "pw_live_new_key_here...",
"api_key_prefix": "pw_live_newk"
}Orders (admin view)
List orders
GET /api/v1/admin/orders?page=1&limit=20&status=confirmed&merchant_id=550e8400-e29b-41d4-a716-446655440000
Cookie: admin_token=<jwt>| Query param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 20 | Items per page (max 100) |
status | — | Filter by order status |
merchant_id | — | Filter by merchant UUID (dual-accept merchantId) |
from | — | ISO timestamp lower bound on created_at |
to | — | ISO timestamp upper bound on created_at |
Response 200:
{
"data": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"external_id": "order_123",
"merchant_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_name": "My Shop",
"address": "TXxx...",
"amount_expected": "99.000000",
"amount_received": "99.000000",
"status": "completed",
"tx_hash": "def456...",
"created_at": "2026-04-08T00:00:00.000Z",
"expires_at": "2026-04-08T01:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 142,
"total_pages": 8
}
}Get order detail
GET /api/v1/admin/orders/:id
Cookie: admin_token=<jwt>Returns { order, events, transaction, settlement, meta } where meta contains system info (network, USDT contract, Tronscan base URL, sweep config). transaction and settlement are null until a payment is seen and swept respectively.
Error 404: { "error": "Order not found", "code": "NOT_FOUND" }
Retry webhook
POST /api/v1/admin/orders/:id/retry-webhook
Cookie: admin_token=<jwt>
Content-Type: application/json
{ "callback_url": "https://sandbox.example.com/hooks" }Re-enqueues webhook delivery for an order. The body is optional; when present, callback_url is a one-shot override — it is used for this retry only and is never persisted on the order. Dual-accept: callbackUrl also accepted.
Only orders in a webhook-retry-eligible state (e.g. callback_failed) can be retried.
Response 200: { "ok": true }
Errors:
400 VALIDATION_ERROR—callback_urlis not a valid http(s) URL404 NOT_FOUND— order id does not exist409 INVALID_STATE— order is not eligible for webhook retry
Retry sweep
POST /api/v1/admin/orders/:id/retry-sweep
Cookie: admin_token=<jwt>Re-enqueues the fund sweep for an order whose sweep previously failed.
Response 200: { "ok": true }
Errors:
404 NOT_FOUND— order id does not exist409 INVALID_STATE— order is not eligible for sweep retry409 SWEEP_DISABLED— sweep is not configured on this deployment
Wallet
GET /api/v1/admin/wallet
Cookie: admin_token=<jwt>Response 200:
{
"hot_wallet": {
"address": "TXxx...",
"trx_balance": "450.000000",
"is_low_balance": false
},
"cold_wallet": {
"address": "TYyy..."
},
"address_pool": {
"total": 120,
"free": 98,
"allocated": 22
},
"sweep_queue": {
"waiting": 0,
"active": 0,
"failed": 0
},
"system": {
"tron_network": "nile",
"usdt_contract": "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf",
"trongrid_url": "https://nile.trongrid.io",
"tronscan_base_url": "https://nile.tronscan.org/#",
"tx_explorer_base_url": "https://nile.tronscan.org/#/transaction/",
"address_explorer_base_url": "https://nile.tronscan.org/#/address/",
"sweep_enabled": true,
"sweep_mode": "burn",
"hot_wallet_alert_trx": 100
}
}hot_wallet is null when HOT_WALLET_KEY is not configured; cold_wallet is null when COLD_WALLET_ADDRESS is not configured.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_PASSWORD | 401 | Wrong admin password on login |
ADMIN_UNAUTHORIZED | 401 | Missing admin_token cookie |
ADMIN_TOKEN_INVALID | 401 | Cookie present but JWT invalid or expired |
INVALID_BODY | 400 | Request body failed schema validation |
INVALID_QUERY | 400 | Query params failed schema validation |
VALIDATION_ERROR | 400 | Field-level validation failure (e.g. bad override URL) |
EMPTY_UPDATE | 400 | PUT merchant called with no fields |
NOT_FOUND | 404 | Resource does not exist |
INVALID_STATE | 409 | Order not eligible for the requested admin action |
SWEEP_DISABLED | 409 | Sweep is not enabled on this deployment |