Create and manage tunnels programmatically with the stunl gRPC API
stunl exposes two APIs. The gRPC API is what the stunl CLI and SDKs use for low-latency streaming — tunnel lifecycle, request/response framing, real-time events. The REST API at api.stunl.com is for everything else: account management, tunnel CRUD, webhooks, Inspector captures, and integrations from any HTTPS-capable client.
| REST API base URL | https://api.stunl.com |
| gRPC server | portal.stunl.com:9090 |
| Protocol | HTTPS (REST) / gRPC over TLS |
| Authentication | Authorization: Bearer <token> (REST) / API key in x-api-key metadata (gRPC) |
Proto Files
The complete protobuf definitions are available at stunl.com-proto
The REST API at https://api.stunl.com is OpenAPI-compatible and works from any HTTPS client. Responses are JSON; sensitive data uses Cache-Control: no-store. All endpoints (except /health and /api/version) require an Authorization: Bearer <token> header.
$ curl https://api.stunl.com/health
{"status":"healthy"}
$ curl https://api.stunl.com/api/version
{"service":"portal","version":"5.35.0","git_sha":"abc1234"}
$ curl -X POST https://api.stunl.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"hunter2"}'
{"access_token":"eyJ…","refresh_token":"eyJ…","expires_in":3600}
$ curl https://api.stunl.com/api/account \
-H "Authorization: Bearer $TOKEN"
Notes
/api/admin/*) are not exposed on api.stunl.com — they live on portal.stunl.com and require admin auth./v1/… versioned path prefix is reserved for the next major API revision; today everything lives under /api/… on both api.stunl.com and portal.stunl.com.For the gRPC API, include your API key in the metadata.
import (
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
// Create gRPC connection with API key
func connectWithAuth(apiKey string) (*grpc.ClientConn, error) {
// Add API key to context
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"x-api-key", apiKey,
)
conn, err := grpc.DialContext(ctx,
"portal.stunl.com:9090",
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
)
return conn, err
}
| Service | Purpose |
|---|---|
AuthService |
Authentication, API key validation, session management |
TunnelService |
Tunnel lifecycle (create, destroy, list) |
HTTPTunnelService |
HTTP request/response streaming |
TCPTunnelService |
TCP data streaming |
UDPTunnelService |
UDP packet forwarding |
ManagementService |
Health checks, metrics, admin operations |
OAuthService |
OAuth provider integration |
The main service for creating and managing tunnels.
Create a new tunnel and establish connection.
message CreateTunnelRequest {
string tunnel_id = 1; // Custom tunnel ID (optional)
TunnelType tunnel_type = 2; // HTTP, TCP, UDP
int32 local_port = 3; // Local port to tunnel
string local_host = 4; // Local host (default: localhost)
string domain = 5; // Custom domain (optional)
bool use_root_domain = 6; // Use root domain
string password = 7; // Basic auth password
int32 preferred_port = 8; // Reserved public port
TunnelOAuthConfig oauth = 9; // OAuth configuration
}
message CreateTunnelResponse {
string tunnel_id = 1; // Assigned tunnel ID
string public_url = 2; // Public HTTPS URL
string public_addr = 3; // Public hostname
int32 public_port = 4; // Public port (for TCP/UDP)
TunnelType tunnel_type = 5; // Tunnel type
int32 local_port = 6; // Local port
}
enum TunnelType {
TUNNEL_TYPE_UNSPECIFIED = 0;
TUNNEL_TYPE_HTTP = 1;
TUNNEL_TYPE_TCP = 2;
TUNNEL_TYPE_UDP = 3;
TUNNEL_TYPE_WEBSOCKET = 4;
}
Terminate an active tunnel.
message DestroyTunnelRequest {
string tunnel_id = 1;
}
message DestroyTunnelResponse {
bool success = 1;
string message = 2;
}
List all active tunnels for the authenticated user.
message ListTunnelsRequest {}
message ListTunnelsResponse {
repeated TunnelInfo tunnels = 1;
}
message TunnelInfo {
string tunnel_id = 1;
string public_url = 2;
TunnelType tunnel_type = 3;
int32 local_port = 4;
int64 created_at = 5;
int64 bytes_in = 6;
int64 bytes_out = 7;
}
Configure OAuth authentication for your tunnels.
message TunnelOAuthConfig {
AuthProvider provider = 1; // OAuth provider
repeated string allowed_domains = 2; // Email domain whitelist
repeated string allowed_emails = 3; // Specific email whitelist
repeated string github_orgs = 4; // GitHub org restrictions
repeated string github_teams = 5; // GitHub team restrictions
}
enum AuthProvider {
AUTH_PROVIDER_UNSPECIFIED = 0;
AUTH_PROVIDER_GITHUB = 1;
AUTH_PROVIDER_GOOGLE = 2;
AUTH_PROVIDER_MICROSOFT = 3;
}
Once a tunnel is created, data flows through bidirectional gRPC streams.
service HTTPTunnelService {
// Bidirectional stream for HTTP requests/responses
rpc StreamHTTP(stream HTTPRequest) returns (stream HTTPResponse);
}
message HTTPRequest {
string request_id = 1;
string method = 2;
string path = 3;
map<string, string> headers = 4;
bytes body = 5;
}
message HTTPResponse {
string request_id = 1;
int32 status_code = 2;
map<string, string> headers = 3;
bytes body = 4;
}
service TCPTunnelService {
// Bidirectional stream for raw TCP data
rpc StreamTCP(stream TCPData) returns (stream TCPData);
}
message TCPData {
string connection_id = 1;
bytes data = 2;
bool eof = 3;
}
The API uses standard gRPC status codes with additional error details.
| Code | Meaning |
|---|---|
UNAUTHENTICATED |
Invalid or missing API key |
PERMISSION_DENIED |
Feature not available in your tier |
RESOURCE_EXHAUSTED |
Tunnel or bandwidth limit exceeded |
ALREADY_EXISTS |
Tunnel ID already in use |
NOT_FOUND |
Tunnel not found |
UNAVAILABLE |
Server temporarily unavailable |
| Operation | Pro |
|---|---|
| CreateTunnel / min | 30 |
| API requests / min | 200 |
| Concurrent connections | 20 |
Official and community client libraries are available: