Exit Server Tunnel API Reference
Create and remove WireGuard-over-TCP users on your own exit server, from your own system. With auto-apply enabled, the peer is also created on your MikroTik router, with nobody running a command by hand.
https://servernet.cloud/en/developers/tunnel · 2026/08/25
/api/v1/tunnel/servers
exit servers with a TCP tunnel
tunnel:read
/api/v1/tunnel/{service}/accounts
users and their state
tunnel:read
/api/v1/tunnel/{service}/accounts
create user — private key returned once
tunnel:write
/api/v1/tunnel/{service}/accounts/{name}
delete user
tunnel:write
/api/v1/tunnel/{service}/agent
auto-apply status
tunnel:read
/api/v1/tunnel/{service}/agent
enable auto-apply — token returned once
tunnel:write
1Prerequisites and authentication
This interface only works for exit servers that have a TCP tunnel configured. A server without such a profile does not appear in the listing at all, so you never have to know which server is eligible: the listing itself is the answer.
- Open the Security page in your client panel and issue an API token.
- Tick the two scopes tunnel:read and tunnel:write.
- If needed, set an IP allowlist on that token so it only works from your own server.
- The token is shown once; store it right there.
curl -H "Authorization: Bearer sn_xxxxxxxx" \
https://servernet.cloud/api/v1/tunnel/servers
2Find your service id
Every later path needs a numeric id. Read it from this call and keep it in your code.
GET https://servernet.cloud/api/v1/tunnel/servers
{"ok": true, "data": [{
"service_id": 49,
"name": "blackwood-vip-1",
"status": "active", "writable": true,
"host": "sn-571100.servernet.cloud", "port": 8443,
"subnet": "10.77.0.0/24", "next_ip": "10.77.0.5",
"accounts": 7, "max": 100,
"agent": {"installed": true, "alive": true,
"last_seen_at": "2026-08-24T08:16:18+00:00",
"pending_jobs": 0}
}]}
service_id | the id that replaces {service} in every later path |
host | the name or address the end user connects to |
subnet | the internal range of the server; every user address comes from it |
next_ip | first free address, used when you omit ip |
writable | false means the service is not active and no account is issued |
agent | auto-apply status on the router, see the next section |
3Auto-apply on the router
Your router is not reachable from our side, so we never connect to it: it asks us. Running the two lines below installs a script and a scheduler that read your queue regularly and apply new peers. Without this, everything still works, you just run the router command yourself.
- Fetch the agent token with the call below; the response also returns the two ready to run lines.
- Run those two lines in your MikroTik router terminal.
- The first run takes a few seconds because the root certificate is installed too.
- After that the status should turn alive.
POST https://servernet.cloud/api/v1/tunnel/49/agent
{"ok": true, "data": {
"token": "sna_49_xxxxxxxx",
"replaced": false,
"install": [
"/tool fetch url=\"https://servernet.cloud/agent/tunnel/install\" http-header-field=\"X-Agent-Token: sna_49_xxxxxxxx\" dst-path=snet-agent.rsc",
"/import file-name=snet-agent.rsc"
]
}}
installed means a token was issued; alive means the router actually checked in recently. The two are deliberately separate: an agent that is installed but silent does not work from your point of view, and a single label would hide that.
GET https://servernet.cloud/api/v1/tunnel/49/agent
{"ok": true, "data": {
"installed": true, "alive": true,
"last_seen_at": "2026-08-24T08:16:18+00:00",
"pending_jobs": 0
}}
The server never sends the router a command. The reply carries only three values, name, address and public key; the script validates each one separately and builds the command itself. The address is only accepted from inside your own range.
4Create a user
POST https://servernet.cloud/api/v1/tunnel/49/accounts
{"name": "ali-mobile"}
{"ok": true, "data": {
"name": "ali-mobile", "ip": "10.77.0.5",
"public_key": "...", "private_key": "...",
"delivery": {"mode": "agent", "status": "pending",
"job_id": 41, "agent_alive": true},
"router_command": "/interface/wireguard/peers/add ...",
"config": { ... sing-box ... }
}}
Input fields
name | required, 2 to 24 lowercase latin letters, digits, hyphen or underscore |
ip | optional, omit it and the next free address is chosen |
format | optional, singbox is the default; legacy is for older apps |
The config field is a complete sing-box configuration; hand it to the end user to save with a json extension and import into their app. You do not have to build anything.
The delivery field says where the work is: mode agent means it is queued for the router and will be created within seconds; mode manual means no agent is installed and you run router_command yourself. router_command is returned in both cases, because the agent may be down and that line is then the only way out.
5List users and their state
Every user carries a state field that reports what the end user experiences, not an internal label.
GET https://servernet.cloud/api/v1/tunnel/49/accounts
{"ok": true, "data": {
"service_id": 49, "next_ip": "10.77.0.6",
"agent": {"installed": true, "alive": true, "pending_jobs": 0},
"accounts": [
{"name": "ali-mobile", "ip": "10.77.0.5",
"public_key": "...", "issued_at": "...",
"state": "active"}
]
}}
active applied on the router, the user connects
pending still queued for the router, wait a few seconds
failed the router refused it or the agent never ran; check the router log
Users created before this queue existed have no job row and are deliberately reported as active rather than unknown: they are all sitting correctly on the router.
6Delete a user
DELETE https://servernet.cloud/api/v1/tunnel/49/accounts/ali-mobile
{"ok": true, "data": {
"name": "ali-mobile",
"delivery": {"mode": "agent", "status": "pending"},
"router_command": "/interface/wireguard/peers/remove [find name=\"ali-mobile\"]"
}}
7Errors and limits
A failed response always carries a machine readable code in the error field. Do not depend on the message text, it may change.
insufficient_scope | the token lacks the required scope |
not_found | no such server or account in your account |
service_not_active | the service is suspended, expired or cancelled |
bad_name | the name does not match the allowed pattern |
name_taken | an account with this name already exists |
bad_ip | the address is not from this server internal range |
ip_taken | that address is already in use |
limit_reached | you reached the account limit |
Rate limits
| read | 120 / 1 min |
| create and delete | 20 / 1 min |
| agent token issue | 5 / 1 min |
| maximum users per server | 100 |