Accounts & delivery
Accounts
The Accounts page is the master list of every VPN credential the panel manages, across every protocol. The detail page (/accounts/<id>) is a single-account console: settings, node assignment, live traffic, connected devices and generated client configs for every node the account is scoped to.
- Supported protocols:
ssh,vmess,vless,trojan,zivpn. Creation flows also accept the unifiedxrayalias, which is always provisioned as VLESS underneath. - An Xray identity can own up to three sibling rows (VLESS / VMess / Trojan) that share one
uuid, username and Telegram id. The list collapses them into one card, so switching protocol never loses history. - Usernames are unique per plane — SSH, the Xray family and ZIVPN each have their own namespace. An SSH and a VLESS account may share a username.
Data model
Every account field#
| Field | Meaning |
|---|---|
| id | Row id, <protocol>-<uuid prefix> e.g. vless-73967b74. |
| accountUuid | Separate logical-account handle used in panel URLs; either id or accountUuid resolves the detail page. |
| protocol | ssh | vmess | vless | trojan | zivpn. |
| username | 1–32 chars: letters, digits, dot, underscore, hyphen. Unique per plane. |
| password | SSH/ZIVPN secret; Trojan stores its client secret here. Auto-generated (12 hex) when omitted. |
| uuid | Xray client id. Sibling protocol rows share it. |
| createdAt / expiresAt | ISO timestamps. Create accepts durationDays or durationHours; default 30 days. |
| ipLimit | Max simultaneous devices; 0 = unlimited, default 2. |
| speedUpKbps / speedDnKbps | Per-account throttle in kbps, 0 = unlimited. Enforced with tc plus Xray sockopt.mark. |
| quotaGb | Lifetime data cap in GB, 0 = unlimited. |
| usedBytes / localUsedBytes | Measured usage; usedBytes is aggregated across the whole fleet, localUsedBytes is panel-host only. |
| status | active | trial | suspended | locked | pending | expired. pending means the last provisioning attempt failed — see note. |
| telegramId / planId | Links the account to a bot user and to a plan. |
| note | Free text, also used as a machine flag: quota_locked, grace_until:<iso>, daily_quota_locked:<iso>. |
| blockTorrent / unlockAll | Per-account torrent blocking and a bypass override for all blocking rules. |
| outboundId | Pin this account's egress to a named outbound instead of the node IP. |
| resetIntervalDays / resetEnabled / lastResetAt | Recurring automatic quota reset. |
| dailyQuotaGb / dailyQuotaEnabled | Daily cap layered on top of the lifetime quota. |
| transports | Xray only: subset of ws, httpupgrade, xhttp, grpc. Omitted on create means all four. |
| xhttpMode | Only used when xhttp is enabled: auto, stream-up, stream-one, packet-up. The node narrows this to a single mode, preferring stream-up. |
| cdnIds / cdnOnly | CDN endpoints this account may use; cdnOnly hides the direct config. Auto-set to false if cdnIds becomes empty. |
| subToken / showRaw | Public subscription token, and whether the subscription page exposes the raw protocol link. |
| cfTlsPort / cfNtlsPort | Preferred Cloudflare-friendly port. Values outside the allowed sets are silently zeroed. |
| flow | VLESS flow control, e.g. empty or xtls-rprx-vision. |
| online | Computed per request: count of live IPs for this account across the fleet. |
- 1Open
Accounts → New account. - 2Choose the protocol. Picking the unified Xray option lets you tick VLESS, VMess and Trojan; each ticked protocol becomes a sibling row sharing one UUID.
- 3Enter a username. Leave the password blank for SSH/ZIVPN to get a generated 12-character secret.
- 4Set the limits: expiry (or duration in days/hours), data quota in GB, device limit (concurrent IPs) and optional up/down speed caps in kbps.
- 5Optionally attach a plan — its quota, device limit, speed, duration and CDN defaults pre-fill the form.
- 6For Xray protocols choose the allowed transports and, if xHTTP is enabled, the xHTTP mode.
- 7Choose the nodes this account may use. Leaving the scope untouched means “allowed on every node”.
- 8Save. The row is inserted, then provisioning is pushed to the fleet. If provisioning throws, the row survives with
status=pendingand the error innote— fix the cause and press Repair. - 9Hand off the credentials: copy the config, the QR code or the subscription URL, or press Send via Telegram if the account has a Telegram id.
What provisioning actually does
- Validates the protocol, rejects ZIVPN if the ZIVPN tunnel is not installed on the target host, and rejects reserved SSH usernames.
- For SSH: derives the real Linux login, cleans orphaned grvpn-* users, and refuses with 409 on a genuine system-user conflict.
- Generates the password/UUID, derives the row id and mints a fresh accountUuid.
- Normalises transports and xHTTP mode, unions the plan's CDN ids, snaps the Cloudflare ports to the allowed sets.
- Pushes the account to the fleet: Xray client add + config test + restart, SSH useradd with expiry, or ZIVPN password-list reconcile.
- Rebases the Xray per-email traffic baseline for new non-SSH accounts so a reused username never inherits the previous holder's counters.
- Re-applies the fleet torrent-block and unlock-all policies, and writes a coalesced audit-log entry for sibling creations made within 20 seconds.
- Rename: the UUID and accountUuid are preserved, so history and usage survive. Session state is moved to the new username, then the account is revoked under the old identity and re-provisioned under the new one.
- Protocol switch is allowed only inside the Xray family (VLESS ↔ VMess ↔ Trojan). Switching to Trojan mints a password if none exists; switching to VLESS/VMess mints a UUID if none exists. Both revoke and re-provision.
- Transport-only change re-syncs the Xray inbound client lists without touching the protocol.
- Quota edits auto-unsuspend accounts the system suspended (note
quota_lockedor usage ≥ old quota) when the new quota is above current usage. Manual suspensions are untouched.
Note the new secret
| Action | What happens |
|---|---|
| Delete | Row disappears optimistically, the server deletes it, then a best-effort purge scrubs the account from every node's live config and reports nodes that failed. |
| Extend | Adds N days (default 30) and re-provisions SSH accounts so the OS expiry moves too. |
| Lock / Unlock | Sets status=locked or status=active, clears note on unlock and re-provisions SSH. |
| Repair | Re-runs provisioning for one account. The fix for “invalid username or password” after a node reboot or reimage. |
| Reset traffic | Zeroes the panel ledger and pushes a reset watermark to every node, so the fleet stops enforcing the old over-quota total. |
| Copy subscription URL | Fetches the account subscription and copies the public URL. |
| Rotate subscription | Issues a new subToken, invalidating the previous public URL. |
| Cancel with refund | Cancels a prepaid account and optionally refunds to wallet, showing which funding source would be refunded. |
| Send via Telegram | Re-delivers connection details to the linked Telegram user. |
| Export / Import CSV | Exports every account, or imports rows from CSV (bad rows are skipped silently). |
The stats strip at the top counts Total, Active, Disabled, Expired, Grace (7d) and Limited. Grace and Limited are derived from the note markers grace_until: and daily_quota_locked:.
- 1Accounts → Bulk create.
- 2Pick the kind: SSH, Xray or ZIVPN. For Xray, tick the protocols each user should get.
- 3Pick a count (presets 10 / 50 / 100 / 250 / 500 / 1000; the server caps a job at 2000).
- 4Optionally pick a plan to auto-fill device limit, quota, speed and duration.
- 5Set a username prefix (sanitised, max 20 chars). Final usernames are
prefix-XXXXXXwith an alphabet that excludes ambiguous characters. - 6Choose the password mode: random per account, or fixed for the whole batch (SSH/ZIVPN only).
- 7Pick the target nodes. Only nodes with that protocol's service installed are selectable.
- 8Generate. The dialog polls the job and shows live progress until it reports done; each created account gets the chosen node scope applied.
Job history
Tabs: Overview, Settings, Nodes, Devices, Traffic, Configs. The active tab is remembered per account.
- Save
- Persists node scope, the field patch and the outbound assignment in one click, then pushes an instant sync to the nodes.
- Node scope
- Two-state: an untouched scope means “allow on every node”. Once you pick specific nodes the scope becomes restricted — even if that list later empties, which the UI warns about rather than silently reverting to all nodes.
- Usage math
- usedBytes is aggregated across every node that measured the account: 50 GB on node A plus 50 GB on node B is 100 GB.
- Online detection
- Authoritative node-reported presence, not byte activity, so a sibling protocol row with the same username never shows a false green dot.
- Devices
- Merges the panel's active-IP table with node-reported presence inside a 20-second freshness window matched to the heartbeat cadence.
- Configs
- One client config block per node the account is scoped to, falling back to the single-host profile only when no nodes are registered.
- Traffic
- Per-account history, polled while the page is open.
- Lifetime quota (
quotaGb) suspends the account when exceeded and marksnote=quota_locked. Raising the quota above current usage auto-reactivates it. - Daily quota (
dailyQuotaGb) limits per day and marksdaily_quota_locked:<iso>until the next window. - Recurring reset (
resetEnabled,resetIntervalDays) zeroes usage on a cycle — the “30 GB per month, renewing” model. - Grace is expressed with
grace_until:<iso>in the note, and the list counts accounts in grace for the next 7 days.
Always reset traffic through the panel button
GET /api/accounts?protocol=<p> list, with computed online counts
GET /api/accounts/{id} single row
GET /api/accounts/{id}/detail usage, host, activeIps, connectionProfiles
GET /api/accounts/{id}/traffic traffic history
GET /api/accounts/{id}/config client config text
GET /api/accounts/{id}/subscription subscription payload + public URL
POST /api/accounts/{id}/subscription/rotate
GET /api/accounts/{id}/subscription/hits?limit=
POST /api/accounts create
PATCH /api/accounts/{id} partial update
DELETE /api/accounts/{id} delete + queue node cleanup
POST /api/accounts/{id}/repair re-run provisioning
POST /api/accounts/{id}/cancel-prepaid {refund, reason}
POST /api/accounts/{id}/telegram re-send details over the bot
PATCH /api/accounts/{id}/outbound pin/clear the egress outbound
POST /api/accounts/{id}/rotate-token
POST /api/accounts/bulk {action: extend|delete|lock|unlock, ids[], days?}
POST /api/accounts/bulk-create -> {jobId, total}
GET /api/accounts/bulk-create/{jobId} progress
GET /api/accounts/export CSV
POST /api/accounts/import {csv}
GET /api/accounts/revoke-queue {pending}Every write invalidates the panel’s short GET cache, so the list reflects changes on the next poll.
- A 409 “username already exists” is per plane, not global.
- Deleting removes the DB row instantly but node cleanup is queued and drained one item at a time. Check
/api/accounts/revoke-queue; on a very large fleet credentials can work for a few more seconds. cdnOnly=truewith an emptiedcdnIdslist is auto-corrected to false, unless the same patch explicitly re-enables it.cfTlsPort/cfNtlsPortoutside the allowed sets reset to 0 with no error.- Raising a quota never unlocks a manually locked account.
- ZIVPN account creation returns 400 until the ZIVPN UDP tunnel is installed on the target host.
