Skip to content

Client API Reference

The Bifrost client exposes a REST API for local management, VPN control, debugging, and desktop app integration.

Default Address: http://localhost:7383

Check if the client is healthy and connected to the server.

GET /api/v1/health

Response:

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

Get client 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": "darwin/arm64"
}

Get comprehensive client status.

GET /api/v1/status

Response:

{
"status": "running",
"version": "1.0.0",
"server_connected": true,
"server_address": "bifrost.example.com:7080",
"http_proxy": "127.0.0.1:7380",
"socks5_proxy": "127.0.0.1:7381",
"vpn_enabled": true,
"vpn_status": "connected",
"debug_entries": 150,
"uptime": "2h30m15s",
"bytes_sent": 104857600,
"bytes_received": 209715200,
"active_connections": 12,
"timestamp": "2024-01-15T10:00:00Z"
}

Traffic counters:

Field Meaning
bytes_sent Cumulative bytes written back to local proxy clients since the client started
bytes_received Cumulative bytes read from local proxy clients since the client started
active_connections Proxy connections currently being served across the HTTP and SOCKS5 listeners

The byte counters cover both the HTTP and SOCKS5 listeners and are reset when the client process restarts (they are in-memory, not persisted).


Establish connection to the Bifrost server.

POST /api/v1/connect

Response:

{
"status": "connected"
}

Disconnect from the Bifrost server.

POST /api/v1/disconnect

Response:

{
"status": "disconnected"
}

Get all configured servers.

GET /api/v1/servers

Response:

[
{
"name": "US West",
"address": "us-west.bifrost.example.com:7080",
"protocol": "HTTP",
"is_default": true,
"latency_ms": 45,
"status": "connected"
},
{
"name": "EU Central",
"address": "eu-central.bifrost.example.com:7080",
"protocol": "HTTP",
"is_default": false,
"latency_ms": 120,
"status": "online"
}
]

Select a server to connect to.

POST /api/v1/server/select

Request Body:

{
"server": "EU Central"
}

Response:

{
"status": "selected",
"server": "EU Central"
}

Get quick settings for desktop app integration.

GET /api/v1/settings

Response:

{
"auto_connect": true,
"start_minimized": false,
"show_notifications": true,
"vpn_enabled": false,
"system_proxy_enabled": true,
"current_server": "US West"
}

Update quick settings.

POST /api/v1/settings

Request Body:

{
"auto_connect": true,
"start_minimized": true,
"show_notifications": true,
"vpn_enabled": true,
"system_proxy_enabled": true,
"current_server": "US West"
}

Response:

{
"status": "updated"
}

Get current client configuration.

GET /api/v1/config

Response: Full ClientConfig object.

Field names match the YAML configuration keys exactly, so a response can be edited and sent straight back to PUT /api/v1/config.

Durations are duration strings, not numbers — "30s", "1m30s", "5m0s" — in both directions. This covers vpn.dns.cache_ttl, mesh.discovery.heartbeat_interval, mesh.discovery.peer_timeout, mesh.stun.timeout, mesh.connection.connect_timeout and mesh.connection.keep_alive_interval, alongside the proxy and server timeouts. On input a bare number is also accepted and read as nanoseconds.


Update configuration with partial updates.

PUT /api/v1/config

Request Body:

{
"proxy": {
"http": {
"listen": ":7380"
}
},
"routing": {
"default_action": "server"
}
}

Response:

{
"status": "updated",
"restart_required": true,
"restart_fields": ["proxy.http.listen"],
"warnings": []
}

Restart-Required Fields:

  • proxy.http.listen
  • proxy.socks5.listen
  • web_ui.listen
  • api.listen
  • vpn.enabled
  • vpn.tun
  • mesh.enabled
  • mesh.device

Reload configuration from file.

POST /api/v1/config/reload

Response:

{
"status": "reloaded"
}

Validate configuration changes without applying.

POST /api/v1/config/validate

Request Body: Partial config updates

Response:

{
"valid": true,
"errors": [],
"warnings": ["Changes to proxy.http.listen require client restart"]
}

Get default configuration values.

GET /api/v1/config/defaults

Export configuration to YAML or JSON.

POST /api/v1/config/export?format=yaml
POST /api/v1/config/export?format=json

Response: File download with Content-Disposition: attachment


Import configuration from YAML or JSON.

POST /api/v1/config/import?format=yaml
POST /api/v1/config/import?format=json

Request Body: YAML or JSON configuration

Response:

{
"status": "imported",
"restart_required": false,
"restart_fields": [],
"warnings": []
}

Get all configured routing rules.

GET /api/v1/routes

Response:

[
{
"name": "direct-local",
"patterns": ["*.local", "localhost", "127.0.0.1"],
"action": "direct",
"priority": 100
},
{
"name": "default",
"patterns": ["*"],
"action": "server",
"priority": 0
}
]

Add a new routing rule.

POST /api/v1/routes

Request Body:

{
"name": "work-vpn",
"domains": ["*.company.com", "intranet.*"],
"action": "server",
"priority": 90
}

Response:

{
"status": "created",
"route": "work-vpn",
"domains": ["*.company.com", "intranet.*"],
"action": "server"
}

Remove a routing rule.

DELETE /api/v1/routes/{name}

Response:

{
"status": "removed",
"route": "work-vpn"
}

Test which action a domain would receive.

GET /api/v1/routes/test?domain=api.company.com

Response:

{
"domain": "api.company.com",
"action": "server"
}

Get current VPN connection status.

GET /api/v1/vpn/status

Response:

{
"status": "connected",
"enabled": true,
"tunnel_type": "WireGuard",
"interface_name": "bifrost0",
"ip": "10.0.0.2",
"gateway": "10.0.0.1",
"dns": ["1.1.1.1", "8.8.8.8"],
"mtu": 1420,
"port": 51820,
"encryption": "ChaCha20-Poly1305",
"bytes_sent": 1048576,
"bytes_received": 2097152,
"connected_at": "2024-01-15T10:00:00Z"
}

Status Values:

  • disabled - VPN is not configured
  • connecting - VPN is establishing connection
  • connected - VPN is active
  • error - VPN encountered an error

Enable and start the VPN connection.

POST /api/v1/vpn/enable

Response:

{
"status": "enabled"
}

Disable and stop the VPN connection.

POST /api/v1/vpn/disable

Response:

{
"status": "disabled"
}

Get active VPN connections.

GET /api/v1/vpn/connections

Response:

[
{
"id": "conn-123",
"remote_addr": "api.example.com:443",
"local_addr": "10.0.0.2:54321",
"protocol": "tcp",
"started_at": "2024-01-15T10:00:00Z",
"bytes_sent": 1024,
"bytes_received": 2048
}
]

Get current split tunneling configuration.

GET /api/v1/vpn/split/rules

Response:

{
"mode": "exclude",
"apps": [
{"name": "Slack", "path": "/Applications/Slack.app"},
{"name": "Zoom", "path": "/Applications/zoom.us.app"}
],
"domains": ["*.local", "localhost", "*.lan"],
"ips": ["192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"],
"always_bypass": ["127.0.0.0/8", "169.254.0.0/16"]
}

Mode Values:

  • exclude - Traffic from listed apps/domains/IPs bypasses VPN
  • include - Only traffic from listed apps/domains/IPs uses VPN

Change split tunneling mode.

PUT /api/v1/vpn/split/mode

Request Body:

{
"mode": "exclude"
}

Response:

{
"status": "updated",
"mode": "exclude"
}

Add an application to split tunnel rules.

POST /api/v1/vpn/split/apps

Request Body:

{
"name": "Discord",
"path": "/Applications/Discord.app"
}

Response:

{
"status": "added"
}

Remove an application from split tunnel rules.

DELETE /api/v1/vpn/split/apps/{name}

Response:

{
"status": "removed"
}

Add a domain pattern to split tunnel rules.

POST /api/v1/vpn/split/domains

Request Body:

{
"pattern": "*.internal.company.com"
}

Response:

{
"status": "added"
}

DELETE /api/v1/vpn/split/domains/{pattern}

Add an IP range to split tunnel rules.

POST /api/v1/vpn/split/ips

Request Body:

{
"cidr": "172.16.0.0/12"
}

Response:

{
"status": "added"
}

DELETE /api/v1/vpn/split/ips/{cidr}

Get VPN DNS cache entries.

GET /api/v1/vpn/dns/cache

Get all traffic debug entries.

GET /api/v1/debug/entries

Response:

[
{
"timestamp": "2024-01-15T10:00:00Z",
"method": "GET",
"host": "api.example.com",
"path": "/v1/users",
"status_code": 200,
"duration": "150ms",
"action": "server",
"backend": "wireguard-us",
"bytes_sent": 1024,
"bytes_recv": 2048,
"error": ""
}
]

Get the most recent debug entries.

GET /api/v1/debug/entries/last/{count}

Clear all debug entries.

DELETE /api/v1/debug/entries

Response:

{
"message": "cleared"
}

Get only entries with errors.

GET /api/v1/debug/errors

Get memory usage statistics and runtime information.

GET /api/v1/debug/memory

Response:

{
"heap_alloc": 10485760,
"heap_sys": 20971520,
"heap_idle": 5242880,
"heap_inuse": 15728640,
"heap_released": 1048576,
"heap_objects": 50000,
"stack_inuse": 1048576,
"stack_sys": 2097152,
"num_gc": 25,
"last_gc_pause_ns": 500000,
"total_gc_pause_ns": 10000000,
"gc_cpu_fraction": 0.01,
"num_goroutines": 50,
"log_subscribers": 2
}

Get log entries with pagination.

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

Query Parameters:

Parameter Type Default Description
limit int 100 Maximum entries
offset int 0 Skip entries
level string - Filter by level (info, error, warn, debug)

Response:

{
"entries": [
{
"timestamp": "2024-01-15T10:00:00Z",
"level": "info",
"message": "GET https://api.example.com/v1/users",
"fields": {
"method": "GET",
"url": "api.example.com/v1/users",
"status_code": 200,
"duration_ms": 150,
"action": "server"
}
}
],
"total": 1500,
"limit": 100,
"offset": 0
}

Stream logs in real-time using Server-Sent Events.

GET /api/v1/logs/stream

Response: text/event-stream

data: {"type": "connected"}
data: {"timestamp": "2024-01-15T10:00:00Z", "level": "info", "message": "GET https://api.example.com/v1/users"}
data: {"timestamp": "2024-01-15T10:00:01Z", "level": "error", "message": "Connection failed", "fields": {"error": "timeout"}}

JavaScript Example:

const eventSource = new EventSource('/api/v1/logs/stream');
eventSource.onmessage = (event) => {
const log = JSON.parse(event.data);
console.log(`[${log.level}] ${log.message}`);
};
eventSource.onerror = () => {
console.log('Connection lost, reconnecting...');
};

Get cache statistics.

GET /api/v1/cache/stats

Response:

{
"enabled": true,
"storage_type": "memory",
"hit_rate": 0.75,
"total_requests": 5000,
"cache_hits": 3750,
"cache_misses": 1250,
"entries": 500,
"size_bytes": 52428800,
"max_size_bytes": 104857600
}

Get cached entries.

GET /api/v1/cache/entries?limit=100&offset=0&domain=example.com

Delete a specific cache entry.

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

Clear all cache entries.

POST /api/v1/cache/clear

Response:

{
"status": "cleared"
}

Get mesh network status.

GET /api/v1/mesh/status

Response:

{
"status": "running",
"enabled": true,
"peer_id": "peer-abc123",
"virtual_ip": "10.100.0.2",
"network_id": "net-xyz789",
"network_cidr": "10.100.0.0/24",
"peer_name": "laptop-work",
"peer_count": 5,
"connected_peers": 3,
"direct_connections": 2,
"relayed_connections": 1,
"bytes_sent": 10485760,
"bytes_received": 20971520,
"packets_sent": 10000,
"packets_received": 15000,
"uptime": "2h30m15s"
}

Get detailed mesh statistics.

GET /api/v1/mesh/stats

Enable mesh networking.

POST /api/v1/mesh/enable

Response:

{
"status": "enabled"
}

Disable mesh networking.

POST /api/v1/mesh/disable

Response:

{
"status": "disabled"
}

Get all known peers in the mesh.

GET /api/v1/mesh/peers

Response:

[
{
"id": "peer-xyz789",
"name": "desktop-home",
"virtual_ip": "10.100.0.3",
"virtual_mac": "02:00:00:00:00:03",
"public_key": "base64...",
"status": "connected",
"connection_type": "direct",
"latency_ms": 5,
"last_seen": "2024-01-15T10:00:00Z",
"joined_at": "2024-01-01T00:00:00Z",
"bytes_sent": 1048576,
"bytes_received": 2097152,
"endpoints": [
{
"address": "192.168.1.50",
"port": 51820,
"type": "lan",
"priority": 100
}
],
"metadata": {
"os": "linux",
"version": "1.0.0"
}
}
]

Get only currently connected peers.

GET /api/v1/mesh/peers/connected

Get details of a specific peer.

GET /api/v1/mesh/peers/{id}

Get mesh routing table.

GET /api/v1/mesh/routes

Response:

[
{
"dest_peer_id": "peer-xyz789",
"dest_ip": "10.100.0.3",
"next_hop": "",
"type": "direct",
"metric": 1,
"latency_ms": 5,
"hop_count": 1,
"last_updated": "2024-01-15T10:00:00Z",
"active": true
},
{
"dest_peer_id": "peer-abc456",
"dest_ip": "10.100.0.4",
"next_hop": "peer-xyz789",
"type": "relayed",
"metric": 2,
"latency_ms": 25,
"hop_count": 2,
"last_updated": "2024-01-15T09:55:00Z",
"active": true
}
]

Get mesh network information.

GET /api/v1/mesh/network

Response:

{
"network_id": "net-xyz789",
"network_cidr": "10.100.0.0/24",
"peer_name": "laptop-work",
"local_peer_id": "peer-abc123",
"virtual_ip": "10.100.0.2"
}

The client exposes Go’s pprof endpoints for debugging and profiling.

Endpoint Description
GET /debug/pprof/ Profiling index page
GET /debug/pprof/cmdline Command line arguments
GET /debug/pprof/profile CPU profile (30s default)
GET /debug/pprof/heap Heap memory profile
GET /debug/pprof/goroutine Goroutine stack traces
GET /debug/pprof/allocs Memory allocation profile
GET /debug/pprof/block Block contention profile
GET /debug/pprof/mutex Mutex contention profile
GET /debug/pprof/threadcreate Thread creation profile
Terminal window
# Get CPU profile (30 seconds)
curl http://localhost:7383/debug/pprof/profile > cpu.prof
# Get heap profile
curl http://localhost:7383/debug/pprof/heap > heap.prof
# Analyze with go tool
go tool pprof cpu.prof