Partner API · v1.1.0

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.

Base URLhttps://api.vox-vpn.com/partner/v1
Current version1.1.0
FormatJSON over HTTPS (UTF-8)
Version headerX-Vox-API-Version

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.

HTTP header
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

  1. 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.
  2. Customer picks a locationGET/locations — cache the list for up to an hour; filter by country, continent or protocol.
  3. Customer taps ConnectGET/users/{external_id}/config and load the returned config into your WireGuard / AmneziaWG client.
  4. Subscription ends or is refundedPOST/users/{external_id}/suspend — the user is disconnected within seconds. POST/users/{external_id}/resume restores access.
Always fetch a fresh config right before connecting. When a server IP is blocked in a country we move that server to a new IP automatically; only freshly fetched configs contain the new endpoint. Fetching a config is fast and does not create new keys — a user keeps the same keys in every location.

Locations

GET/locations

Lists the locations available to your account. All filters are optional and can be combined.

Query parameterDescription
countryISO 3166-1 alpha-2 code, comma-separated for several: JP,KR
continentOne or more of asia, europe, north-america, south-america, oceania, middle-east, africa
protocolwireguard or amneziawg — only locations that support it
qCase-insensitive text search in name, ID and country code
GET /locations?continent=asia&protocol=amneziawg
{
    "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:

FieldTypeDescription
idintegerVox user ID
external_idstringYour ID for the customer. 1–80 characters: letters, digits and _ . : @ + -. Unique within your account.
labelstring | nullFree text for your own reference (max 120)
statusstringactive or suspended
onlinebooleanConnected in the last 3 minutes
last_seen_atstring | nullLast VPN handshake (UTC, ISO 8601)
last_locationstring | nullLocation of the last connection
created_at, last_config_atstringUTC timestamps
EndpointDescription
POST/usersCreate a user. Body {"external_id": "…", "label": "…"} — both optional (an ID is generated if omitted). Returns {"user": {…}, "created": true|false}.
GET/usersList 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}/suspendDisconnect and block the user (keys are kept).
POST/users/{external_id}/resumeRe-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 parameterDescription
locationRequired. A location ID from /locations
protocolamneziawg (default) — hides VPN traffic from deep packet inspection; or wireguard — standard WireGuard
formatjson (default) or conf — the plain .conf file as text/plain
GET /users/user-1042/config?location=hk&protocol=amneziawg
{
    "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

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.

StatusdetailWhat to do
400invalid_external_id · bad_protocol · protocol_unavailable · bad_statusFix the request.
401invalid_api_keyMissing, wrong or revoked key.
403account_limit_reached · account_suspended · partner_suspendedNot allowed in the current state — contact your account manager for limits.
404account_not_found · location_not_foundUnknown user, or a location not enabled for your account.
422(validation list)Missing required parameter, e.g. location.
429rate_limitedSlow down; retry after a short backoff.
503temporarily_unavailableRetry 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

curl
# 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.conf
Node.js (18+)
const 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");
Python (requests)
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"]
PHP
$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

idLocationCountryContinentProtocols
au-sydneyAustralia (Sydney)AUoceaniawireguard, amneziawg
br-sao-pauloBrazil (São Paulo)BRsouth-americawireguard, amneziawg
ca-montrealCanada (Montreal)CAnorth-americawireguard, amneziawg
fr-parisFrance (Paris)FReuropewireguard, amneziawg
de-frankfurtGermany (Frankfurt)DEeuropewireguard, amneziawg
hkHong KongHKasiawireguard, amneziawg
in-mumbaiIndia (Mumbai)INasiawireguard, amneziawg
id-jakartaIndonesia (Jakarta)IDasiawireguard, amneziawg
ie-dublinIreland (Dublin)IEeuropewireguard, amneziawg
jp-tokyoJapan (Tokyo)JPasiawireguard, amneziawg
jp-tokyo-2Japan (Tokyo) 2JPasiawireguard, amneziawg
sgSingaporeSGasiawireguard, amneziawg
kr-seoulSouth Korea (Seoul)KRasiawireguard, amneziawg
se-stockholmSweden (Stockholm)SEeuropewireguard, amneziawg
gb-londonUnited Kingdom (London)GBeuropewireguard, amneziawg
us-ohioUnited States (Ohio)USnorth-americawireguard, amneziawg
us-oregonUnited States (Oregon)USnorth-americawireguard, amneziawg
us-oregon-2United States (Oregon) 2USnorth-americawireguard, amneziawg
us-virginiaUnited States (Virginia)USnorth-americawireguard, amneziawg

Changelog

v1.1.02026-10-10Current
  • 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).
v1.0.02026-10-10
  • First release: accounts (create, list, get, suspend, resume, delete), WireGuard and AmneziaWG configs, locations and usage.