CallMeTechie
DE Login
Home Products Blog About Contact

API Reference

v1.0 · Updated 1 month ago

Complete endpoint reference for server v1.50+. Practical integration recipes live in the separate API_GUIDE.md; only desktop-client-specific flows have their own chapter in CLIENT-API.md.

Conventions:

  • All admin endpoints are reachable under /api/v1/*. The path /api/* exists as a backward-compatible alias and is rewritten client-side (in public/js/app.js) to /api/v1/*.
  • Responses follow the pattern { ok: true, … } or { ok: false, error: "…" }. Deviations are noted at the endpoint.
  • All examples use gate.example.com as a placeholder for your GateControl domain.

Table of Contents


Authentication

GateControl knows three authentication paths. Different rules for CSRF, rate limits and input scope apply depending on the path.

Session Authentication (Browser)

Used by the admin UI. Login via POST /login sets a session cookie.

curl -c cookies.txt -X POST https://gate.example.com/login \
  -d "username=admin&password=…"
curl -b cookies.txt https://gate.example.com/api/v1/peers

State-changing methods (POST/PUT/DELETE/PATCH) additionally require a CSRF token in the X-CSRF-Token header. The token is in window.GC.csrfToken (injected by the server into every template via res.locals.csrfToken) or in the _csrf body field.

API Token (Automation)

For scripts and integrations. Tokens are created under Settings → API Tokens. Bearer header:

curl -H "Authorization: Bearer gc_…" https://gate.example.com/api/v1/peers

Alternatively X-API-Token or X-API-Key. Token-authenticated requests bypass CSRF.

Token scopes

Scope Access
full-access All endpoints (read + write)
read-only Only GET requests
peers /api/v1/peers/* completely
routes /api/v1/routes/* completely
settings /api/v1/settings/* + /api/v1/smtp/*
webhooks /api/v1/webhooks/*
logs /api/v1/logs/*
system /api/v1/system/*, /api/v1/wg/*, /api/v1/caddy/*

Gateway Token (Companion Container)

Each home gateway peer has its own bearer token, delivered in the gateway.env file. Applies exclusively to endpoints under /api/v1/gateway/*. Auth is done via Authorization: Bearer <apiToken>. The token is bound to a specific peer (see requireGateway middleware), CSRF does not apply.


Rate-Limiting

Built-in limiters per IP:

Scope Window Limit
/login 15 min 10
/api/v1/* (admin) 15 min 1000
/api/v1/client/* (peer agent) 15 min 100
/api/v1/client/peer/hostname 1 min 3
/api/v1/gateway/heartbeat no explicit limit (token auth)

Response headers Ratelimit-Limit, Ratelimit-Remaining, Ratelimit-Reset are always set.


Error responses

{ "ok": false, "error": "Human-readable message", "fields": { "domain": "Required field" } }

fields is optional — only on schema validation errors (HTTP 400). Status codes:

Code Meaning
200 OK
201 Created
304 Not Modified (e.g. /gateway/config/check)
400 Validation error — details usually in fields
401 Not authenticated
403 CSRF missing/invalid or scope insufficient
404 Resource not found
409 Conflict (e.g. peer group with name already exists)
429 Rate limit
500 Server error

Admin API

All following endpoints are behind requireAuth + apiLimiter (/api/v1/*). Writing operations require CSRF on session auth.

Dashboard

GET /api/v1/dashboard/stats

Returns the numbers for the 5 dashboard stat cards.

{
  "ok": true,
  "peers":   { "total": 6, "online": 4 },
  "routes":  { "active": 7 },
  "monitoring": { "total": 3, "up": 2, "down": 1 },
  "traffic": { "today": 3200000000, "todayUpload": 1200000000, "todayDownload": 2000000000,
               "uploadRate": 41200, "downloadRate": 128000 },
  "wireguard": { "running": true },
  "latency": 23
}

GET /api/v1/dashboard/traffic?period=1h|24h|7d

Chart data points for the traffic graph.


System

GET /api/v1/system/resources

CPU, RAM, uptime, disk. Called by the dashboard among others.

{ "ok": true,
  "cpu":    { "cores": 4, "percent": 8, "model": "…" },
  "memory": { "total": 8e9, "used": 2e9, "percent": 25 },
  "uptime": { "seconds": 1234, "days": 0, "hours": 0, "minutes": 20, "formatted": "20m", "bootTime": "2026-04-22" },
  "disk":   { "total": …, "free": …, "used": … }
}

GET /api/v1/system/dns/status · Feature internal_dns

Internal DNS service state: hosts-file metadata, peer count by hostname source (admin/agent/stale).

GET /api/v1/system/dns/records · Feature internal_dns

Snapshot of the entire internal DNS zone: static records (gateway.<domain>, server.<domain>), all peer records with source.


Logs

GET /api/v1/logs/activity?limit=100&offset=0&severity=info

Full activity log. Filters: severity, source, event_type, since, until (ISO date or epoch-ms).

GET /api/v1/logs/recent?limit=10

Activity feed for the dashboard.

GET /api/v1/logs/access?limit=100&offset=0

Caddy access log (filtered from /data/caddy/access.log).

GET /api/v1/logs/activity/export?format=csv|json

GET /api/v1/logs/access/export?format=csv|json

As file download.


Peers

GET /api/v1/peers?limit=250&offset=0

List of all peers.

{ "ok": true, "peers": [
  { "id": 73, "name": "NAS2", "peer_type": "regular", "enabled": 1, "allowed_ips": "10.8.0.3/32",
    "tags": "NAS, Heimnetz", "group_id": 2, "hostname": "nas2", "hostname_source": "admin",
    "expires_at": null, "total_rx": …, "total_tx": …, "latestHandshake": 1776…,
    "isOnline": true }
] }

peer_type is either "regular" or "gateway".

GET /api/v1/peers/:id

Single peer.

POST /api/v1/peers

Creates a peer. Body:

{ "name": "Laptop", "description": "Travel device", "tags": "Desktop",
  "group_id": 1, "expires_at": "2026-12-31", "dns": "1.1.1.1",
  "is_gateway": false, "api_port": 9876 }

is_gateway: true creates a gateway peer and responds with { peer, gateway: { apiToken, pushToken, envContent } } (plaintext tokens only once; envContent contains a ready-to-use gateway.env).

PUT /api/v1/peers/:id

Partial update. All fields optional.

DELETE /api/v1/peers/:id

Removes the peer. Breaks existing WG handshakes.

PUT /api/v1/peers/:id/toggle

Enable/disable without deletion.

POST /api/v1/peers/batch

Body { action: "enable"|"disable"|"delete", ids: [1,2,3] }.

GET /api/v1/peers/:id/config

WireGuard config as plaintext body (Content-Type text/plain). Query ?download=1 triggers an attachment download.

GET /api/v1/peers/:id/qr

QR code as data URL + raw config.

{ "ok": true, "name": "…", "qr": "data:image/png;base64,…", "config": "[Interface]\n…" }

PATCH /api/v1/peers/:id/hostname · Feature internal_dns

Admin sets hostname for internal DNS. Body { hostname: "nas2" }. Empty string or null removes. Sticky-admin: overrides agent-reported names, agent writes cannot replace an admin value.

GET /api/v1/peers/:id/gateway-info

Only for peer_type === "gateway". Parsed last_health payload + state machine status + API port.

{ "ok": true, "gateway": {
  "peer_id": 79, "status": "online", "api_port": 9876, "last_seen_at": 1776…,
  "health": { "uptime_s": 4200, "wg_handshake_age_s": 45,
    "route_reachability": [ { "route_id": 43, "reachable": true, "latency_ms": 2 } ],
    "telemetry": { "gateway_version": "1.4.0", "cpu_cores": 4, "mem_used": 234000000, … } }
} }

GET /api/v1/peers/:id/traffic?period=24h|7d|30d

Chart data for per-peer traffic.

POST /api/v1/peers/:id/gateway-env/rotate

Rotates gateway API token + push token for a gateway peer. Invalidates the running companion immediately. Response contains fresh tokens + newly built envContent.


Peer groups

GET /api/v1/peer-groups

All groups with peer count.

{ "ok": true, "groups": [{ "id": 2, "name": "Heimnetz", "color": "#64748b", "description": "", "peer_count": 3 }] }

POST /api/v1/peer-groups — Body { name, color?, description? }

PUT /api/v1/peer-groups/:id

DELETE /api/v1/peer-groups/:id

Deletion sets group_id of all members to NULL.


Tags

Peer tags are a CSV on peers.tags. Since v1.48 there is also a registry table (tags) that manages tag names independently from peer assignments.

GET /api/v1/tags

Merged view: registry + distinct tokens from all peer CSVs.

{ "ok": true, "tags": [
  { "id": 3, "name": "server", "peer_count": 2, "registered": true },
  { "id": null, "name": "Pixel-only", "peer_count": 1, "registered": false }
] }

POST /api/v1/tags — Body { name }

Idempotent (INSERT OR IGNORE). Rejected on ,<>"\n\r\t or > 64 characters.

DELETE /api/v1/tags/:name

Deletes registry entry and strips the token from all peers.tags CSVs (case-insensitive, whole-word — prod does not wipe prod-backup).


Routes

Reverse-proxy configuration. Route types: http, l4. Targets: peer (direct WG peer) or gateway (via home gateway container).

GET /api/v1/routes?limit=250&offset=0&type=http|l4

GET /api/v1/routes/:id

POST /api/v1/routes

Body fields (excerpt):

{ "domain": "app.example.com",
  "route_type": "http",
  "target_kind": "peer",
  "peer_id": 73,
  "target_port": "5000",

  "https_enabled": true,
  "backend_https": false,
  "compress_enabled": false,

  "bot_blocker_enabled": false,
  "bot_blocker_mode": "block",
  "bot_blocker_config": {},

  "monitoring_enabled": false,
  "ip_filter_enabled": false,
  "ip_filter_mode": "whitelist",
  "ip_filter_rules": [],

  "acl_enabled": false,
  "acl_peer_ids": [],

  "rate_limit_enabled": false,
  "rate_limit_requests": 100,
  "rate_limit_window": "1m",

  "retry_enabled": false,
  "retry_count": 3,
  "retry_status_codes": "502,503,504",

  "circuit_breaker_enabled": false,
  "circuit_breaker_threshold": 5,
  "circuit_breaker_timeout": 30,

  "mirror_enabled": false,
  "mirror_targets": [],

  "debug_enabled": false,

  "basic_auth_user": "",
  "basic_auth_password": "",

  "user_ids": [1, 2] }

Gateway routes: target_kind: "gateway", target_peer_id: <gateway-peer-id>, target_lan_host, target_lan_port (+ optional wol_enabled, wol_mac).

L4 routes: additionally l4_protocol (tcp|udp), l4_listen_port, l4_tls_mode (none|passthrough|terminate).

PUT /api/v1/routes/:id — partial update

DELETE /api/v1/routes/:id

PUT /api/v1/routes/:id/toggle

POST /api/v1/routes/batch — Body { action, ids }

POST /api/v1/routes/check-dns — Body { domain }

DNS check: loads A records, compares against the public IP of the server.

{ "ok": true, "matches": true, "resolvedIps": ["1.2.3.4"], "expectedIp": "1.2.3.4" }

GET /api/v1/routes/peers

List of available peers for the route wizard (online status, gateway flag, group). Query ?peer_type=gateway filters to gateway peers.

POST /api/v1/routes/:id/check

Trigger an immediate uptime monitoring check.

POST /api/v1/routes/:id/circuit-breaker/reset · Feature circuit_breaker

Resets a stuck circuit breaker manually to closed (zeroes cb_failure_count, clears cb_opened_at, re-renders Caddy). Necessary because the breaker state is persisted in SQLite and survives restarts — without a reset an open breaker waits for the monitoring's timeout and half-open cycle. Responds 400 if the circuit breaker is not enabled on the route.

POST /api/v1/routes/:id/branding/logo (Multipart file)

DELETE /api/v1/routes/:id/branding/logo

POST /api/v1/routes/:id/branding/bg-image (Multipart file)

DELETE /api/v1/routes/:id/branding/bg-image

Custom branding for the route-auth login page.

GET /api/v1/routes/:id/trace

Request tracing: returns the last N requests for the route. Details see features/request-tracing.md.


Route-Auth

Sub-resource to routes. Gateway is /:id/auth.

GET /api/v1/routes/:id/auth

POST /api/v1/routes/:id/auth

Body configures the auth method: email_password, email_code, totp; optional two_factor_enabled + two_factor_method. Fields: email, password, session_max_age (ms).

DELETE /api/v1/routes/:id/auth

POST /api/v1/routes/:id/auth/totp-setup

POST /api/v1/routes/:id/auth/totp-verify — Body { token }


RDP routes

RDP is its own route type with its own API under /api/v1/rdp/*. License-gated (remote_desktop).

GET /api/v1/rdp

List of all RDP routes.

POST /api/v1/rdp — Body (excerpt)

{ "name": "Office-PC",
  "access_mode": "internal" | "external" | "both" | "gateway",
  "target_host": "192.168.2.10", "target_port": 3389,
  "listen_port": 13389,
  "gateway_peer_id": 79, "gateway_listen_port": 3389,
  "gateway_host": "rdg.example.com", "gateway_port": 443,
  "credentials": { "username": "mka", "password": "…", "domain": "" },
  "wol_enabled": true, "wol_mac": "AA:BB:CC:DD:EE:FF",
  "network_level_auth": true, "audio_mode": "local", "clipboard_enabled": true,
  "resolution": "1920x1080", "color_depth": 32,
  "maintenance_windows": [] }

access_mode (from VALID_ACCESS_MODES in src/services/rdp.js):

  • internal — target is a direct WG peer in the tunnel (client's LAN IP + RDP port)
  • external — target is reachable via public IP/domain (e.g. your own RDP server with port forwarding)
  • both — both paths possible, desktop client chooses by reachability
  • gateway — connection runs through a home gateway container; the server automatically creates a linked L4-TCP route (gateway_l4_route_id FK to routes.id). See features/rdp-via-gateway.md.

Microsoft RD-Gateway (TSGateway) is not a dedicated mode but an additional field pair: gateway_host + gateway_port are passed through to the desktop client as .rdp config with gatewayhostname/gatewayport and can be configured additionally with any access mode.

GET /api/v1/rdp/:id

PATCH /api/v1/rdp/:id

DELETE /api/v1/rdp/:id

PUT /api/v1/rdp/:id/toggle

POST /api/v1/rdp/batch

GET /api/v1/rdp/status

Aggregated status of all RDP routes.

GET /api/v1/rdp/pubkey

Server public key for credential encryption (client-side).

GET /api/v1/rdp/:id/credentials

PUT /api/v1/rdp/:id/credentials — Body { credentials: { …E2EE-encrypted… } }

DELETE /api/v1/rdp/:id/credentials

Ciphertext in/out — password is never stored in plaintext. See src/utils/crypto.js.

POST /api/v1/rdp/:id/wol

Sends Wake-on-LAN magic packet via gateway (when gateway-typed) or directly (peer-typed). Body optional { timeout_ms }.

GET /api/v1/rdp/:id/wol/status

GET /api/v1/rdp/:id/status

POST /api/v1/rdp/:id/sessions/disconnect-all

Disconnects all active sessions (server-side kill via RDP admin protocol, if available — otherwise marker).

GET /api/v1/rdp/:id/history?limit=100

GET /api/v1/rdp/history/export?format=csv|json

Per-route session history.

GET /api/v1/rdp/:id/maintenance

PUT /api/v1/rdp/:id/maintenance — Body { windows: [{ from, to, note }] }

GET /api/v1/rdp/rotation/pending

POST /api/v1/rdp/:id/rotation/ack

Credential rotation workflow — which routes are waiting for a new password (because the master key was rotated), admin confirms after re-entry.


Gateways (admin view)

GET /api/v1/gateways

Consolidated list of all gateway peers. One call feeds the entire "Home Gateways" section on /peers.

{ "ok": true, "gateways": [{
  "peer_id": 79, "name": "Home Gateway", "hostname": "nas1", "ip": "10.8.0.8",
  "api_port": 9876, "status": "online", "last_seen_at": 1776…,
  "health": { …complete last_health JSON including telemetry… },
  "routes": [ { "id": 43, "domain": "nas.domaincaster.com", "route_type": "http",
                "target_lan_host": "192.168.2.228", "target_lan_port": 5000 } ]
}] }

status comes from the gateway state machine (see src/services/gateways.js): "online", "offline", "unknown". Ground truth is route_reachability — details in the guide for _isHeartbeatHealthy (tests/gateway_health_definition.test.js).


Settings

All under /api/v1/settings/*.

GET /api/v1/settings/profile

PUT /api/v1/settings/profile — Body { username?, email?, language? }

PUT /api/v1/settings/password — Body { current, next }

POST /api/v1/settings/language — Body { language: "de"|"en" }

GET /api/v1/settings/app

PUT /api/v1/settings/default-theme — Body { theme: "pro"|"default" }

Server-wide default theme, applies to users without their own selection.

POST /api/v1/settings/clear-logs

Clears activity and access logs (confirmation via CSRF).


SMTP

GET /api/v1/smtp

PUT /api/v1/smtp — Body { host, port, secure, user, password, from }

POST /api/v1/smtp/test — Body { to }

Sends a test email to the given address.


Backup & Restore

GET /api/v1/settings/backup

Complete JSON backup (peers, routes, settings, webhooks, route-auth, peer-groups). Query ?format=download as attachment.

POST /api/v1/settings/backup/restore (Multipart)

Loads a backup JSON. Optional ?mode=merge|replace.

Auto-backup jobs

  • GET /api/v1/settings/autobackup
  • POST /api/v1/settings/autobackup — Body { schedule: "daily"|"weekly"|..., keep: 7, target: "local"|"s3", … }
  • PUT /api/v1/settings/autobackup/:id
  • DELETE /api/v1/settings/autobackup/:id
  • POST /api/v1/settings/autobackup/:id/run — trigger manually

WireGuard

GET /api/v1/wg/status

Current interface status, peer handshakes.

POST /api/v1/wg/restart

wg-quick down wg0 && wg-quick up wg0.


Caddy

GET /api/v1/caddy/status

{ ok, running: bool, pid, uptime_s, admin_api: "127.0.0.1:2019" }.

POST /api/v1/caddy/reload

Forces re-rendering of the Caddy config and admin-API load.


Webhooks

  • GET /api/v1/webhooks
  • POST /api/v1/webhooks — Body { name, url, events: ["peer_online", …], enabled, secret, retry_count? }
  • PUT /api/v1/webhooks/:id
  • DELETE /api/v1/webhooks/:id
  • PUT /api/v1/webhooks/:id/toggle
  • POST /api/v1/webhooks/:id/test — fires a test POST

API tokens

  • GET /api/v1/tokens — list (token hash only on create)
  • POST /api/v1/tokens — Body { name, scopes: ["full-access"], expires_at? } → plain token returned once
  • DELETE /api/v1/tokens/:id — revoke

License

  • GET /api/v1/license — current license state + features
  • POST /api/v1/license/activate — Body { key }
  • POST /api/v1/license/refresh — force revalidation against the license API
  • DELETE /api/v1/license — remove locally, back to community fallback

Users

Only for role admin. Multi-user table for UI logins (as opposed to API tokens).

  • GET /api/v1/users
  • POST /api/v1/users — Body { username, email, password, role }
  • PUT /api/v1/users/:id
  • DELETE /api/v1/users/:id
  • POST /api/v1/users/:id/reset-password

Gateway-Companion API

Endpoints under /api/v1/gateway/*. The purpose of this group is data exchange between a home gateway container (gatecontrol-gateway) and the server. Auth: bearer token from the gateway.env — middleware requireGateway binds the token to a peer ID.

GET /api/v1/gateway/config

Fetches the current gateway configuration (routes, TCP listener ports, Caddy proxy port). Response contains config_hash for efficient polling.

GET /api/v1/gateway/config/check?hash=sha256:…

Long-poll-friendly hash check. Responds 304 when unchanged, otherwise 200 with new hash.

POST /api/v1/gateway/status — Body { rx_bytes, tx_bytes, active_connections }

Traffic counter snapshot. Is not counted as a heartbeat.

POST /api/v1/gateway/probe — Body arbitrary

Echo endpoint for end-to-end connection tests (round-trip time, payload integrity).

POST /api/v1/gateway/heartbeat — Body (excerpt)

{ "uptime_s": 4200,
  "http_proxy_healthy": true,
  "api_healthy": true,
  "tcp_listeners": [{ "port": 13389, "status": "listening" }],
  "wg_handshake_age_s": 45,
  "dns_resolve_ok": true,
  "route_reachability": [{ "route_id": 43, "reachable": true, "latency_ms": 2 }],
  "overall_healthy": true,
  "hostname": "nas1",
  "config_hash": "sha256:…",
  "telemetry": { "gateway_version": "1.4.0", "node_version": "20.20.2",
    "wg_tools_version": "wireguard-tools v1.0.20210914",
    "cpu_cores": 4, "cpu_load_avg": [0.15, 0.22, 0.18],
    "mem_total": …, "mem_free": …, "mem_used": …,
    "disk": { "total": …, "free": …, "used": … },
    "os_platform": "linux", "os_release": "6.1.78", "arch": "arm64",
    "dns_resolvers": ["192.168.1.1"], "default_gateway_ip": "192.168.1.1" }
}

Side effects:

  • gateway_meta.last_health is stored as complete JSON
  • State machine reads route_reachability as ground truth (fallback: self-check)
  • hostname + telemetry additionally update peers.hostname (agent-source, sticky-admin)

Desktop-Client API

Path /api/v1/client/*. Auth: bearer token + machine-fingerprint binding. Details: CLIENT-API.md.

Compact list:

Endpoint Purpose
GET /ping Liveness check + server version
GET /permissions What is this token allowed to do?
POST /register New client registers a peer slot
GET /config Current WG config
GET /config/check?hash=… Hash-based idle poll
POST /heartbeat Handshake status, rx/tx, optional hostname
POST /status Detailed device status update
POST /peer/hostname Opportunistic hostname report (internal DNS, rate-limited 3/min)
GET /peer-info?peerId=… Lookup of another peer (e.g. for DNS fallback)
GET /traffic?period=… Per-peer traffic history
GET /services Assigned services (routes) for the client
GET /dns-check?domain=… DNS lookup proxy for the UI diagnostic tool
GET /rdp RDP routes this client is authorized for
GET /rdp/:id/status Is the target reachable?
GET /rdp/:id/connect Generate .rdp connect profile
POST /rdp/:id/session Record session start
PATCH /rdp/:id/session Session heartbeat
DELETE /rdp/:id/session Session end
GET /split-tunnel List of IP ranges to tunnel (override possible per token)

Public endpoints

No auth required.

GET /health

Health check (DB + WG interface). Localhost requests get a detailed structure, remote only { ok }. 503 on error.

GET /api/v1/client/update

Public release info (version, download URL). This allows desktop clients to discover updates even if their token is invalid.

GET /metrics · Feature prometheus_metrics

Prometheus format. Auth via session or token (system/full-access/read-only). Additionally ?token=… allowed as query param.

POST /login · POST /logout

Admin auth endpoints (body see AUTHENTICATION.md).


Quick-start examples

Create a peer with an API token

TOKEN="gc_…"
curl -X POST https://gate.example.com/api/v1/peers \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Laptop","description":"Travel device","tags":"Desktop"}' \
  | jq .peer.id

Create a gateway peer (download gateway env)

curl -X POST https://gate.example.com/api/v1/peers \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"NAS1","is_gateway":true,"api_port":9876}' \
  | jq -r .gateway.envContent > gateway.env

Query today's traffic (for monitoring dashboard)

curl -H "Authorization: Bearer $TOKEN" \
  https://gate.example.com/api/v1/dashboard/stats | jq .traffic.today

Disable a route (home-automation scenario)

curl -X PUT -H "Authorization: Bearer $TOKEN" \
  https://gate.example.com/api/v1/routes/7/toggle

Save a full backup as a file

curl -H "Authorization: Bearer $TOKEN" \
  "https://gate.example.com/api/v1/settings/backup?format=download" \
  -o backup-$(date -u +%F).json

More integration recipes (Home Assistant, Python, Tasker, GitHub Actions …): API_GUIDE.md.

Cookie Settings

We use cookies to improve your experience. Essential cookies are always active.

Privacy Policy
ESC
↑↓ navigate open esc close