Base URLs
https://admin.vsl247.com
https://admin2.vsl247.com
Authorization
Protected endpoints authenticate a portal account before checking its role.
- Recommended: create an access token at
POST /api/admin/token, then sendAuthorization: Bearer ACCESS_TOKEN. - Compatibility:
/api/v1routes also accept HTTP Basic authentication with the portal username and password. - The API token is valid for up to 30 days. Generate a new token after it expires.
- The browser login session and API authentication are separate; do not send the browser session cookie as an API credential.
Quick start
1. Create a token. 2. Copy access_token. 3. Send it in the Authorization header.
curl -X POST 'https://admin.vsl247.com/api/admin/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=admin_username&password=admin_password'
curl -H 'Authorization: Bearer ACCESS_TOKEN' \
https://admin.vsl247.com/api/users
Use a superadmin account for superadmin-only endpoints. A normal admin token is authenticated successfully but receives 403 Forbidden when its role is not allowed.
Authentication
Create a token and inspect the authenticated portal admin.
POST /api/admin/token Create a PasarGuard-style access token.
Authenticate with the portal username and password. The endpoint accepts either form data or a JSON object.
POST /api/admin/token
Content-Type: application/x-www-form-urlencoded
username=admin_username&password=admin_password
POST /api/admin/token
Content-Type: application/json
{
"username": "admin_username",
"password": "admin_password"
}
{
"access_token": "...",
"token_type": "bearer"
}
curl -H 'Authorization: Bearer ACCESS_TOKEN' \
'https://admin.vsl247.com/api/v1/me'
- Use the exact token returned in
access_token; do not include quotation marks around it. - Invalid credentials return
401 Unauthorized. - An inactive normal admin cannot receive an API token and returns
403 Forbidden.
GET /api/admin Return the authenticated admin.
{
"username": "admin_username",
"is_sudo": false,
"telegram_id": null,
"discord_webhook": null,
"users_usage": 0
}
HTTP Error responses Common authentication, authorization, and validation responses.
400 Bad Request— the request is authenticated but a body, query parameter, or value is invalid.401 Unauthorized— credentials are missing, invalid, or the Bearer token has expired. The response includes aWWW-Authenticateheader.403 Forbidden— the credentials identify a valid account, but that account's role is not allowed to use the endpoint.- Errors use the JSON shape
{"ok": false, "error": "..."}on portal-native/api/v1routes.
Users
Manage only the clients owned by the authenticated portal admin.
GET /api/users List owned clients with filters and pagination.
GET /api/users?offset=0&limit=10&username=client&status=active
{
"users": [],
"total": 0
}
POST /api/user Create a user using the admin's remaining quota.
{
"username": "client_username",
"data_limit": 21474836480,
"duration_days": 30,
"status": "active",
"note": "optional"
}
{
"username": "client_username",
"status": "active",
"used_traffic": 0,
"data_limit": 21474836480,
"subscription_url": "https://sub.vsl247.com/sub/..."
}
GET /api/user/<username> Return one owned client.
{
"username": "client_username",
"status": "active",
"used_traffic": 1073741824,
"data_limit": 21474836480,
"expire": 1780000000,
"subscription_url": "https://sub.vsl247.com/sub/..."
}
PUT /api/user/<username> Increase quota or update expiry and status.
{
"data_limit": 32212254720,
"expire": 1780000000,
"status": "active"
}
{
"username": "client_username",
"status": "active",
"data_limit": 32212254720,
"expire": 1780000000
}
DELETE /api/user/<username> Delete an owned client through the panel API.
{
"detail": "User successfully deleted"
}
POST /api/user/<username>/reset Reset the client's used traffic.
POST /api/user/client_username/reset
Authorization: Bearer ACCESS_TOKEN
{
"username": "client_username",
"status": "active",
"used_traffic": 0,
"data_limit": 21474836480
}
POST /api/user/<username>/revoke_sub Revoke and regenerate the subscription link.
POST /api/user/client_username/revoke_sub
Authorization: Bearer ACCESS_TOKEN
{
"username": "client_username",
"status": "active",
"subscription_url": "https://sub.vsl247.com/sub/NEW_TOKEN"
}
Activity
Inspect subscription fetches and traffic usage.
GET /api/user/<username>/sub_update Return subscription fetch history.
GET /api/user/client_username/sub_update?offset=0&limit=10
{
"updates": [
{
"created_at": "2026-05-29T10:25:55Z",
"user_agent": "v2rayNG/1.8.5"
}
]
}
GET /api/user/<username>/usage Return usage records for one owned client.
GET /api/user/client_username/usage?start=2026-06-01T00:00:00Z&end=2026-06-06T00:00:00Z
{
"username": "client_username",
"usages": [
{
"node_id": 6,
"used_traffic": 268435456,
"created_at": "2026-06-05T12:00:00Z"
}
]
}
GET /api/users/usage Return usage records for all owned clients.
GET /api/users/usage?start=2026-06-01T00:00:00Z&end=2026-06-06T00:00:00Z
{
"usages": [
{
"username": "client_username",
"node_id": 6,
"used_traffic": 268435456,
"created_at": "2026-06-05T12:00:00Z"
}
]
}
Superadmin
Reporting and admin-management endpoints restricted to authenticated superadmins.
GET /api/v1/superadmin/sales Return each admin's GB sales for a time range.
superadmin
Normal admin accounts are denied.
Query parameters
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
start |
string or integer |
Required | Beginning of the range, inclusive. Accepts an ISO 8601 date/time or a Unix timestamp in seconds or milliseconds. | 2026-08-01T00:00:00+03:30 |
end |
string or integer |
Required | End of the range, exclusive. It must be later than start. |
2026-08-02T00:00:00+03:30 |
Asia/Tehran. For predictable integrations, send an explicit offset or use UTC with Z. The interval includes start and excludes end.
Request examples
curl --get 'https://admin.vsl247.com/api/v1/superadmin/sales' \
-H 'Authorization: Bearer SUPERADMIN_ACCESS_TOKEN' \
--data-urlencode 'start=2026-08-01T00:00:00+03:30' \
--data-urlencode 'end=2026-08-02T00:00:00+03:30'
--data-urlencode safely encodes the + in the timezone offset.
curl --get 'https://admin.vsl247.com/api/v1/superadmin/sales' \
--user 'superadmin_username:superadmin_password' \
--data-urlencode 'start=2026-08-01' \
--data-urlencode 'end=2026-08-02'
Basic authentication is supported for /api/v1 routes. The account must still be a superadmin.
{
"ok": true,
"range": {
"start": "2026-07-31T20:30:00Z",
"end": "2026-08-01T20:30:00Z",
"start_timestamp": 1785529800,
"end_timestamp": 1785616200,
"end_exclusive": true,
"naive_input_timezone": "Asia/Tehran"
},
"totals": {
"sales_gb": 125.5,
"new_client_gb": 100.0,
"added_gb": 25.5,
"new_clients": 8,
"add_operations": 3,
"unresolved_events": 0
},
"admins": [
{
"admin_id": 42,
"username": "portal_admin",
"is_active": true,
"sales_gb": 55.5,
"new_client_gb": 40.0,
"added_gb": 15.5,
"new_clients": 3,
"add_operations": 2,
"unresolved_events": 0
}
]
}
Response fields
| Field | Type | Description |
|---|---|---|
range | object | Normalized UTC range and the Unix-second boundaries used by the report. |
range.start, range.end | string | Normalized UTC ISO 8601 values. start is inclusive; end is exclusive. |
range.start_timestamp, range.end_timestamp | integer | Unix timestamps in seconds. |
range.end_exclusive | boolean | Always true for this endpoint. |
range.naive_input_timezone | string | Timezone applied to ISO input without an explicit offset: Asia/Tehran. |
totals | object | Combined totals across all returned admin rows. |
totals.sales_gb | number | Gross sales in GB: new_client_gb + added_gb. |
totals.new_client_gb, totals.added_gb | number | GB assigned at client creation and GB added to existing clients. |
totals.new_clients, totals.add_operations | integer | Counts of client-creation and GB-addition sale events. |
totals.unresolved_events | integer | Legacy events whose amount could not be reconstructed. Non-zero means the report is incomplete for those events. |
admins[] | array | One row per current admin, including zero-sale admins. Historical deleted actors may appear as admin #ID. |
admins[].* | object | Each row contains admin_id, username, is_active, the GB totals, and the event counts. |
Sales calculation
sales_gb = new_client_gb + added_gb
The report uses client-creation and GB-addition audit events. Deleted users or later quota changes do not retroactively remove the original sale event.
Validation errors
| Status | When it happens | Example body |
|---|---|---|
| 400 Bad Request | Missing or malformed time values, a non-positive range, or an out-of-range timestamp. | {"ok": false, "error": "end must be later than start"} |
| 401 Unauthorized | Credentials are missing, invalid, or an access token has expired. | {"detail": "Not authenticated"} |
| 403 Forbidden | The credentials are valid, but the account is not a superadmin. | {"detail": "Insufficient role"} |
PATCH /api/v1/admins/{admin_id} Charge an admin, reduce quota, or update admin settings.
admin_id. Quota changes are write operations and are recorded in the superadmin audit log.
superadmin
Normal admin accounts receive 403.
{"ok": true}.
Path parameter
| Name | Type | Required | Description |
|---|---|---|---|
admin_id | integer | Required | ID of the portal admin to update. Get IDs from GET /api/v1/admins. |
Quota actions
| Field | Type | Required | Effect | Rules |
|---|---|---|---|---|
add_gb |
number |
Optional | Charges the admin by increasing their total quota by this amount. | Must be greater than 0. It does not directly change any client allocation. |
reduce_gb |
number |
Optional | Reduces the admin's total quota by this amount. | Must be greater than 0. The new total cannot be lower than quota already allocated to clients. |
80 GB allocated to clients, their total quota cannot be reduced below 80 GB. The API rejects a reduction that would violate this rule.
Other supported updates
| Field | Type | Effect |
|---|---|---|
is_active | boolean | Activate or deactivate the admin account. |
min_client_gb | number | Set the minimum GB allowed when the admin creates a client; must be greater than 0. |
subscription_host | string | Set the subscription hostname, such as sub.example.com. |
hide_branding | boolean | Enable or disable branded subscription output. |
portal_password | string | Change the portal login password. |
marzban_username, marzban_password | string | Update the linked panel credentials; the credentials are validated before saving. |
Request examples
curl -X PATCH 'https://admin.vsl247.com/api/v1/admins/42' \
-H 'Authorization: Bearer SUPERADMIN_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"add_gb": 25}'
curl -X PATCH 'https://admin.vsl247.com/api/v1/admins/42' \
-H 'Authorization: Bearer SUPERADMIN_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"reduce_gb": 10}'
{
"ok": true
}
Errors
| Status | When it happens | Example |
|---|---|---|
| 400 Bad Request | The body is not a JSON object, a GB value is missing/invalid/non-positive, the admin does not exist, or a reduction would go below allocated quota. | Cannot reduce below allocated quota (80 GB) |
| 401 Unauthorized | Credentials are missing, invalid, or expired. | {"ok": false, "error": "Authentication required"} |
| 403 Forbidden | The credentials are valid, but the account is not a superadmin. | {"ok": false, "error": "Forbidden"} |
add_gb or reduce_gb. Each successful change is auditable by the superadmin.
Portal-Native API
Legacy routes that use the portal's ok response wrapper.
GET /api/v1/me Return the current portal account and quota.
{
"ok": true,
"user": {
"username": "admin_username",
"role": "admin",
"quota_bytes": 107374182400,
"remaining_bytes": 85899345920
}
}
GET /api/v1/clients Return the portal-native client list.
GET /api/v1/clients?page=1&per_page=10&search=client&live=1
{
"ok": true,
"page": 1,
"per_page": 10,
"total": 1,
"has_next": false,
"clients": []
}
POST /api/v1/admins Create a portal admin and link a non-sudo panel admin.
{
"username": "portal_admin",
"portal_password": "portal-password",
"quota_gb": 100,
"min_client_gb": 0.5,
"subscription_host": "sub.example.com",
"marzban_admin_mode": "existing",
"marzban_username": "panel_admin",
"marzban_password": "panel-password"
}
{
"ok": true,
"admin_id": 42
}
No matching endpoints.
Compatibility Notes
- PasarGuard-compatible routes manage only clients owned by the authenticated portal admin.
- Client creation and data-limit increases follow portal quota rules.
- Deleting a user never returns quota to the admin.
- PasarGuard-compatible responses do not use the portal-native
okwrapper. - Unsupported panel administration and system routes are not proxied.