Client API: Gateway-Companion Integration
API endpoints for communication between GateControl desktop clients (Windows, macOS, Linux) and the GateControl server.
All endpoints live under /api/v1/client/* and require authentication via an API token with scope peers or full-access.
Table of Contents
Authentication
The client authenticates via an API token. The token is created under Settings > API Tokens in the GateControl web interface and must have at least scope peers.
The token can be sent in three header variants:
Authorization: Bearer gc_your_token_here
X-API-Token: gc_your_token_here
X-API-Key: gc_your_token_here
Additionally, the client sends metadata headers:
X-Client-Version: 1.0.0
X-Client-Platform: windows
Creating a token (admin)
- Open the GateControl web interface
- Settings > API Tokens > Create Token
- Name: e.g. "Windows Client"
- Scope:
peers(orfull-access) - Copy the token and enter it into the client
Connection flow
Client Server
│ │
│──── GET /client/ping ───────────────>│ 1. Test connection
│<──── { ok, version } ───────────────│
│ │
│──── POST /client/register ──────────>│ 2. Register as peer
│<──── { peerId, config, hash } ──────│ (peer is created automatically)
│ │
│ [ Client stores peerId ] │
│ [ Client writes WG config ] │
│ [ Client starts WG tunnel ] │
│ │
│──── GET /client/config/check ───────>│ 3. Periodically check for updates
│<──── { updated: false } ────────────│ (every 300s, hash-based)
│ │
│──── POST /client/heartbeat ─────────>│ 4. Send heartbeat
│<──── { ok } ────────────────────────│ (periodically)
│ │
│──── POST /client/status ────────────>│ 5. Report status events
│<──── { ok } ────────────────────────│ (on connection state change)
│ │
Endpoints
Ping
Checks reachability of the server and validity of the API token.
GET /api/v1/client/ping
Response:
{
"ok": true,
"version": "1.5.2",
"timestamp": "2026-03-29T15:00:00.000Z"
}
Errors:
| Status | Meaning |
|---|---|
401 |
Token invalid or missing |
403 |
Token does not have peers scope |
Register
Registers the client as a new WireGuard peer. The peer name is generated automatically from the hostname. On name collision a suffix is appended (e.g. DESKTOP-ABC, DESKTOP-ABC-1).
POST /api/v1/client/register
Request body:
{
"hostname": "DESKTOP-ABC123",
"platform": "win32 10.0.22631",
"clientVersion": "1.0.0"
}
| Field | Type | Required | Description |
|---|---|---|---|
hostname |
string | yes | Windows hostname (os.hostname()) |
platform |
string | no | Operating system info |
clientVersion |
string | no | Client version |
Response (201):
{
"ok": true,
"peerId": 5,
"peerName": "DESKTOP-ABC123",
"config": "[Interface]\nPrivateKey = ...\nAddress = 10.8.0.5/32\nDNS = 1.1.1.1,8.8.8.8\n\n[Peer]\nPublicKey = ...\nEndpoint = vpn.example.com:51820\nAllowedIPs = 0.0.0.0/0\nPersistentKeepalive = 25\n",
"hash": "a1b2c3d4e5f6..."
}
| Field | Description |
|---|---|
peerId |
Unique peer ID (store for all following requests!) |
peerName |
Generated peer name |
config |
Complete WireGuard client configuration |
hash |
SHA-256 hash of the config (for update detection) |
Errors:
| Status | Meaning |
|---|---|
400 |
Hostname missing or invalid |
403 |
License peer limit reached |
409 |
No free IP addresses in the subnet |
Note: The created peer appears in the GateControl web interface with the tag desktop-client and a description containing platform info.
Fetching config
Retrieves the current WireGuard configuration for the registered peer.
GET /api/v1/client/config?peerId=5
| Parameter | Type | Required | Description |
|---|---|---|---|
peerId |
number | yes | Peer ID (from /register response) |
Alternatively via header: X-Peer-Id: 5
Response:
{
"ok": true,
"config": "[Interface]\nPrivateKey = ...\n...",
"hash": "a1b2c3d4e5f6...",
"peerName": "DESKTOP-ABC123"
}
Checking for config updates
Checks whether the WireGuard configuration has changed since the last fetch (hash-based). This endpoint is called periodically by the client (default: every 300 seconds).
GET /api/v1/client/config/check?peerId=5&hash=a1b2c3d4e5f6...
| Parameter | Type | Required | Description |
|---|---|---|---|
peerId |
number | yes | Peer ID |
hash |
string | no | Last known config hash |
Response (no change):
{
"ok": true,
"updated": false
}
Response (config changed):
{
"ok": true,
"updated": true,
"config": "[Interface]\nPrivateKey = ...\n...",
"hash": "f6e5d4c3b2a1..."
}
Behavior: If no hash parameter is sent, the server always returns the current config (updated: true).
Heartbeat
Periodically sends the client's current connection status to the server. Updates the peer's updated_at timestamp.
POST /api/v1/client/heartbeat
Request body:
{
"peerId": 5,
"connected": true,
"rxBytes": 1048576,
"txBytes": 524288,
"uptime": 3600,
"hostname": "DESKTOP-ABC123"
}
| Field | Type | Required | Description |
|---|---|---|---|
peerId |
number | yes | Peer ID |
connected |
boolean | no | VPN tunnel active? |
rxBytes |
number | no | Received bytes |
txBytes |
number | no | Transmitted bytes |
uptime |
number | no | Connection duration in seconds |
hostname |
string | no | Current hostname |
Response:
{
"ok": true
}
Reporting status
Reports one-off status events (e.g. connection established, disconnected, errors). Logged in the server's activity log.
POST /api/v1/client/status
Request body:
{
"peerId": 5,
"status": "connected",
"timestamp": "2026-03-29T15:30:00.000Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
peerId |
number | yes | Peer ID |
status |
string | yes | Status label |
timestamp |
string | no | ISO 8601 timestamp |
Typical status values:
| Status | Meaning |
|---|---|
connected |
Tunnel established successfully |
disconnected |
Tunnel disconnected |
reconnecting |
Reconnect attempt in progress |
error |
Connection error |
Response:
{
"ok": true
}
Error handling
All errors follow the standard format:
{
"ok": false,
"error": "Description of the error"
}
| Status | Meaning |
|---|---|
400 |
Missing or invalid parameters |
401 |
Authentication failed |
403 |
Insufficient permissions or license limit |
404 |
Peer not found |
409 |
Resource conflict (e.g. no IPs available) |
429 |
Rate limit exceeded (max. 100 requests/15 min) |
500 |
Server error |
Recommended client strategy
- 401/403: Check the token, prompt re-entry if necessary
- 404: Peer was deleted server-side, register again
- 429: Observe
Retry-Afterheader, reduce requests - 5xx: Exponential backoff (2s, 3s, 4.5s, ... max 60s)
Examples
Complete setup flow with curl
TOKEN="gc_your_token_here"
SERVER="https://gate.example.com"
# 1. Ping
curl -s -H "X-API-Token: $TOKEN" "$SERVER/api/v1/client/ping"
# {"ok":true,"version":"1.5.2","timestamp":"..."}
# 2. Register
REGISTER=$(curl -s -X POST \
-H "X-API-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hostname":"my-pc","platform":"linux","clientVersion":"1.0.0"}' \
"$SERVER/api/v1/client/register")
PEER_ID=$(echo "$REGISTER" | jq -r '.peerId')
echo "Peer ID: $PEER_ID"
# 3. Fetch config
curl -s -H "X-API-Token: $TOKEN" \
"$SERVER/api/v1/client/config?peerId=$PEER_ID" | jq -r '.config' > wg0.conf
# 4. Check for config updates
HASH=$(echo "$REGISTER" | jq -r '.hash')
curl -s -H "X-API-Token: $TOKEN" \
"$SERVER/api/v1/client/config/check?peerId=$PEER_ID&hash=$HASH"
# {"ok":true,"updated":false}
# 5. Send heartbeat
curl -s -X POST \
-H "X-API-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"peerId\":$PEER_ID,\"connected\":true,\"rxBytes\":0,\"txBytes\":0}" \
"$SERVER/api/v1/client/heartbeat"
# 6. Report status
curl -s -X POST \
-H "X-API-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"peerId\":$PEER_ID,\"status\":\"connected\"}" \
"$SERVER/api/v1/client/status"
Windows client setup
-
In the GateControl web interface: Settings > API Tokens > Create new token
- Name: "Windows Client"
- Scope:
peers - Copy the token (displayed only once!)
-
In the GateControl Windows client:
- Open Settings
- Enter the server URL (e.g.
https://gate.example.com) - Enter the API token (starts with
gc_) - Click Connect
-
The client automatically registers, creates a peer and establishes the VPN tunnel.