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
| Plan | API |
|---|---|
| Free | Not included |
| Plus | 60 requests per minute |
| Pro | 300 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
| Name | In | Type | Description |
|---|---|---|---|
file required | path | string |
Responses
200 File
206 Range
304 Not modified
404 Not found
{
error: {
code: string;
message: string;
};
}HEAD/download/agent/{file}Download metadata
Parameters
| Name | In | Type | Description |
|---|---|---|---|
file required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
hours | query | integer | Hours 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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
code required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
code required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
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;
};
}