Skip to content

Server API Reference

The Bifrost server exposes a REST API for management, monitoring, and configuration.

Default Address: http://localhost:7082

Check if the server is healthy and operational.

GET /api/v1/health

Response:

{
"status": "healthy",
"time": "2024-01-15T10:00:00Z"
}
Field Type Description
status string healthy or degraded
time string ISO 8601 timestamp

Get server version information.

GET /api/v1/version

Response:

{
"version": "1.0.0",
"git_commit": "abc123",
"build_time": "2024-01-15T10:00:00Z",
"go_version": "go1.22.0",
"platform": "linux/amd64"
}

Get server running status.

GET /api/v1/status

Response:

{
"status": "running",
"time": "2024-01-15T10:00:00Z",
"version": "1.0.0",
"backends": 3
}

Get aggregated server statistics.

GET /api/v1/stats

Response:

{
"total_connections": 5000,
"active_connections": 25,
"bytes_sent": 104857600,
"bytes_received": 209715200,
"backends": {
"total": 3,
"healthy": 3
},
"time": "2024-01-15T10:00:00Z"
}

Get all configured backends with their status and statistics.

GET /api/v1/backends

Response:

[
{
"name": "direct",
"type": "direct",
"healthy": true,
"stats": {
"active_connections": 5,
"total_connections": 100,
"bytes_sent": 1024000,
"bytes_received": 2048000
}
},
{
"name": "wireguard-us",
"type": "wireguard",
"healthy": true,
"stats": {
"active_connections": 10,
"total_connections": 500,
"bytes_sent": 5120000,
"bytes_received": 10240000
}
}
]

Get a specific backend by name.

GET /api/v1/backends/{name}

Path Parameters:

Parameter Type Description
name string Backend name

Response:

{
"name": "wireguard-us",
"type": "wireguard",
"healthy": true,
"stats": {
"active_connections": 10,
"total_connections": 500,
"bytes_sent": 5120000,
"bytes_received": 10240000
}
}

Add a new backend to the proxy server.

POST /api/v1/backends

Request Body:

{
"name": "my-proxy",
"type": "httpproxy",
"enabled": true,
"httpproxy": {
"address": "proxy.example.com:8080",
"username": "user",
"password": "pass"
}
}

Supported Backend Types:

  • direct - Direct internet connection
  • wireguard - WireGuard VPN tunnel
  • openvpn - OpenVPN tunnel
  • httpproxy - HTTP/HTTPS proxy
  • socks5 - SOCKS5 proxy

Response (201 Created):

{
"status": "created",
"backend": "my-proxy",
"type": "httpproxy"
}

Remove a backend from the proxy server.

DELETE /api/v1/backends/{name}

Response:

{
"status": "removed",
"backend": "my-proxy"
}

Get detailed statistics for a specific backend.

GET /api/v1/backends/{name}/stats

Response:

{
"active_connections": 10,
"total_connections": 500,
"bytes_sent": 5120000,
"bytes_received": 10240000,
"errors": 0,
"latency_ms": 45
}

Test connectivity through a specific backend.

POST /api/v1/backends/{name}/test

Request Body (optional):

{
"target": "google.com:443",
"timeout": "10s"
}

Response:

{
"status": "success",
"backend": "wireguard-us",
"target": "google.com:443",
"duration": "45ms",
"healthy": true
}

Get the current configuration with sensitive values redacted.

GET /api/v1/config

Response: Sanitized ServerConfig object with passwords, keys, and tokens masked.


Get the complete configuration for editing (use with caution).

GET /api/v1/config/full

Response: Full ServerConfig object including sensitive values.


Get metadata about configuration sections, including hot-reload capabilities.

GET /api/v1/config/meta

Response: a JSON array with one entry per top-level config section.

[
{
"section": "routes",
"hot_reloadable": true,
"description": "Routing rules"
},
{
"section": "rate_limit",
"hot_reloadable": true,
"description": "Rate limiting and bandwidth configuration"
},
{
"section": "server",
"hot_reloadable": false,
"description": "Server listeners and timeouts"
},
{
"section": "backends",
"hot_reloadable": false,
"description": "Backend connection configurations"
}
]

Exactly four sections are hot-reloadable: routes, rate_limit (including bandwidth), access_control and cache (rules and presets only — storage changes still require a restart). Every other section reports hot_reloadable: false.

This endpoint is derived from the same table the save path uses, so a client that renders “applies immediately” vs “needs a restart” from it cannot disagree with the save response. Prefer it over a hard-coded client-side list.

One caveat: cache reports hot_reloadable: true, but the reload only touches the cache when a cache manager exists. Turning cache.enabled on from a server that started with caching disabled still needs a restart.


Save configuration changes.

PUT /api/v1/config

Request Body:

{
"config": {
"server": { ... },
"backends": [ ... ],
"routes": [ ... ]
},
"create_backup": true
}

Response:

{
"success": true,
"message": "Configuration saved successfully",
"requires_restart": true,
"changed_sections": ["server", "access_control"],
"hot_reloaded_sections": ["access_control"],
"restart_required_sections": ["server"]
}
Field Type Description
changed_sections string[] Sections that differ from the running config
hot_reloaded_sections string[] Changed sections applied to the running server immediately
restart_required_sections string[] Changed sections written to disk that take effect after a restart
requires_restart boolean True exactly when restart_required_sections is non-empty
backup_path string Path of the backup, when create_backup was set
errors object[] {section, field, message} entries; a reload section means the save succeeded but applying it did not

A save that touches both kinds of section still hot-applies the hot-reloadable part — in the example above the new access_control blocklist is enforced immediately even though the listener change needs a restart. If the reload itself fails, hot_reloaded_sections is empty, every changed section is listed under restart_required_sections, and an error with "section": "reload" is returned; the configuration is still saved to disk.


Validate configuration without saving.

POST /api/v1/config/validate

Request Body: ServerConfig object

Response:

{
"valid": true,
"errors": [],
"warnings": ["Backend 'old-vpn' is enabled but not configured"]
}

Validation covers the auth providers as well as the config shape. An enabled provider whose type cannot authenticate in this build is reported invalid here and refused by PUT /api/v1/config — so a configuration that would fail at the next restart is never saved. Disabled providers are not checked, which keeps “disable it and save” a way out of a bad configuration.

{
"valid": false,
"message": "Configuration validation failed",
"errors": [
{
"section": "auth",
"message": "provider \"corp-ntlm\": auth provider type \"ntlm\" cannot authenticate anyone and is refused: NTLM response verification is not implemented..."
}
]
}

Reload configuration from file (hot-reload for supported sections).

POST /api/v1/config/reload

Response:

{
"message": "Config reloaded successfully",
"time": "2024-01-15T10:00:00Z"
}

Report every auth plugin registered in this server binary, together with whether it can actually authenticate. The Web UI uses this to label providers instead of hard-coding the list, which cannot be correct for build-dependent plugins.

GET /api/v1/auth/plugins

Response:

{
"plugins": [
{
"name": "native",
"type": "native",
"description": "Built-in user database with bcrypt passwords",
"availability": { "state": "available" }
},
{
"name": "system",
"type": "system",
"description": "System/PAM authentication (macOS via dscl; on Linux requires building with CGO_ENABLED=1 -tags pam...)",
"availability": {
"state": "build_disabled",
"reason": "this binary was built without the PAM backend, so system authentication fails closed on Linux..."
}
},
{
"name": "ntlm",
"type": "ntlm",
"description": "NTLM authentication (NON-FUNCTIONAL: fails closed...)",
"availability": {
"state": "unimplemented",
"reason": "NTLM response verification is not implemented: there is no credential source..."
}
}
]
}

Availability states:

State Meaning Configuration
available Works normally Accepted
build_disabled This binary lacks the support the plugin needs (a build tag, cgo, a platform library), so it fails closed here but works in a build that includes it Accepted, with a warning at startup and in the UI
unimplemented Cannot authenticate anyone in any build Refused at config validation

negotiate never appears here: SPNEGO/Negotiate is HTTP middleware configured under auth.negotiate.*, not a provider. A provider of type: negotiate is refused with an error pointing at the correct section.


Get all configured routing rules.

GET /api/v1/routes

Response:

[
{
"name": "internal",
"domains": ["*.internal.company.com", "intranet.*"],
"backend": "wireguard-corp",
"enabled": true,
"priority": 100
},
{
"name": "streaming",
"domains": ["*.netflix.com", "*.youtube.com"],
"backends": ["direct", "wireguard-us"],
"enabled": true,
"priority": 50
}
]

Add a new routing rule.

POST /api/v1/routes

Request Body:

{
"name": "social-media",
"domains": ["*.twitter.com", "*.facebook.com", "*.instagram.com"],
"backend": "direct",
"enabled": true,
"priority": 75
}

Response (201 Created):

{
"status": "created",
"route": "social-media",
"domains": ["*.twitter.com", "*.facebook.com", "*.instagram.com"],
"backend": "direct"
}

Remove a routing rule.

DELETE /api/v1/routes/{name}

Response:

{
"status": "removed",
"route": "social-media"
}

Get recent request log entries (requires enable_request_log: true in config).

GET /api/v1/requests
GET /api/v1/requests?limit=50
GET /api/v1/requests?since=123
GET /api/v1/requests?offset=100

Query Parameters:

Parameter Type Default Description
limit int 100 Maximum entries to return
since int - Get entries since this ID
offset int 0 Skip this many entries

Response:

{
"enabled": true,
"requests": [
{
"id": 1,
"timestamp": "2024-01-15T10:00:00Z",
"method": "GET",
"host": "api.example.com",
"path": "/v1/users",
"url": "https://api.example.com/v1/users",
"user_agent": "Mozilla/5.0...",
"client_ip": "192.168.1.100",
"username": "admin",
"backend": "direct",
"status_code": 200,
"bytes_sent": 1024,
"bytes_recv": 2048,
"duration_ms": 150,
"protocol": "HTTP"
}
]
}

Get request log statistics.

GET /api/v1/requests/stats

Response:

{
"enabled": true,
"count": 1500,
"max_size": 10000,
"total_requests": 48213,
"total_bytes_sent": 10485760,
"total_bytes_recv": 83886080,
"requests_by_method": { "GET": 1200, "POST": 300 },
"requests_by_status": { "200": 1400, "404": 100 },
"top_hosts": [
{ "host": "api.example.com", "count": 900 },
{ "host": "cdn.example.com", "count": 600 }
]
}

Two scopes are mixed in this response, because the request log is a fixed-size ring buffer:

  • total_requests, total_bytes_sent and total_bytes_recv are running totals since the server started or since the log was last cleared. They keep counting after entries are evicted from the buffer.
  • count, requests_by_method, requests_by_status and top_hosts describe only the entries still retained in the buffer — at most max_size of them.

top_hosts is ordered by descending count and holds at most 10 hosts. Every field is always present; the maps and the list are never null.


Clear all request log entries. This also resets total_requests, total_bytes_sent and total_bytes_recv.

DELETE /api/v1/requests

Response:

{
"message": "Request log cleared"
}

Get all currently active connections.

GET /api/v1/connections

Response:

{
"connections": [
{
"id": "20240115100000-1",
"client_ip": "192.168.1.100",
"client_port": "54321",
"host": "api.example.com:443",
"backend": "direct",
"protocol": "CONNECT",
"start_time": "2024-01-15T10:00:00Z",
"bytes_sent": 1024,
"bytes_recv": 2048
}
],
"count": 1,
"time": "2024-01-15T10:00:05Z"
}

Get unique clients with aggregated statistics.

GET /api/v1/connections/clients

Response:

{
"clients": [
{
"client_ip": "192.168.1.100",
"connections": 5,
"bytes_sent": 10240,
"bytes_recv": 20480,
"first_seen": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"time": "2024-01-15T10:00:05Z"
}

Get cache statistics.

GET /api/v1/cache/stats

Response:

{
"enabled": true,
"hit_rate": 0.85,
"total_requests": 12450,
"cache_hits": 10582,
"cache_misses": 1868,
"storage_type": "tiered",
"rules_count": 5,
"memory": {
"entries": 4521,
"size_bytes": 1073741824,
"max_size_bytes": 2147483648
},
"disk": {
"entries": 892,
"size_bytes": 107374182400,
"max_size_bytes": 536870912000
}
}

Get cached entries with optional filtering.

GET /api/v1/cache/entries
GET /api/v1/cache/entries?domain=*.steamcontent.com
GET /api/v1/cache/entries?limit=10&offset=0

Query Parameters:

Parameter Type Default Description
domain string - Filter by domain pattern
limit int 100 Maximum entries (max 1000)
offset int 0 Skip entries for pagination

Response:

{
"entries": [
{
"key": "ab12cd34...",
"url": "http://cdn.steamcontent.com/depot/123/chunk/abc",
"host": "cdn.steamcontent.com",
"size": 1048576,
"content_type": "application/octet-stream",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2025-01-15T10:30:00Z"
}
],
"total": 892,
"offset": 0,
"limit": 10
}

Get metadata for a specific cache entry.

GET /api/v1/cache/entries/{key}

Delete a specific cache entry.

DELETE /api/v1/cache/entries/{key}

Response:

{
"message": "Cache entry deleted",
"key": "ab12cd34..."
}

Clear the entire cache.

DELETE /api/v1/cache/entries?confirm=true

Response:

{
"message": "Cache cleared",
"time": "2024-01-15T10:00:00Z"
}

Purge all cache entries for a domain.

DELETE /api/v1/cache/domain/{domain}

Response:

{
"message": "Domain purged from cache",
"domain": "cdn.steamcontent.com",
"deleted": 150
}

Get all caching rules.

GET /api/v1/cache/rules

Response:

{
"rules": [
{
"name": "steam",
"domains": ["*.steamcontent.com", "content*.steampowered.com"],
"enabled": true,
"ttl": "8760h0m0s",
"priority": 100,
"preset": "steam"
}
],
"count": 5
}

Add a custom caching rule.

POST /api/v1/cache/rules

Request Body:

{
"name": "my-cdn",
"domains": ["cdn.example.com", "assets.example.com"],
"enabled": true,
"ttl": "168h",
"priority": 50
}

Response:

{
"message": "Cache rule created",
"rule": "my-cdn"
}

Update an existing cache rule.

PUT /api/v1/cache/rules/{name}

Request Body:

{
"enabled": false
}

Delete a cache rule.

DELETE /api/v1/cache/rules/{name}

Get available caching presets.

GET /api/v1/cache/presets

Response:

{
"presets": [
{
"name": "steam",
"description": "Steam game downloads and updates",
"domains": ["*.steamcontent.com", "content*.steampowered.com"],
"ttl": "8760h0m0s",
"priority": 100
},
{
"name": "epic-games",
"description": "Epic Games Store downloads",
"domains": ["*.epicgames.com", "download.epicgames.com"],
"ttl": "8760h0m0s",
"priority": 100
}
],
"count": 10
}

POST /api/v1/cache/presets/{name}/enable
POST /api/v1/cache/presets/{name}/disable

Response:

{
"message": "Preset enabled",
"preset": "steam"
}

Get all mesh networks.

GET /api/v1/mesh/networks

Response:

{
"networks": [
{
"id": "net-abc123",
"name": "Home Network",
"cidr": "10.100.0.0/24",
"peer_count": 5,
"created": "2024-01-01T00:00:00Z"
}
]
}

Create a new mesh network.

POST /api/v1/mesh/networks

Request Body:

{
"id": "net-xyz789",
"name": "Office Network",
"cidr": "10.200.0.0/24"
}

Response:

{
"id": "net-xyz789",
"name": "Office Network",
"cidr": "10.200.0.0/24",
"peer_count": 0,
"created": "2024-01-15T10:00:00Z"
}

Get details of a specific mesh network.

GET /api/v1/mesh/networks/{networkID}

Delete a mesh network.

DELETE /api/v1/mesh/networks/{networkID}

Register a peer to a mesh network.

POST /api/v1/mesh/networks/{networkID}/peers

Request Body:

{
"network_id": "net-abc123",
"peer": {
"id": "peer-xyz789",
"name": "laptop-work",
"public_key": "base64-encoded-key",
"endpoints": [
{
"address": "192.168.1.100",
"port": 51820,
"type": "lan",
"priority": 100
}
]
}
}

Response:

{
"success": true,
"virtual_ip": "10.100.0.5",
"peers": [
{
"id": "peer-abc123",
"name": "desktop-home",
"virtual_ip": "10.100.0.2",
"endpoints": [...]
}
]
}

Get all peers in a mesh network.

GET /api/v1/mesh/networks/{networkID}/peers

Get details of a specific peer.

GET /api/v1/mesh/networks/{networkID}/peers/{peerID}

Update peer information.

PATCH /api/v1/mesh/networks/{networkID}/peers/{peerID}

Request Body:

{
"endpoints": [...],
"metadata": {"location": "office"}
}

Remove a peer from the mesh network.

DELETE /api/v1/mesh/networks/{networkID}/peers/{peerID}

Update peer heartbeat (keep-alive).

POST /api/v1/mesh/networks/{networkID}/peers/{peerID}/heartbeat

Bifrost automatically generates PAC (Proxy Auto-Configuration) files based on routing rules.

GET /proxy.pac
GET /wpad.dat

Response (application/x-ns-proxy-autoconfig):

function FindProxyForURL(url, host) {
if (shExpMatch(host, "*.internal.company.com")) {
return "PROXY bifrost.example.com:7080; DIRECT";
}
return "DIRECT";
}

Browser Configuration:

  • macOS: System Preferences → Network → Advanced → Proxies → Automatic Proxy Configuration
  • Windows: Settings → Network → Proxy → Use setup script
  • Firefox: Settings → Network Settings → Automatic proxy configuration URL
  • Chrome: Uses system settings or Proxy SwitchyOmega extension