REST API Reference¶
The Surf Shield Panel exposes two API surfaces:
| Surface | Purpose | Audience |
|---|---|---|
/webapi/application/... |
Programmatic management for your own backend | Server-to-server |
Subscription URL (https://<panel>/sub/<token>) |
Per-client config document consumed by the SDK | Mobile / desktop clients |
This page documents the /webapi/application surface. For the local HTTP API exposed by the running core on 127.0.0.1 see the Runtime HTTP API.
Authentication¶
Every call requires:
| Where | Name | Value |
|---|---|---|
| Header | X-Api-Key |
Your application API key (one per application). |
| Query | applicationId |
UUID of the application the call targets. |
Base URL: https://your-panel.example/webapi/application.
API keys have application-scoped permissions. Rotate them from the panel dashboard. Never ship an API key in a client app; all management calls belong in your own backend.
Conventions¶
- Dates are ISO-ish strings in UTC, formatted
yyyy-MM-dd HH:mm:ss. - Integer sizes like
traffic,used_trafficare bytes. download_speed_limit/upload_speed_limitare Mbps.0means "unlimited".max_online_ips = 0means unlimited concurrent connections.classis a free-form integer reserved for your own tiering.
Traffic States¶
| Value | Meaning |
|---|---|
traffic = -1 |
Blocked. Client is excluded from the active list and cannot connect. |
traffic = 0 |
Unlimited traffic (standard quotas still apply via expiry_date). |
traffic > 0 |
Hard allowance in bytes. Client is active while used_traffic <= traffic. |
Response Envelope¶
All JSON responses are wrapped:
Failures use HTTP status codes plus:
Common status codes:
| Code | Meaning |
|---|---|
200 |
OK |
201 |
Created |
204 |
No Content (delete / update) |
400 |
Invalid input |
401 |
Missing / invalid API key |
403 |
API key does not cover this application |
404 |
Resource not found |
500 |
Internal error |
Client Management¶
Endpoints under /webapi/application/clients.
List clients (paginated)¶
| Query | Default | Description |
|---|---|---|
page |
0 |
0-based page index. |
pageSize |
10 |
Max items per page. |
Response:
{
"status": "success",
"data": {
"page": 1,
"page_size": 25,
"total_pages": 4,
"total_items": 98,
"items": [
{
"id": "uuid",
"username": "user_001",
"class": 0,
"is_active": true,
"created_at": "2025-01-14 11:02:31",
"updated_at": "2025-02-01 09:11:54",
"expiry_date": "2026-01-14 23:59:59",
"traffic": 53687091200,
"used_traffic": 15048293,
"max_online_ips": 2,
"online_ips": ["198.51.100.12"],
"download_speed_limit": 0,
"upload_speed_limit": 0,
"application_id": "app-uuid",
"subscription_url": "https://panel.example/sub/abcdef"
}
]
}
}
Create client¶
POST /webapi/application/clients?applicationId=APP_ID
X-Api-Key: YOUR_API_KEY
Content-Type: application/json
{
"username": "user_001",
"traffic": 53687091200,
"used_traffic": 0,
"expiry_date": "2025-12-31 23:59:59",
"max_online_ips": 2,
"download_speed_limit": 0,
"upload_speed_limit": 0,
"class": 0,
"is_active": true
}
Response:
Fetch one client¶
Update client (partial)¶
PUT behaves as PATCH — fields you omit are left untouched.
PUT /webapi/application/clients/CLIENT_UUID?applicationId=APP_ID
X-Api-Key: YOUR_API_KEY
Content-Type: application/json
{
"traffic": -1,
"is_active": false
}
Use this to:
- Add more traffic (
"traffic": <new-allowance>). - Extend the expiry date (
"expiry_date": "2026-06-30 23:59:59"). - Ban instantly (
"traffic": -1, "is_active": false). - Change tier (
"class": 2).
Delete client¶
Immediately terminates any active session.
Lookup by username¶
Returns { "status": "success", "data": "uuid" } — useful when integrating with an external identity system where usernames are the primary key.
Traffic statistics¶
GET /webapi/application/clients/CLIENT_UUID/traffic?applicationId=APP_ID&page=1&pageSize=30
X-Api-Key: YOUR_API_KEY
Returns paginated hourly / daily traffic samples aggregated per node. Use it to drive graphs in your dashboard.
{
"status": "success",
"data": {
"page": 1,
"page_size": 30,
"total_pages": 12,
"total_items": 345,
"items": [
{
"timestamp": "2025-10-18 12:00:00",
"bytes_in": 2048576,
"bytes_out": 5242880,
"node_id": "node-uuid"
}
]
}
}
Subscription URL (per client)¶
The panel auto-generates a long-lived URL per client that returns a signed Leaf configuration document. Clients fetch it through the SDK via updateSubscription(clientId, ...) — you do not normally need to touch it from your backend.
If you need to rotate it manually use the panel UI or call:
POST /webapi/application/clients/CLIENT_UUID/rotate-subscription?applicationId=APP_ID
X-Api-Key: YOUR_API_KEY
Old URLs stop working immediately after rotation.
DTO Reference¶
ClientDTO¶
| Field | Type | Required | Notes |
|---|---|---|---|
id |
UUID | on read | Server-generated. |
application_id |
UUID | required | Must match the query parameter. |
username |
string | required | Unique within an application. |
class |
int | required | Your tier integer. Min: 0. |
is_active |
bool | required | false blocks login. |
traffic |
int64 | required | Bytes. -1 block / 0 unlimited. |
used_traffic |
int64 | required | Bytes. Server-updated every few seconds from node telemetry. |
expiry_date |
date | required | yyyy-MM-dd HH:mm:ss. |
max_online_ips |
int | required | Concurrent connections. 0 unlimited. |
online_ips |
string[] | on read | Currently connected source IPs. |
download_speed_limit |
int64 | required | Mbps. |
upload_speed_limit |
int64 | required | Mbps. |
subscription_url |
string | on read | Long-lived URL consumed by the SDK. |
created_at / updated_at |
date | on read | Audit timestamps. |
PaginationDTO<T>¶
| Field | Description |
|---|---|
items |
Array of T. |
page |
1-based page number. |
page_size |
Items per page. |
total_pages |
Total pages. |
total_items |
Total matching items. |
Typical flows¶
Provision a new user¶
- Your billing system calls
POST /clientswithtraffic,expiry_date,max_online_ips. - Panel returns the new client UUID.
- You display the UUID (or the deep link
leafvpn://install?clientId=<uuid>) to the user. - User opens the mobile / desktop app; the SDK calls
updateSubscription(uuid)and fetches the signed config.
Suspend for non-payment¶
- Billing job calls
PUT /clients/<uuid>with{ "is_active": false, "traffic": -1 }. - Active sessions drop on the next telemetry tick (usually < 30 s).
Top up traffic¶
- Billing job calls
PUT /clients/<uuid>with{ "traffic": <new-allowance>, "used_traffic": 0 }. - Next
updateSubscriptioncall on the client mirrors the new quota intoLeafPreferences.
See also¶
- Live per-session stats and log streaming: Local Runtime HTTP API.
- Embedding the SDK in your client: Android / Desktop (Rust) / FFI / Java.