API reference

The REST API lives at https://relay.enzonic.com/api/v1. Requests and responses are JSON. Errors look like {"error":{"code":"…","message":"…"}} with a matching HTTP status.

Authentication

Create a key in API keys (Plus and Pro) and send it as a bearer token. Keys start with enzr_, are shown once, carry scopes such as tunnels:read or domains:write, and can be revoked at any time. Keys stop working if your plan no longer includes the API.

curl -H "Authorization: Bearer enzr_…" https://relay.enzonic.com/api/v1/tunnels

Rate limits

PlanAPI
FreeNot included
Plus60 requests per minute
Pro300 requests per minute

Over the limit, the API answers 429. Wait and retry.

Endpoints

Generated from the live OpenAPI document.

Enzonic Relay API · version 1.0.0 · 39 endpoints

Public

GET/healthzLiveness

Responses

200 OK

GET/api/v1/plansPlans and entitlements

Responses

200 Plans

GET/api/v1/regionsRegions, nearest first

Responses

200 Regions

GET/api/v1/openapi.jsonThis document

Responses

200 OpenAPI 3.1

POST/api/v1/abuseReport abuse (5 per hour per IP)

Request body (application/json)

{
  target: string;
  category: "phishing" | "malware" | "ddos" | "spam" | "other";
  details: string;
  email?: string (email);
}

Responses

202 Received

429 Rate limited

{
  error: {
    code: string;
    message: string;
  };
}
GET/download/agent/{file}Download an agent binary or SHA256SUMS

Parameters

NameInTypeDescription
file requiredpathstring

Responses

200 File

206 Range

304 Not modified

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
HEAD/download/agent/{file}Download metadata

Parameters

NameInTypeDescription
file requiredpathstring

Responses

200 Headers only

Account

GET/api/v1/mePlan, limits and usage

Auth: clerk, apiKey

Responses

200 Account

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

Tunnels

GET/api/v1/tunnelsList tunnels with live status and addresses

Auth: clerk, apiKey

Responses

200 Tunnels

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/tunnelsCreate a tunnel

Auth: clerk, apiKey

Request body (application/json)

{
  name?: string | null;
  proto: "java" | "bedrock" | "tcp" | "udp";
  local_host?: string;
  local_port: integer;
  region?: string;
  agent_id: string;
}

Responses

201 Created

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Name taken

{
  error: {
    code: string;
    message: string;
  };
}
GET/api/v1/tunnels/{id}Get a tunnel

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Responses

200 Tunnel

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
PATCH/api/v1/tunnels/{id}Update a tunnel

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Request body (application/json)

{
  name?: string | null;
  local_host?: string;
  local_port?: integer;
  region?: string;
  agent_id?: string;
  enabled?: boolean;
}

Responses

200 Tunnel

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Name taken

{
  error: {
    code: string;
    message: string;
  };
}
DELETE/api/v1/tunnels/{id}Delete a tunnel

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Responses

204 Deleted

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
GET/api/v1/tunnels/{id}/statsHourly traffic buckets (limited to the plan's stats_days)

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring
hoursqueryintegerHours back (default 24)

Responses

200 Buckets

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

Agents

GET/api/v1/agentsList agents

Auth: clerk, apiKey

Responses

200 Agents

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
PATCH/api/v1/agents/{id}Rename an agent

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Request body (application/json)

{
  name: string;
}

Responses

200 Agent

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
DELETE/api/v1/agents/{id}Revoke an agent (its tunnels stay)

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Responses

204 Revoked

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
GET/api/v1/claims/{code}Show a pending agent claim

Auth: clerk

Parameters

NameInTypeDescription
code requiredpathstring

Responses

200 Claim

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/claims/{code}/approveApprove a claim (checks max_agents)

Auth: clerk

Parameters

NameInTypeDescription
code requiredpathstring

Responses

200 Already approved

201 Approved

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

410 Expired

{
  error: {
    code: string;
    message: string;
  };
}

Domains

GET/api/v1/domainsList custom domains

Auth: clerk, apiKey

Responses

200 Domains

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/domainsAdd a custom domain (TXT challenge + records)

Auth: clerk, apiKey

Request body (application/json)

{
  hostname: string;
  tunnel_id: string;
}

Responses

201 Pending domain

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Exists

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/domains/{id}/verifyCheck the TXT record now

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Responses

200 Domain with check result

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Verified by another account

{
  error: {
    code: string;
    message: string;
  };
}
DELETE/api/v1/domains/{id}Remove a custom domain (unrouted at once)

Auth: clerk, apiKey

Parameters

NameInTypeDescription
id requiredpathstring

Responses

204 Deleted

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

API keys

GET/api/v1/api-keysList API keys

Auth: clerk

Responses

200 Keys

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/api-keysCreate an API key (plaintext returned once)

Auth: clerk

Request body (application/json)

{
  name: string;
  scopes: ("tunnels:read" | "tunnels:write" | "agents:read" | "agents:write" | "stats:read" | "domains:read" | "domains:write")[];
}

Responses

201 Key

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}
DELETE/api/v1/api-keys/{id}Revoke an API key

Auth: clerk

Parameters

NameInTypeDescription
id requiredpathstring

Responses

204 Revoked

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

Billing

POST/api/v1/billing/checkoutStripe Checkout URL for a plan

Auth: clerk

Request body (application/json)

{
  plan: "plus" | "pro";
  interval: "month" | "year";
}

Responses

200 {url}

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Already subscribed

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/billing/portalStripe Customer Portal URL

Auth: clerk

Responses

200 {url}

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 No billing account

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/stripe/webhookStripe webhook

Auth: stripeSignature

Responses

200 Received

400 Bad signature

{
  error: {
    code: string;
    message: string;
  };
}

Launcher

POST/api/v1/launcher/tunnelsRelay token for a launcher server (idempotent, token rotates); answers the home node's IP and TLS pins

Auth: clerk

Request body (application/json)

{
  server_id: string;
  display_name: string;
  name?: string;
  region?: string;
  pinning?: true;
}

Responses

200 Token

401 Not authenticated

{
  error: {
    code: string;
    message: string;
  };
}

403 Forbidden (plan, scope)

{
  error: {
    code: string;
    message: string;
  };
}

404 Not found

{
  error: {
    code: string;
    message: string;
  };
}

409 Name taken

{
  error: {
    code: string;
    message: string;
  };
}

426 Launcher too old (no node key pinning)

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/anon/registerRegister a signed-out launcher device (Ed25519 public key; the request is signed with that key)

Request body (application/json)

{
  public_key: string;
}

Responses

200 Already registered

201 Registered

401 Bad signature

{
  error: {
    code: string;
    message: string;
  };
}

429 Rate limited

{
  error: {
    code: string;
    message: string;
  };
}

503 Anonymous tier off

{
  error: {
    code: string;
    message: string;
  };
}
POST/api/v1/anon/tunnelsTemporary tunnel of a signed-out launcher device (random name per session, 24 h idle expiry)

Auth: deviceId, deviceSignature

Request body (application/json)

{
  server_id: string;
  display_name: string;
  session: string;
  pinning?: true;
  region?: string;
}

Responses

200 Token

401 Unknown device, bad signature, replay or stale timestamp

{
  error: {
    code: string;
    message: string;
  };
}

403 One server at a time, or banned

{
  error: {
    code: string;
    message: string;
  };
}

426 Launcher too old

{
  error: {
    code: string;
    message: string;
  };
}

429 Rate limited

{
  error: {
    code: string;
    message: string;
  };
}

503 Anonymous tier off

{
  error: {
    code: string;
    message: string;
  };
}

Agent

POST/agent/v1/claimsStart a claim (code + polling secret)

Request body (application/json)

{
  name: string;
  platform?: string;
  version?: string;
}

Responses

201 Claim

429 Rate limited

{
  error: {
    code: string;
    message: string;
  };
}
POST/agent/v1/claims/exchangePoll a claim: 202 pending, 200 key once, 410 expired

Request body (application/json)

{
  secret: string;
}

Responses

200 {agent_id, agent_key}

202 Pending

404 Unknown

{
  error: {
    code: string;
    message: string;
  };
}

410 Expired or consumed

{
  error: {
    code: string;
    message: string;
  };
}
GET/agent/v1/configThe agent's tunnels and home nodes

Auth: agentKey, agentId

Responses

200 Config

401 Bad credentials

{
  error: {
    code: string;
    message: string;
  };
}
POST/agent/v1/rotateRotate the agent key

Auth: agentKey, agentId

Responses

200 {agent_key}

401 Bad credentials

{
  error: {
    code: string;
    message: string;
  };
}
POST/agent/v1/tunnelsCreate a tunnel served by this agent

Auth: agentKey, agentId

Request body (application/json)

{
  name?: string | null;
  proto: "java" | "bedrock" | "tcp" | "udp";
  local_port: integer;
  local_host?: string;
  region?: string;
}

Responses

201 Tunnel

401 Bad credentials

{
  error: {
    code: string;
    message: string;
  };
}

403 Plan limit

{
  error: {
    code: string;
    message: string;
  };
}

Node

GET/node/v1/configConfig snapshot (ETag / If-None-Match)

Auth: nodeKey, nodeId

Responses

200 Snapshot

304 Unchanged

401 Bad credentials

{
  error: {
    code: string;
    message: string;
  };
}
POST/node/v1/reportStats report (deltas) and heartbeat

Auth: nodeKey, nodeId

Request body (application/json)

{
  node: string;
  version?: string;
  uptime_s?: integer;
  sessions?: integer;
  player_conns?: integer;
  udp_flows?: integer;
  tunnels?: {
    id: string;
    online?: boolean;
    players?: integer;
    bytes_in?: integer;
    bytes_out?: integer;
    conns?: integer;
    port_conflict?: boolean;
    port?: integer;
  }[];
  problems?: string[];
  port_range?: unknown[] | null;
  tls_pins?: string[];
  busy_ports?: {
    tcp?: integer[] | null;
    udp?: integer[] | null;
  } | null;
}

Responses

204 Accepted

401 Bad credentials

{
  error: {
    code: string;
    message: string;
  };
}

403 Node mismatch

{
  error: {
    code: string;
    message: string;
  };
}