VPN API reference
Everything you need to run a white-label VPN on the Vox network: users, WireGuard & AmneziaWG configs, locations and usage.
Overview
The Vox partner API lets you run your own VPN brand on the Vox network. You create one user per customer, and request a ready-to-use WireGuard® or AmneziaWG configuration whenever that customer connects. We run the servers, capacity and blocked-IP rotation.
Versioning: the API follows semantic versioning. Additive changes (new fields, endpoints or filters) increase the minor version and never break existing integrations — ignore fields you don't know. Breaking changes would get a new base path (/partner/v2) with advance notice.
Authentication
Every request needs an API key in the Authorization header. Partners create and revoke keys in the dashboard under API keys. Keys start with vxk_ and are shown only once.
Authorization: Bearer vxk_your_secret_key- Call the API from your backend only. Never ship a key inside a mobile app, desktop app or web page.
- Use separate keys per environment (e.g. production, staging) so you can revoke one without downtime.
- A revoked key stops working immediately and returns 401 invalid_api_key.
Integration flow
- Customer signs up or paysPOST/users with your own user ID as external_id. Safe to repeat: an existing user is returned with "created": false.
- Customer picks a locationGET/locations — cache the list for up to an hour; filter by country, continent or protocol.
- Customer taps ConnectGET/users/{external_id}/config and load the returned config into your WireGuard / AmneziaWG client.
- Subscription ends or is refundedPOST/users/{external_id}/suspend — the user is disconnected within seconds. POST/users/{external_id}/resume restores access.
Locations
GET/locations
Lists the locations available to your account. All filters are optional and can be combined.
| Query parameter | Description |
|---|---|
| country | ISO 3166-1 alpha-2 code, comma-separated for several: JP,KR |
| continent | One or more of asia, europe, north-america, south-america, oceania, middle-east, africa |
| protocol | wireguard or amneziawg — only locations that support it |
| q | Case-insensitive text search in name, ID and country code |
{
"locations": [
{
"id": "hk",
"label": "Hong Kong",
"country": "HK",
"country_name": "Hong Kong",
"city": "Hong Kong",
"continent": "asia",
"protocols": [
"wireguard",
"amneziawg"
]
},
{
"id": "jp-tokyo",
"label": "Japan (Tokyo)",
"country": "JP",
"country_name": "Japan",
"city": "Tokyo",
"continent": "asia",
"protocols": [
"wireguard",
"amneziawg"
]
}
]
}Location IDs are stable — store them. A second server in the same city gets a suffix, e.g. jp-tokyo-2.
Users
A user is one of your customers. It has its own keys and works in every location. Fields:
| Field | Type | Description |
|---|---|---|
| id | integer | Vox user ID |
| external_id | string | Your ID for the customer. 1–80 characters: letters, digits and _ . : @ + -. Unique within your account. |
| label | string | null | Free text for your own reference (max 120) |
| status | string | active or suspended |
| online | boolean | Connected in the last 3 minutes |
| last_seen_at | string | null | Last VPN handshake (UTC, ISO 8601) |
| last_location | string | null | Location of the last connection |
| created_at, last_config_at | string | UTC timestamps |
| Endpoint | Description |
|---|---|
| POST/users | Create a user. Body {"external_id": "…", "label": "…"} — both optional (an ID is generated if omitted). Returns {"user": {…}, "created": true|false}. |
| GET/users | List users, newest first. Query: status, limit (1–500, default 100), offset. Returns {"total": n, "items": […]}. |
| GET/users/{external_id} | Get one user. |
| POST/users/{external_id}/suspend | Disconnect and block the user (keys are kept). |
| POST/users/{external_id}/resume | Re-enable a suspended user. |
| DELETE/users/{external_id} | Delete the user and its keys permanently. Returns {"deleted": true}. |
URL-encode external_id in paths when it contains @, + or :.
Configs
GET/users/{external_id}/config
| Query parameter | Description |
|---|---|
| location | Required. A location ID from /locations |
| protocol | amneziawg (default) — hides VPN traffic from deep packet inspection; or wireguard — standard WireGuard |
| format | json (default) or conf — the plain .conf file as text/plain |
{
"account_id": 42,
"external_id": "user-1042",
"location": "hk",
"location_label": "Hong Kong",
"protocol": "amneziawg",
"endpoint": "203.0.113.10:443",
"config": "[Interface]\nPrivateKey = …\nAddress = 10.11.4.20/32\nDNS = 1.1.1.1\nJc = 4\nJmin = 40\nJmax = 70\nS1 = …\nS2 = …\nH1 = …\nH2 = …\nH3 = …\nH4 = …\n\n[Peer]\nPublicKey = …\nAllowedIPs = 0.0.0.0/0, ::/0\nPersistentKeepalive = 25\nEndpoint = 203.0.113.10:443\n",
"note": "Server IPs can rotate when blocked: fetch a fresh config before connecting."
}The response contains the user's private key: don't log it, send it only to the user's own device over HTTPS, and don't cache it on shared storage. Responses carry Cache-Control: no-store.
Usage
GET/usage
{
"accounts": 1250,
"active_accounts": 1180,
"suspended_accounts": 70,
"max_accounts": 5000,
"online_now": 214,
"active_24h": 903,
"billable_this_month": 1102,
"month": "2026-10",
"locations_24h": [
{
"location": "Hong Kong",
"accounts": 402
},
{
"location": "Japan (Tokyo)",
"accounts": 233
}
]
}A billable user is one that connected at least once in the current calendar month (UTC). Users that never connect in a month are not billed.
GET · GET/changelog
The root returns the API version and your partner ID (useful as a key check). /changelog is public and needs no key.
Errors & limits
Errors return a non-2xx status and a JSON body {"detail": "error_code"}. Match on the code, not on the message.
| Status | detail | What to do |
|---|---|---|
| 400 | invalid_external_id · bad_protocol · protocol_unavailable · bad_status | Fix the request. |
| 401 | invalid_api_key | Missing, wrong or revoked key. |
| 403 | account_limit_reached · account_suspended · partner_suspended | Not allowed in the current state — contact your account manager for limits. |
| 404 | account_not_found · location_not_found | Unknown user, or a location not enabled for your account. |
| 422 | (validation list) | Missing required parameter, e.g. location. |
| 429 | rate_limited | Slow down; retry after a short backoff. |
| 503 | temporarily_unavailable | Retry with exponential backoff (1 s, 2 s, 4 s…). |
- Rate limit: 600 requests per minute per partner (all keys together); config requests 120 per minute. Ask us if you need more.
- Timeouts: most calls answer in under 100 ms. The first config for a user in a new location can take 1–3 s while the server is prepared — use a 15 s client timeout.
- Idempotency: POST /users, suspend and resume are safe to retry.
Code examples
# Create (or fetch) the user
curl -X POST https://api.vox-vpn.com/partner/v1/users \
-H "Authorization: Bearer $VOX_KEY" -H "Content-Type: application/json" \
-d '{"external_id": "user-1042", "label": "jane@example.com"}'
# AmneziaWG config for Hong Kong as a .conf file
curl "https://api.vox-vpn.com/partner/v1/users/user-1042/config?location=hk&protocol=amneziawg&format=conf" \
-H "Authorization: Bearer $VOX_KEY" -o user-1042-hk.confconst BASE = "https://api.vox-vpn.com/partner/v1";
const headers = { Authorization: `Bearer ${process.env.VOX_KEY}`, "Content-Type": "application/json" };
async function vox(path, init = {}) {
const r = await fetch(BASE + path, { ...init, headers });
const body = await r.json();
if (!r.ok) throw new Error(`${r.status} ${body.detail}`);
return body;
}
await vox("/users", { method: "POST", body: JSON.stringify({ external_id: "user-1042" }) });
const { config } = await vox("/users/user-1042/config?location=hk&protocol=amneziawg");import os, requests
BASE = "https://api.vox-vpn.com/partner/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VOX_KEY']}"
S.post(f"{BASE}/users", json={"external_id": "user-1042"}, timeout=15).raise_for_status()
r = S.get(f"{BASE}/users/user-1042/config",
params={"location": "hk", "protocol": "amneziawg"}, timeout=15)
r.raise_for_status()
config_text = r.json()["config"]$ch = curl_init("https://api.vox-vpn.com/partner/v1/users/user-1042/config?location=hk&protocol=wireguard");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("VOX_KEY")],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$res = json_decode(curl_exec($ch), true);
$config = $res["config"] ?? null;Client apps
WireGuard configs work with the official WireGuard apps and libraries on every platform. AmneziaWG configs need an AmneziaWG-capable client: the AmneziaVPN / AmneziaWG apps, or the open-source amneziawg-go, amneziawg-android and amneziawg-apple libraries for your own app. Both protocols use the same keys, so you can offer AmneziaWG in censored networks and WireGuard everywhere else.
Location list
| id | Location | Country | Continent | Protocols |
|---|---|---|---|---|
| au-sydney | Australia (Sydney) | AU | oceania | wireguard, amneziawg |
| br-sao-paulo | Brazil (São Paulo) | BR | south-america | wireguard, amneziawg |
| ca-montreal | Canada (Montreal) | CA | north-america | wireguard, amneziawg |
| fr-paris | France (Paris) | FR | europe | wireguard, amneziawg |
| de-frankfurt | Germany (Frankfurt) | DE | europe | wireguard, amneziawg |
| hk | Hong Kong | HK | asia | wireguard, amneziawg |
| in-mumbai | India (Mumbai) | IN | asia | wireguard, amneziawg |
| id-jakarta | Indonesia (Jakarta) | ID | asia | wireguard, amneziawg |
| ie-dublin | Ireland (Dublin) | IE | europe | wireguard, amneziawg |
| jp-tokyo | Japan (Tokyo) | JP | asia | wireguard, amneziawg |
| jp-tokyo-2 | Japan (Tokyo) 2 | JP | asia | wireguard, amneziawg |
| sg | Singapore | SG | asia | wireguard, amneziawg |
| kr-seoul | South Korea (Seoul) | KR | asia | wireguard, amneziawg |
| se-stockholm | Sweden (Stockholm) | SE | europe | wireguard, amneziawg |
| gb-london | United Kingdom (London) | GB | europe | wireguard, amneziawg |
| us-ohio | United States (Ohio) | US | north-america | wireguard, amneziawg |
| us-oregon | United States (Oregon) | US | north-america | wireguard, amneziawg |
| us-oregon-2 | United States (Oregon) 2 | US | north-america | wireguard, amneziawg |
| us-virginia | United States (Virginia) | US | north-america | wireguard, amneziawg |
Changelog
- GET /locations: new filters country, continent, protocol and q (text search).
- Locations now include country, city and continent fields.
- New GET /partner/v1 and GET /changelog endpoints; every response carries an X-Vox-API-Version header.
- Suspending or deleting an account now disconnects it within seconds.
- Config requests are limited to 120 per minute per partner (other endpoints: 600 per minute).
- First release: accounts (create, list, get, suspend, resume, delete), WireGuard and AmneziaWG configs, locations and usage.