Skip to main content

Create - POST /v1/sessions

Creates a session and returns a fully-formed WireGuard config. Hand it to any WireGuard library, or write it to a file and wg-quick up.
POST /v1/sessions
Authorization: Bearer $TB_KEY
Content-Type: application/json

Example

curl -sX POST https://api.tunnelbyte.dev/v1/sessions \
  -H "Authorization: Bearer $TB_KEY" \
  -d '{"region":"ash"}'
Response
{
  "id": "tb_a3kf91",
  "public_ip": "51.34.65.83",
  "wg_config": "[Interface]\nAddress = 10.101.0.7/24\n[Peer]\nPublicKey = ...\nEndpoint = 51.34.65.83:51820\nAllowedIPs = 0.0.0.0/0",
  "expires_at": "2026-05-28T14:32:00Z",
  "tier": "anonymous"
}

Body

FieldTypeRequiredNotes
regionstringyesOne of the codes from GET /v1/regions.
client_public_keystringnoBase64 WireGuard public key (32 bytes). When supplied, the server uses it directly and the returned wg_config omits PrivateKey (the caller already has it locally). When omitted, the server generates a keypair and returns both halves embedded in wg_config.

Response

FieldTypeNotes
idstringOpaque session id. Use for status lookups. Surfaces in invoices on the paid tier.
public_ipstringThe exit IP your traffic will appear from.
wg_configstringFull WireGuard config. If you supplied client_public_key, no private key is included; otherwise treat the string as a secret.
expires_atstring (RFC 3339)Server-side hard cutoff (tier max-session cap).
tierstringResolved account tier: anonymous, free, or paid.

Errors

StatusBodyWhy
400invalid json / <reason>Malformed body or invalid client_public_key.
401missing token / invalid tokenBearer header missing or token expired/revoked.
402quota exceeded (JSON body with tier + usage breakdown + reset times)Per-tier window cap hit. Body includes tier, bytes_used, bytes_limit, session_seconds, session_limit, bytes_window_resets_at, seconds_window_resets_at, resets_at (whichever of the two tripped, for single-field consumers), hint.
503no active nodes / no active node in region <code>Region has no healthy nodes. The latter response also includes available: [...] so the client can suggest another region.
503subnet exhaustedRegion is technically up but all client IPs in its /24 are taken. Retry shortly.
503peer apply failed; node may be disconnectedThe exit node didn’t accept the session within the timeout. Retry; the server will route around a persistently unreachable node within a minute.

Status - GET /v1/sessions/{id}

Look up the live state of a session. The CLI polls this every 10 seconds while a tunnel is up; the response shape feeds the live status line.

Example

curl https://api.tunnelbyte.dev/v1/sessions/tb_a3kf91 \
  -H "Authorization: Bearer $TB_KEY"
Response
{
  "id": "tb_a3kf91",
  "bytes_in": 187324162,
  "bytes_out": 13001216,
  "created_at": "2026-05-28T14:02:14Z",
  "tier": "anonymous",
  "quota_bytes_used": 199768576,
  "quota_bytes_limit": 1073741824,
  "quota_seconds_used": 840,
  "quota_seconds_limit": 1800
}
A live paid-tier session, with cost fields populated:
Response
{
  "id": "tb_p9q2x4",
  "bytes_in": 482137624,
  "bytes_out": 24811013,
  "created_at": "2026-06-06T09:15:00Z",
  "tier": "paid",
  "cost_time_millicents": 21000,
  "cost_bytes_millicents": 944,
  "cost_currency": "eur",
  "quota_bytes_used": 506948637,
  "quota_bytes_limit": 0,
  "quota_seconds_used": 1260,
  "quota_seconds_limit": 0
}
(21000 millicents = 21 cents = €0.210 for ~21 min of session time, plus a fraction of a cent for the bytes.) On a terminated session:
Response
{
  "id": "tb_a3kf91",
  "terminated_at": "2026-05-28T14:32:14Z",
  "terminated_reason": "quota_exceeded",
  "bytes_in": 1073741824,
  "bytes_out": 38271511,
  "created_at": "2026-05-28T14:02:14Z",
  "tier": "anonymous",
  "quota_bytes_used": 1073741824,
  "quota_bytes_limit": 1073741824,
  "quota_seconds_used": 1800,
  "quota_seconds_limit": 1800
}

Response

FieldTypeNotes
idstringEcho of the path id.
terminated_atstring (RFC 3339), optionalSet once the server has ended the session (Ctrl-C teardown also lands here). Empty / absent means the tunnel is still live.
terminated_reasonstring, optionalOne of: quota_exceeded, expired (30-day safety ceiling hit), idle (>5 min without WG handshake), or other reason text the server attaches at terminate.
bytes_in / bytes_outintegerCumulative bytes through the tunnel since created_at. Updated by node-agent heartbeats every ~10-30 seconds.
created_atstring (RFC 3339)Session start. Use for client-side elapsed-time math; do not depend on server-client clock alignment.
tierstringAccount tier the session was created under.
cost_time_millicentsinteger, optionalTime-leg cost so far in millicents (1/1000 of a cent, i.e. 1/100,000 of cost_currency). Sub-cent precision keeps short sessions from rounding to zero. Paid tier only; omitted when tier != paid or when the server can’t yet confirm rates (rare).
cost_bytes_millicentsinteger, optionalByte-leg cost so far in millicents, same units as cost_time_millicents. Total cost = cost_time_millicents + cost_bytes_millicents.
cost_currencystring, optionalISO 4217 lowercase code (eur, …). All v1 pricing is in EUR; the field is present so future regional pricing has somewhere to land. Omitted under the same conditions as the cost fields.
quota_bytes_usedintegerBytes used in the current bytes window (rolling 7 days on anonymous and free).
quota_bytes_limitintegerPer-tier byte cap for the current window. 0 for paid (unmetered).
quota_seconds_usedintegerCumulative session seconds in the current time window (24 h on every tier).
quota_seconds_limitintegerPer-tier time cap for the current window. 0 for paid (unmetered).
Owner-scoped: a session id created by another account returns 404 session not found.

See also

  • tunnelbyte - CLI driver that wraps these endpoints and renders the live status line.
  • Tiers - per-tier daily caps and per-session caps.