getxpos

Go SDK for XPOS — instant public URLs via SSH tunnels. Zero dependencies.
Features
- Zero dependencies — uses only the Go standard library
- CLI + programmatic API — use from terminal or embed in your tooling
- HTTP & TCP tunnels — expose web apps or any TCP service
- Anonymous or authenticated — works instantly, tokens unlock more features
- Reserved subdomains & custom domains — with Pro/Business plans
- Context-aware — idiomatic Go with
context.Context support
Quick Start
# Install CLI
go install github.com/getxpos/go/cmd/xpos@latest
# Run (anonymous tunnel, random subdomain, 3hr expiry)
xpos --port 3000
Installation
# CLI tool
go install github.com/getxpos/go/cmd/xpos@latest
# Library (add to your Go project)
go get github.com/getxpos/go
Authentication
Get a token from xpos.dev/dashboard/tokens, then either:
# Pass directly
xpos --port 3000 --token tk_xxx
# Or set environment variable
export XPOS_TOKEN=tk_xxx
xpos --port 3000
CLI Usage
# Anonymous tunnel (random subdomain, 3hr expiry)
xpos --port 3000
# Authenticated (random subdomain, 10hr expiry)
xpos --port 3000 --token tk_xxx
# Reserved subdomain (Pro+)
xpos --port 3000 --token tk_xxx --subdomain myapp
# Custom domain (Business)
xpos --port 8000 --token tk_xxx --domain tunnel.example.com
# Port-based TCP tunnel (Pro+)
xpos --port 5432 --token tk_xxx --mode tcp
CLI Options
| Option |
Description |
Default |
--port <port> |
Local port to expose |
(required) |
--host <host> |
Local host to forward |
127.0.0.1 |
--token <token> |
Auth token (or XPOS_TOKEN env) |
— |
--subdomain <name> |
Reserved subdomain (requires token) |
— |
--domain <domain> |
Custom domain (requires token) |
— |
--mode <mode> |
http or tcp |
http |
--server <host> |
SSH server hostname |
go.xpos.dev |
-h, --help |
Show help |
— |
-v, --version |
Show version |
— |
Programmatic API
import xpos "github.com/getxpos/go/xpos"
// HTTP tunnel (convenience function)
tunnel, err := xpos.Connect(ctx, xpos.WithPort(3000), xpos.WithToken("tk_xxx"))
if err != nil {
log.Fatal(err)
}
fmt.Println(tunnel.URL) // https://abc.xpos.to
fmt.Println(tunnel.ExpiresAt) // 2026-03-28T10:30:45Z
defer tunnel.Close()
<-tunnel.Done()
TCP Tunnel
tcp, err := xpos.Connect(ctx,
xpos.WithPort(5432),
xpos.WithToken("tk_xxx"),
xpos.WithMode("tcp"),
)
fmt.Println(tcp.URL) // 1.2.3.4:54321
defer tcp.Close()
Manual Control
For more control, use NewTunnel + Start separately:
t, err := xpos.NewTunnel(
xpos.WithPort(3000),
xpos.WithToken("tk_xxx"),
xpos.WithSubdomain("myapp"),
xpos.WithOnConnect(func(url, expiresAt string) {
fmt.Printf("Connected: %s\n", url)
}),
xpos.WithOnClose(func(exitCode int) {
fmt.Printf("Tunnel closed (exit %d)\n", exitCode)
}),
)
if err != nil {
log.Fatal(err)
}
if err := t.Start(ctx); err != nil {
log.Fatal(err)
}
defer t.Close()
<-t.Done()
Options
| Option |
Type |
Description |
WithPort(int) |
int |
Local port to expose (required) |
WithHost(string) |
string |
Local host (default: "127.0.0.1") |
WithToken(string) |
string |
Auth token (or reads XPOS_TOKEN env) |
WithSubdomain(string) |
string |
Reserved subdomain |
WithDomain(string) |
string |
Custom domain |
WithMode(string) |
string |
"http" or "tcp" (default: "http") |
WithServer(string) |
string |
SSH server (default: "go.xpos.dev") |
WithSSHPort(int) |
int |
SSH port (default: 443) |
WithConnectTimeout(time.Duration) |
time.Duration |
Connect timeout (default: 15s) |
WithOnConnect(func) |
func(url, expiresAt string) |
Called on connect |
WithOnOutput(func) |
func(text string) |
Called with raw SSH output |
WithOnClose(func) |
func(exitCode int) |
Called on disconnect |
Tunnel Methods
| Method |
Description |
Start(ctx) |
Start the tunnel (blocks until connected) |
Close() |
Gracefully close the tunnel |
Wait() |
Block until the tunnel closes |
Done() |
Returns a channel closed when tunnel ends |
Requirements
- Go >= 1.18
- SSH client in PATH (
ssh command — comes pre-installed on macOS, Linux, and Windows 10+)
Troubleshooting
"SSH not found" — Install OpenSSH. On Windows: Settings > Apps > Optional Features > OpenSSH Client.
Connection timeout — Check your firewall allows outbound connections on port 443.
"subdomain requires a token" — Reserved subdomains need a Pro plan. Get a token from your dashboard.
Links
License
MIT