Integrations

API reference

The panel exposes three families of HTTP routes: panel routes used by the console itself, admin routes used by the Telegram bot and staff tooling, and the public node plane under /api/public/* that worker nodes call. All of them live on the panel hostname, so https://panel.example.com/api/status is a complete URL.

There is no anonymous write anywhere. Which gate applies depends on who the caller is supposed to be.

CallerGateHow
Browser (console)Session JWTThe panel session cookie is forwarded upstream; endpoints call requireAdmin(), which verifies against JWT_SECRET.
Telegram bot / internal serviceInternal tokenSend x-internal-token matching BOT_INTERNAL_TOKEN. Compared in constant time.
Worker node agentHMAC-SHA256Send x-timestamp and x-signature signed with the node secret.

Fail-closed

If JWT_SECRET is unset or still the default, admin routes answer 401 with configRequired: true instead of allowing the call. If a node secret is missing, signed routes answer 503. Only an explicit GRVPN_ALLOW_INSECURE_DEV=1 bypasses either — never set it in production.

Signing a node request

The signature is HMAC_SHA256(secret, "<timestamp>.<raw body>") in lowercase hex, where the timestamp is Unix seconds. Requests older than 300 seconds are rejected as stale, and a signature that has already been seen is rejected as a replay.

sign.sh
BODY='{"nodeId":"node-de-1","cpu":12.4}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" \
  | openssl dgst -sha256 -hmac "$NODE_SHARED_SECRET" -hex \
  | awk '{print $2}')

curl -sS https://panel.example.com/api/public/node-heartbeat \
  -H "content-type: application/json" \
  -H "x-timestamp: $TS" \
  -H "x-signature: $SIG" \
  -d "$BODY"

The secret resolves per node: NODE_SECRET_<NODE_ID> (uppercased, non alphanumerics replaced with _) if present, otherwise NODE_SHARED_SECRET.

§2 — /api/public/*

Public node plane#

This prefix skips the site session so agents can reach it, which is exactly why every handler authenticates the caller itself.

EndpointMethodAuthPurpose
/api/public/node-handshakePOSTHMACPin a node's TLS fingerprint on first pairing; body carries nodeId, fingerprint (64 hex), agentPort, version.
/api/public/node-handshakeGETSessionPanel-side lookup ?nodeId= returning { paired, agentPort, pairedAt, updatedAt, version } without leaking the fingerprint.
/api/public/node-heartbeatPOSTHMACThe beat: presence, load, NIC counters and per-account usage deltas.
/api/public/node-heartbeatGETSessionLive fleet feed. Upgrades to WebSocket where supported, otherwise long-polls with ?rev= and answers 426 to refused upgrades.
/api/public/node-rpcPOSTAdminPanel → node command proxy (provision, restart, inspect). Includes a circuit breaker and a force-repin control method.
/api/public/usage-resetPOSTAdminPurge accumulated usage counters (also available as DELETE on the heartbeat route).
/api/public/master-baselineGET / POSTHMACRead and publish the master's baseline config so a node can converge on it.

Soft failures

Missing node secrets and unreachable nodes return HTTP 200 with ok: false plus configRequired or cached. That is deliberate: a configuration state should not blank the console through an error boundary.
EndpointMethodPurpose
/api/statusGETSystem status and version; falls back to a safe default when upstream is down.
/api/trafficGETAggregate traffic series for the dashboard.
/api/nodes/$id/trafficGETPer-node traffic series.
/api/panel/stateGET / PUTRead or replace the panel's persisted UI/runtime state blob.
/api/panel/kv/$keyGET / PUTSmall key-value store for panel preferences and cached fragments.
/api/admin/usersGETStaff-facing user listing.
/api/admin/bot/usersGETTelegram-side user listing for the bot.
/api/admin/bot/users/$idGETOne Telegram user with wallet and account context.
/api/settings/botGETBot configuration the storefront needs at runtime.
/api/wallet/txnsGETLedger rows for a track: ?track=points|money|prepaid.
/api/referrals/overviewGETReferral leaderboard and totals, ?limit= capped.
/api/referrals/user/$idGETOne user's referral tree and earned rewards.

Calling from a script

bash
curl -sS https://panel.example.com/api/admin/users \
  -H "x-internal-token: $BOT_INTERNAL_TOKEN"

curl -sS "https://panel.example.com/api/wallet/txns?track=money" \
  -H "x-internal-token: $BOT_INTERNAL_TOKEN"
Content type
Always application/json unless the route streams.
Success
Either the resource itself or { ok: true, ... }.
Handled failure
HTTP 200 with { ok: false, error }.
Auth failure
401 unauthorized, or 503 when a required secret is absent.
Validation failure
400 with the Zod message in error.
Caching
Live feeds send cache-control: no-store.
  • Timestamps are ISO-8601 strings; money is always integer cents.
  • List endpoints accept a limit and return newest-first.
  • Never expose the internal token or a node secret to a browser — both are server-side only.