The Tailrelay Web UI serves an HTTP/JSON API on port 8021 for managing
Tailscale connectivity, tailscale serve / tailscale funnel relays,
configuration backups, and container logs.
The reference below is rendered directly from
docs/openapi.yaml,
the project's OpenAPI specification.
Tailrelay Web UI API 0.9.3
HTTP/JSON API served by the Tailrelay Go Web UI on port 8021. It manages
Tailscale connectivity and tailscale serve / tailscale funnel relays,
plus configuration backups and container logs.
This document is generated from the actual route registration in
webui/internal/web/server.go and the handler implementations under
webui/internal/handlers/. Where the code and the README diverge, this
spec follows the code.
Authentication
Every /api/* route except the public ones (GET /api/info,
GET /api/auth/status, POST /api/auth/setup, POST /api/auth/login)
requires authentication. RequireAuth accepts either:
- a Bearer token —
Authorization: Bearer <token>matching the static token written to/var/lib/tailscale/.webui_tokenon first start; or - a session cookie —
tailrelay_session(HttpOnly, SameSite=Strict,Securewhen served over TLS, 24h expiry). The cookie is set byPOST /api/auth/setup,POST /api/auth/login, and byGET /api/tailscale/pollonce the node is connected.
On failure, /api/* paths return 401 with a JSON body
{"error":"unauthorized"}; non-API paths receive a 303 redirect to
/login.
Error response shapes
Error bodies are not uniform:
- Serve handlers use JSON
{"status":"success"|"pending"|"error","message":...}for success (200), tailscale-not-ready (202), and funnel-not-allowed (409). - Most other errors are emitted via Go's
http.Error, producing a plain-text body (text/plain) with the status code — not JSON. - The auth middleware
401is JSON{"error":"unauthorized"}.
Individual operations document their concrete responses below.
Legacy endpoints (removed)
/api/caddy/* and /api/socat/* are retained only as 410 Gone shims and
are intentionally omitted from this spec.
Servers
| Description | URL |
|---|---|
| Local access | http://localhost:8021 |
| Tailscale MagicDNS access (once connected and HTTPS is enabled) | https://{hostname}.{tailnet}.ts.net:8021 |
Info
GET /api/info
Build metadata
Description
Returns version and commit of the running binary. Public.
Responses
{
"version": "string",
"commit": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"version": {
"type": "string"
},
"commit": {
"type": "string"
}
},
"required": [
"version",
"commit"
]
}
Auth
GET /api/auth/status
Setup / authentication status
Description
Whether first-run setup is needed and whether the caller has a valid session. Public.
Responses
{
"needsSetup": true,
"authenticated": true
}
Schema of the response body
{
"type": "object",
"properties": {
"needsSetup": {
"type": "boolean"
},
"authenticated": {
"type": "boolean"
}
},
"required": [
"needsSetup",
"authenticated"
]
}
POST /api/auth/setup
First-run admin password setup
Description
Creates the initial admin password (bcrypt) and sets a session cookie. Fails if setup was already completed. Public.
Request body
{
"password": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
}
Responses
{
"success": true
}
Schema of the response body
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
}
Response headers
| Name | Description | Schema |
|---|---|---|
Set-Cookie |
tailrelay_session=...; HttpOnly; SameSite=Strict | string |
Refer to the common response description: PlainTextError.
Refer to the common response description: PlainTextError.
POST /api/auth/login
Password login
Description
Validates the admin password and sets a session cookie. Public.
Request body
{
"password": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
}
Responses
{
"success": true
}
Schema of the response body
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
}
Response headers
| Name | Description | Schema |
|---|---|---|
Set-Cookie |
string |
Refer to the common response description: PlainTextError.
Refer to the common response description: PlainTextError.
POST /api/auth/logout
Log out
Description
Clears the session cookie.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"success": true
}
Schema of the response body
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
}
Refer to the common response description: Unauthorized.
POST /api/auth/change-password
Change admin password
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"currentPassword": "string",
"newPassword": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"currentPassword": {
"type": "string"
},
"newPassword": {
"type": "string"
}
},
"required": [
"currentPassword",
"newPassword"
]
}
Responses
{
"success": true
}
Schema of the response body
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
}
Refer to the common response description: PlainTextError.
Refer to the common response description: PlainTextError.
Status
GET /api/status
Aggregate system status
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"timestamp": "2022-04-13T15:42:05.901Z",
"version": "string",
"services": {
"webui": "running",
"tailscale": {
"connected": true,
"state": "Running"
},
"serve": {
"https_relays": 0,
"tcp_relays": 0
}
}
}
Schema of the response body
{
"type": "object",
"description": "Aggregate status from DashboardHandler.APIStatus.",
"properties": {
"timestamp": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "string",
"description": "Hardcoded server-reported version string."
},
"services": {
"type": "object",
"properties": {
"webui": {
"type": "string",
"examples": [
"running"
]
},
"tailscale": {
"type": "object",
"properties": {
"connected": {
"type": "boolean"
},
"state": {
"type": "string",
"examples": [
"Running"
]
}
}
},
"serve": {
"type": "object",
"properties": {
"https_relays": {
"type": "integer"
},
"tcp_relays": {
"type": "integer"
}
}
}
}
}
}
}
Refer to the common response description: Unauthorized.
GET /api/targets
List configured targets
Description
Returns the raw contents of targets.json. Returns [] if the file
is missing or unparseable.
Entries are opaque to the backend, but the Web UI understands these
optional fields when offering a preset target in the Add Relay modal:
target_name, app_id, host, port, type (relay/tcp/
proxy/https), protocol (http/https/tcp), and icon_url
(an http/https URL or small data:image/* URI) which seeds the
icon field when the preset is selected.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
{}
]
Schema of the response body
{
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
Refer to the common response description: Unauthorized.
Tailscale
POST /api/tailscale/login
Begin interactive Tailscale login
Description
Starts an interactive login and returns the auth URL to visit. If a
control server is persisted via POST /api/tailscale/control-server/update,
it's automatically passed as tailscale login --login-server=<url>.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"status": "success",
"auth_url": "string",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"auth_url": {
"type": "string",
"format": "uri"
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
POST /api/tailscale/login-with-key
Non-interactive login with a pre-shared auth key
Description
Authenticates using a tskey- (Tailscale) or hskey- (Headscale)
auth key, then asynchronously reconciles relays. If a control server
is persisted via POST /api/tailscale/control-server/update, it's
automatically passed as tailscale up --login-server=<url>.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"auth_key": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"auth_key": {
"type": "string",
"description": "Must be non-empty and start with `tskey-` (Tailscale) or `hskey-` (Headscale)."
}
},
"required": [
"auth_key"
]
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: TailscaleError.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
POST /api/tailscale/logout
Deauthorize (log out) the node
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
POST /api/tailscale/connect
Bring Tailscale up
Description
Runs tailscale up, then asynchronously reconciles relays.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
POST /api/tailscale/disconnect
Bring Tailscale down
Description
Runs tailscale down.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
POST /api/tailscale/hostname
Change the Tailscale hostname
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"hostname": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"hostname": {
"type": "string"
}
},
"required": [
"hostname"
]
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: TailscaleError.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
GET /api/tailscale/control-server
Get the persisted control server
Description
Returns the custom control server URL (e.g. a self-hosted Headscale
instance) persisted via POST /api/tailscale/control-server/update.
An empty value means Tailscale's default control plane.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"control_server": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"control_server": {
"type": "string",
"description": "Persisted custom control server URL (e.g. a self-hosted Headscale\ninstance). Empty means Tailscale's default control plane.\n"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
POST /api/tailscale/control-server/update
Set the persisted control server
Description
Validates and persists a custom control server URL to webui.yaml.
It's automatically applied to subsequent POST /api/tailscale/login
and POST /api/tailscale/login-with-key calls. Has no effect on a
device that's already registered until it's logged out and
re-authenticated. Pass an empty string to reset to Tailscale's
default control plane.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"control_server": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"control_server": {
"type": "string",
"description": "Must be a valid `http://` or `https://` URL, or empty to reset to\nTailscale's default control plane.\n"
}
}
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: TailscaleError.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: TailscaleError.
GET /api/tailscale/status
Tailscale status summary
Description
Returns a StatusSummary. On daemon error the handler still responds
200 with a degraded object (Connected:false, BackendState:"Unknown").
Note: the Go struct has no JSON tags, so field names are capitalized.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"Connected": true,
"BackendState": "Running",
"Hostname": "string",
"MagicDNSName": "string",
"IPv4": "string",
"IPv6": "string",
"TailnetName": "string",
"Version": "string",
"PeerCount": 0,
"ActivePeers": 0,
"Health": [
"string"
],
"LastCheck": "2022-04-13T15:42:05.901Z"
}
Schema of the response body
{
"type": "object",
"description": "The Go struct has no JSON tags, so keys are the capitalized Go field\nnames.\n",
"properties": {
"Connected": {
"type": "boolean"
},
"BackendState": {
"type": "string",
"examples": [
"Running"
]
},
"Hostname": {
"type": "string"
},
"MagicDNSName": {
"type": "string"
},
"IPv4": {
"type": "string"
},
"IPv6": {
"type": "string"
},
"TailnetName": {
"type": "string"
},
"Version": {
"type": "string"
},
"PeerCount": {
"type": "integer"
},
"ActivePeers": {
"type": "integer"
},
"Health": {
"type": "array",
"items": {
"type": "string"
}
},
"LastCheck": {
"type": "string",
"format": "date-time"
}
}
}
Refer to the common response description: Unauthorized.
GET /api/tailscale/peers
List Tailscale peers
Description
Note: the PeerInfo struct has no JSON tags, so field names are capitalized.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
{
"Hostname": "string",
"DNSName": "string",
"OS": "string",
"IPv4": "string",
"IPv6": "string",
"TailscaleIPs": [
"string"
],
"UserEmail": "string",
"Active": true,
"Online": true,
"LastSeen": "2022-04-13T15:42:05.901Z",
"Relay": "string",
"ExitNode": true
}
]
Schema of the response body
{
"type": "array",
"items": {
"$ref": "#/components/schemas/PeerInfo"
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: TailscaleError.
GET /api/tailscale/poll
Poll connection state
Description
Reports whether the node is connected. When connected, sets the tailrelay_session cookie.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"connected": true,
"timestamp": 110
}
Schema of the response body
{
"type": "object",
"properties": {
"connected": {
"type": "boolean"
},
"timestamp": {
"type": "integer",
"format": "int64",
"description": "Unix timestamp (seconds)."
}
},
"required": [
"connected",
"timestamp"
]
}
Response headers
| Name | Description | Schema |
|---|---|---|
Set-Cookie |
Set only when connected | string |
Refer to the common response description: Unauthorized.
Refer to the common response description: TailscaleError.
GET /api/tailscale/networking
Networking preferences summary
Description
Returns the current exit-node, subnet route, and SSH preferences via
tailscale set's underlying prefs. AdvertiseExitNode and the
exit-node CIDRs (0.0.0.0/0, ::/0) are derived from the same
AdvertiseRoutes preference used for subnet routes; the AdvertiseRoutes
field returned here excludes those two, exposing only custom subnet routes.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"AdvertiseExitNode": true,
"ExitNodeAllowLANAccess": true,
"AdvertiseRoutes": [
"string"
],
"AcceptRoutes": true,
"ExitNode": "string",
"SSH": true,
"UserspaceNetworking": true
}
Schema of the response body
{
"type": "object",
"description": "The Go struct has no JSON tags, so keys are capitalized Go field names.",
"properties": {
"AdvertiseExitNode": {
"type": "boolean"
},
"ExitNodeAllowLANAccess": {
"type": "boolean"
},
"AdvertiseRoutes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Custom subnet routes only; the exit-node CIDRs (0.0.0.0/0, ::/0) are excluded."
},
"AcceptRoutes": {
"type": "boolean"
},
"ExitNode": {
"type": "string",
"description": "IP of the currently-selected exit node, or empty if none is in use."
},
"SSH": {
"type": "boolean"
},
"UserspaceNetworking": {
"type": "boolean",
"description": "True when tailscaled is running with --tun=userspace-networking (Tailrelay's only mode). When true, routing this node's own traffic through a peer exit node is impossible — the UI only offers \"Run as exit node\" and \"None\", and the update endpoint rejects a non-empty `exit_node` with 409. Advertised exit-node usage from other peers still works.\n"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: TailscaleError.
POST /api/tailscale/networking/update
Update networking preferences
Description
Applies a partial update via a single tailscale set invocation.
Only fields present in the request body are changed; omitted fields
are left untouched. advertise_routes entries must be valid CIDRs
with no host bits set; 0.0.0.0/0 and ::/0 are rejected since
exit-node advertisement is controlled via advertise_exit_node instead.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"advertise_exit_node": true,
"exit_node_allow_lan_access": true,
"advertise_routes": [
"string"
],
"accept_routes": true,
"exit_node": "string",
"ssh": true
}
Schema of the request body
{
"type": "object",
"description": "All fields are optional; only fields present in the request are changed.",
"properties": {
"advertise_exit_node": {
"type": "boolean"
},
"exit_node_allow_lan_access": {
"type": "boolean"
},
"advertise_routes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Replaces the full set of advertised subnet routes. Each entry must be a CIDR with no host bits set (e.g. `192.168.1.0/24`). `0.0.0.0/0` and `::/0` are rejected; use `advertise_exit_node` instead. Pass `[]` to clear all advertised routes.\n"
},
"accept_routes": {
"type": "boolean"
},
"exit_node": {
"type": "string",
"description": "IP or MagicDNS name of the peer to use as an exit node, or `\"\"` to clear. Under userspace-networking mode a non-empty value is rejected with 409 since the node cannot install host routes to redirect its own traffic through a peer.\n"
},
"ssh": {
"type": "boolean"
}
}
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
}
}
Refer to the common response description: TailscaleError.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Schema of the response body
{
"description": "Tailscale operation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TailscaleError"
}
}
}
}
Refer to the common response description: TailscaleError.
Serve HTTPS
GET /api/serve/https/list
List web relays
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
null
]
Schema of the response body
{
"type": "array",
"items": {
"$ref": "#/components/schemas/ServeRelayWithRunning"
}
}
Refer to the common response description: Unauthorized.
GET /api/serve/https/get
Get one web relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the response body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Refer to the common response description: Unauthorized.
POST /api/serve/https/create
Create a web relay
Description
Accepts either a JSON ServeRelay body or multipart/form-data. Type is forced to https; ID defaults to https-<listen_port>. The listener uses HTTPS with Tailscale's control plane and HTTP with a custom control server.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
{
"id": "string",
"port": "string",
"target": "string",
"tls": "string",
"enabled": "string",
"autostart": "string",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "Multipart form accepted by HTTPS create/update.",
"properties": {
"id": {
"type": "string"
},
"port": {
"type": "string",
"description": "Listen port."
},
"target": {
"type": "string",
"description": "Target as `host:port`."
},
"tls": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"enabled": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"autostart": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"icon_url": {
"type": "string",
"description": "Optional icon URL or small `data:image/*` URI (see `ServeRelay.icon_url`).\nPersisted onto the relay and rendered on its card.\n"
}
}
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
PUT /api/serve/https/update
Update an HTTPS relay
Description
JSON or multipart; id is required.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
{
"id": "string",
"port": "string",
"target": "string",
"tls": "string",
"enabled": "string",
"autostart": "string",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "Multipart form accepted by HTTPS create/update.",
"properties": {
"id": {
"type": "string"
},
"port": {
"type": "string",
"description": "Listen port."
},
"target": {
"type": "string",
"description": "Target as `host:port`."
},
"tls": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"enabled": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"autostart": {
"type": "string",
"description": "Bool-ish (true/1/on/yes)."
},
"icon_url": {
"type": "string",
"description": "Optional icon URL or small `data:image/*` URI (see `ServeRelay.icon_url`).\nPersisted onto the relay and rendered on its card.\n"
}
}
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: PlainTextError.
DELETE /api/serve/https/delete
Delete an HTTPS relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
POST /api/serve/https/toggle
Enable/disable an HTTPS relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"enabled": true
}
Schema of the request body
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
},
"required": [
"id",
"enabled"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
Serve TCP
GET /api/serve/tcp/list
List TCP relays
Description
Unlike HTTPS/funnel lists, each item wraps the relay: {relay, running}.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
{
"relay": {
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
},
"running": true
}
]
Schema of the response body
{
"type": "array",
"items": {
"$ref": "#/components/schemas/TCPRelayStatus"
}
}
Refer to the common response description: Unauthorized.
GET /api/serve/tcp/get
Get one TCP relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the response body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Refer to the common response description: Unauthorized.
POST /api/serve/tcp/create
Create a TCP relay
Description
JSON ServeRelay. Requires listen_port, target_host, target_port. Type forced to tcp; ID defaults to tcp-<listen_port>.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: PlainTextError.
PUT /api/serve/tcp/update
Update a TCP relay
Description
JSON ServeRelay; id required.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: PlainTextError.
DELETE /api/serve/tcp/delete
Delete a TCP relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
POST /api/serve/tcp/toggle
Enable/disable a TCP relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"enabled": true
}
Schema of the request body
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
},
"required": [
"id",
"enabled"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
Serve
POST /api/serve/reload
Reconcile all enabled relays
Description
Reconciles the live tailscale serve configuration against all enabled
relays. This is the only reconcile endpoint; the per-type /reconcile,
/start, /stop, /restart endpoints described in older docs do not
exist.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: FunnelNotAllowed.
Refer to the common response description: PlainTextError.
Serve Funnel
GET /api/serve/funnel/list
List funnel relays
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
null
]
Schema of the response body
{
"type": "array",
"items": {
"$ref": "#/components/schemas/ServeRelayWithRunning"
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
GET /api/serve/funnel/get
Get one funnel relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the response body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Refer to the common response description: Unauthorized.
POST /api/serve/funnel/create
Create a funnel relay
Description
JSON ServeRelay. listen_port must be a funnel-eligible port
(443, 8443, or 10000). funnel_transport selects https or tcp.
Type forced to funnel; ID defaults to funnel-<listen_port>.
Requires the tailnet funnel node attribute, else 409.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: FunnelNotAllowed.
Refer to the common response description: PlainTextError.
PUT /api/serve/funnel/update
Update a funnel relay
Description
JSON ServeRelay; id required.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"type": "https",
"listen_port": 0,
"target_host": "string",
"target_port": 0,
"target_https": true,
"enabled": true,
"autostart": true,
"funnel_transport": "https",
"icon_url": "string"
}
Schema of the request body
{
"type": "object",
"description": "A relay backed by `tailscale serve` or `tailscale funnel`.",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"https",
"tcp",
"funnel"
]
},
"listen_port": {
"type": "integer"
},
"target_host": {
"type": "string"
},
"target_port": {
"type": "integer"
},
"target_https": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"autostart": {
"type": "boolean"
},
"funnel_transport": {
"type": "string",
"enum": [
"https",
"tcp"
],
"description": "Only meaningful when `type` is `funnel`."
},
"icon_url": {
"type": "string",
"description": "Optional icon rendered on the relay card instead of the default\ntype glyph. Accepts an `http`/`https` URL or a small\n`data:image/*` URI (capped at 256 KiB to avoid bloating\n`serve_relays.json`). Empty/omitted means \"use the default glyph\".\nOther schemes (e.g. `javascript:`) are rejected with 400.\n"
}
},
"required": [
"id",
"type",
"listen_port",
"target_host",
"target_port",
"target_https",
"enabled",
"autostart"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: ServePending.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeMethodError.
Refer to the common response description: FunnelNotAllowed.
Refer to the common response description: PlainTextError.
DELETE /api/serve/funnel/delete
Delete a funnel relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
id |
query | string | No | Relay identifier |
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
POST /api/serve/funnel/toggle
Enable/disable a funnel relay
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"id": "string",
"enabled": true
}
Schema of the request body
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
},
"required": [
"id",
"enabled"
]
}
Responses
Refer to the common response description: ServeSuccess.
Refer to the common response description: Unauthorized.
Refer to the common response description: ServeNotFound.
Refer to the common response description: ServeMethodError.
Refer to the common response description: FunnelNotAllowed.
Backup
GET /api/backup/list
List backups
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
[
{
"filename": "string",
"size": 6,
"timestamp": "2022-04-13T15:42:05.901Z",
"metadata": {
"timestamp": "2022-04-13T15:42:05.901Z",
"version": "string",
"hostname": "string",
"backup_type": "full"
}
}
]
Schema of the response body
{
"type": "array",
"items": {
"$ref": "#/components/schemas/BackupInfo"
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
POST /api/backup/create
Create a backup
Description
backup_type defaults to full if absent/empty.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"backup_type": "full"
}
Schema of the request body
{
"type": "object",
"properties": {
"backup_type": {
"type": "string",
"enum": [
"full",
"config-only"
],
"default": "full"
}
}
}
Responses
{
"status": "success",
"message": "string",
"backup_path": "string",
"filename": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
},
"backup_path": {
"type": "string"
},
"filename": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
POST /api/backup/restore
Restore from a backup
Description
Restores the named backup, then reconciles serve.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"filename": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"filename": {
"type": "string"
}
},
"required": [
"filename"
]
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
DELETE /api/backup/delete
Delete a backup
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
filename |
query | string | No | Backup filename (`*.tar.gz`) |
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
POST /api/backup/rename
Rename a backup
Description
.tar.gz is appended to the new name if missing.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"old_filename": "string",
"new_filename": "string"
}
Schema of the request body
{
"type": "object",
"properties": {
"old_filename": {
"type": "string"
},
"new_filename": {
"type": "string"
}
},
"required": [
"old_filename",
"new_filename"
]
}
Responses
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
GET /api/backup/download
Download a backup
Description
Path-traversal-guarded. Streams the .tar.gz archive.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
filename |
query | string | No | Backup filename (`*.tar.gz`) |
Responses
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ="
Schema of the response body
{
"type": "string",
"format": "binary"
}
Response headers
| Name | Description | Schema |
|---|---|---|
Content-Disposition |
attachment; filename=... | string |
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
POST /api/backup/upload
Upload a backup
Description
multipart/form-data, file field backup, max 32 MB. Filename must end in .tar.gz.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"backup": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ="
}
Schema of the request body
{
"type": "object",
"properties": {
"backup": {
"type": "string",
"format": "binary"
}
},
"required": [
"backup"
]
}
Responses
{
"status": "success",
"message": "string",
"filename": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
},
"filename": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Refer to the common response description: PlainTextError.
Logs
GET /api/logs
Historical logs and current level
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"logs": [
{
"timestamp": "2022-04-13T15:42:05.901Z",
"level": "string",
"message": "string",
"source": "string"
}
],
"level": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogEntry"
}
},
"level": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
GET /api/logs/stream
Live log stream (SSE)
Description
Server-Sent Events stream (text/event-stream). Emits
data: {"connected": true} first, then one data: <LogEntry JSON>
per log event.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
"string"
Schema of the response body
{
"type": "string"
}
Refer to the common response description: Unauthorized.
GET /api/logs/level
Get current log level
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Responses
{
"level": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"level": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
POST /api/logs/level
Set log level
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Session cookie set after setup/login or successful Tailscale poll. |
bearerToken |
header | string | N/A | No | Static token from `/var/lib/tailscale/.webui_token`. |
Request body
{
"level": "debug"
}
Schema of the request body
{
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": [
"debug",
"info",
"warn",
"error"
]
}
},
"required": [
"level"
]
}
Responses
{
"success": true,
"level": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"level": {
"type": "string"
}
}
}
Refer to the common response description: Unauthorized.
Schemas
AuthError
| Name | Type | Description |
|---|---|---|
error |
string |
AuthKeyRequest
| Name | Type | Description |
|---|---|---|
auth_key |
string | Must be non-empty and start with `tskey-` (Tailscale) or `hskey-` (Headscale). |
AuthStatus
| Name | Type | Description |
|---|---|---|
authenticated |
boolean | |
needsSetup |
boolean |
BackupCreateRequest
| Name | Type | Description |
|---|---|---|
backup_type |
string |
BackupCreateResult
| Name | Type | Description |
|---|---|---|
backup_path |
string | |
filename |
string | |
message |
string | |
status |
string |
BackupFilenameRequest
| Name | Type | Description |
|---|---|---|
filename |
string |
BackupInfo
| Name | Type | Description |
|---|---|---|
filename |
string | |
metadata |
BackupMetadata | |
size |
integer(int64) | |
timestamp |
string(date-time) |
BackupMetadata
| Name | Type | Description |
|---|---|---|
backup_type |
string | |
hostname |
string | |
timestamp |
string(date-time) | |
version |
string |
BackupRenameRequest
| Name | Type | Description |
|---|---|---|
new_filename |
string | |
old_filename |
string |
BackupUploadForm
| Name | Type | Description |
|---|---|---|
backup |
string(binary) |
BackupUploadResult
| Name | Type | Description |
|---|---|---|
filename |
string | |
message |
string | |
status |
string |
ChangePasswordRequest
| Name | Type | Description |
|---|---|---|
currentPassword |
string | |
newPassword |
string |
ControlServerRequest
| Name | Type | Description |
|---|---|---|
control_server |
string | Must be a valid `http://` or `https://` URL, or empty to reset to Tailscale's default control plane. |
ControlServerSummary
| Name | Type | Description |
|---|---|---|
control_server |
string | Persisted custom control server URL (e.g. a self-hosted Headscale instance). Empty means Tailscale's default control plane. |
HostnameRequest
| Name | Type | Description |
|---|---|---|
hostname |
string |
HTTPSRelayForm
| Name | Type | Description |
|---|---|---|
autostart |
string | Bool-ish (true/1/on/yes). |
enabled |
string | Bool-ish (true/1/on/yes). |
icon_url |
string | Optional icon URL or small `data:image/*` URI (see `ServeRelay.icon_url`). Persisted onto the relay and rendered on its card. |
id |
string | |
port |
string | Listen port. |
target |
string | Target as `host:port`. |
tls |
string | Bool-ish (true/1/on/yes). |
InfoResponse
| Name | Type | Description |
|---|---|---|
commit |
string | |
version |
string |
LogEntry
| Name | Type | Description |
|---|---|---|
level |
string | |
message |
string | |
source |
string | |
timestamp |
string(date-time) |
LogLevelRequest
| Name | Type | Description |
|---|---|---|
level |
string |
LogLevelResponse
| Name | Type | Description |
|---|---|---|
level |
string |
LogLevelSetResult
| Name | Type | Description |
|---|---|---|
level |
string | |
success |
boolean |
LogsResponse
| Name | Type | Description |
|---|---|---|
level |
string | |
logs |
Array<LogEntry> |
NetworkingSummary
| Name | Type | Description |
|---|---|---|
AcceptRoutes |
boolean | |
AdvertiseExitNode |
boolean | |
AdvertiseRoutes |
Array<string> | Custom subnet routes only; the exit-node CIDRs (0.0.0.0/0, ::/0) are excluded. |
ExitNode |
string | IP of the currently-selected exit node, or empty if none is in use. |
ExitNodeAllowLANAccess |
boolean | |
SSH |
boolean | |
UserspaceNetworking |
boolean | True when tailscaled is running with --tun=userspace-networking (Tailrelay's only mode). When true, routing this node's own traffic through a peer exit node is impossible — the UI only offers "Run as exit node" and "None", and the update endpoint rejects a non-empty `exit_node` with 409. Advertised exit-node usage from other peers still works. |
NetworkingUpdateRequest
| Name | Type | Description |
|---|---|---|
accept_routes |
boolean | |
advertise_exit_node |
boolean | |
advertise_routes |
Array<string> | Replaces the full set of advertised subnet routes. Each entry must be a CIDR with no host bits set (e.g. `192.168.1.0/24`). `0.0.0.0/0` and `::/0` are rejected; use `advertise_exit_node` instead. Pass `[]` to clear all advertised routes. |
exit_node |
string | IP or MagicDNS name of the peer to use as an exit node, or `""` to clear. Under userspace-networking mode a non-empty value is rejected with 409 since the node cannot install host routes to redirect its own traffic through a peer. |
exit_node_allow_lan_access |
boolean | |
ssh |
boolean |
PasswordRequest
| Name | Type | Description |
|---|---|---|
password |
string |
PeerInfo
| Name | Type | Description |
|---|---|---|
Active |
boolean | |
DNSName |
string | |
ExitNode |
boolean | True if this peer is advertising itself as an available exit node. |
Hostname |
string | |
IPv4 |
string | |
IPv6 |
string | |
LastSeen |
string(date-time) | |
Online |
boolean | |
OS |
string | |
Relay |
string | |
TailscaleIPs |
Array<string> | |
UserEmail |
string |
PollResult
| Name | Type | Description |
|---|---|---|
connected |
boolean | |
timestamp |
integer(int64) | Unix timestamp (seconds). |
ServeErrorResult
| Name | Type | Description |
|---|---|---|
message |
string | |
status |
string |
ServePendingResult
| Name | Type | Description |
|---|---|---|
message |
string | |
status |
string |
ServeRelay
| Name | Type | Description |
|---|---|---|
autostart |
boolean | |
enabled |
boolean | |
funnel_transport |
string | Only meaningful when `type` is `funnel`. |
icon_url |
string | Optional icon rendered on the relay card instead of the default type glyph. Accepts an `http`/`https` URL or a small `data:image/*` URI (capped at 256 KiB to avoid bloating `serve_relays.json`). Empty/omitted means "use the default glyph". Other schemes (e.g. `javascript:`) are rejected with 400. |
id |
string | |
listen_port |
integer | |
target_host |
string | |
target_https |
boolean | |
target_port |
integer | |
type |
string |
ServeRelayWithRunning
Type:
ServeResult
| Name | Type | Description |
|---|---|---|
message |
string | |
status |
string |
StatusSummary
| Name | Type | Description |
|---|---|---|
ActivePeers |
integer | |
BackendState |
string | |
Connected |
boolean | |
Health |
Array<string> | |
Hostname |
string | |
IPv4 |
string | |
IPv6 |
string | |
LastCheck |
string(date-time) | |
MagicDNSName |
string | |
PeerCount |
integer | |
TailnetName |
string | |
Version |
string |
SuccessFlag
| Name | Type | Description |
|---|---|---|
success |
boolean |
SystemStatus
| Name | Type | Description |
|---|---|---|
services |
Properties: webui, tailscale, serve |
|
timestamp |
string(date-time) | |
version |
string | Hardcoded server-reported version string. |
TailscaleError
| Name | Type | Description |
|---|---|---|
message |
string | |
status |
string |
TailscaleLoginResult
| Name | Type | Description |
|---|---|---|
auth_url |
string(uri) | |
message |
string | |
status |
string |
TailscaleMessage
| Name | Type | Description |
|---|---|---|
message |
string | |
status |
string |
TCPRelayStatus
| Name | Type | Description |
|---|---|---|
relay |
ServeRelay | |
running |
boolean |
ToggleRequest
| Name | Type | Description |
|---|---|---|
enabled |
boolean | |
id |
string |
Common responses
This section describes common responses that are reused across operations.
Unauthorized
Missing or invalid credentials
{
"error": "unauthorized"
}
Schema of the response body
{
"type": "object",
"properties": {
"error": {
"type": "string",
"examples": [
"unauthorized"
]
}
},
"required": [
"error"
]
}
PlainTextError
Error (plain-text body via http.Error)
"string"
Schema of the response body
{
"type": "string"
}
ServeSuccess
Operation succeeded
{
"status": "success",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"success"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
ServePending
Tailscale not yet ready; relay config saved and applied when connected
{
"status": "pending",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"pending"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
ServeNotFound
Relay not found
"string"
Schema of the response body
{
"type": "string"
}
ServeMethodError
Method not allowed (plain text)
"string"
Schema of the response body
{
"type": "string"
}
FunnelNotAllowed
Tailscale Funnel not permitted for this device (missing funnel node attribute)
{
"status": "error",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"error"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
TailscaleError
Tailscale operation error
{
"status": "error",
"message": "string"
}
Schema of the response body
{
"type": "object",
"properties": {
"status": {
"type": "string",
"examples": [
"error"
]
},
"message": {
"type": "string"
}
},
"required": [
"status",
"message"
]
}
Common parameters
This section describes common parameters that are reused across operations.
RelayId
| Name | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
id |
query | string | No |
Filename
| Name | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
filename |
query | string | No |
Security schemes
| Name | Type | Scheme | Description |
|---|---|---|---|
| bearerToken | http | bearer | Static token from `/var/lib/tailscale/.webui_token`. |
| sessionCookie | apiKey | Session cookie set after setup/login or successful Tailscale poll. |
Tags
| Name | Description |
|---|---|
| Info | Build metadata (public) |
| Auth | First-run setup, password login, session management |
| Status | Aggregate system status and configured targets |
| Tailscale | Tailscale daemon control and status |
| Serve HTTPS | HTTPS relays via tailscale serve |
| Serve TCP | TCP relays via tailscale serve |
| Serve Funnel | Public relays via tailscale funnel (ports 443, 8443, 10000) |
| Serve | Cross-cutting serve operations |
| Backup | Configuration backup create/restore/manage |
| Logs | Container log history, live stream, and level control |