# Core Private Node Routes These routes are consumed only by the node agent and require the `x-node-auth-key` header. ## Transport The node uses two transports to communicate with Core: - **HTTP** — registration only (`POST /node/register`). - **gRPC** — all other node-to-core calls: heartbeat, usage reporting, config sync, and stats. Core listens on `GRPC_PORT` (default `3002`). The node connects via `CORE_GRPC_URL`. Set `CORE_INSECURE=true` on the node to use plaintext instead of TLS. The HTTP routes for heartbeat, usage, config, and error remain available as a fallback but the node agent uses gRPC by default. ## Authentication - `POST /node/register` is the only public route in this group. - All other HTTP routes require the `x-node-auth-key` header. - gRPC calls pass the auth key as the `x-node-auth-key` metadata header (or in the `authKey` request field as fallback). - The auth key is issued by Core during registration and persisted on the node side. ## Route table | Method | Route | Input | Output / Notes | | --- | --- | --- | --- | | `POST` | `/node/register` | `RegisterNodeDto` | Returns `{ authKey, nodeId }` | | `POST` | `/node/heartbeat` | `NodeHeartbeatDto` | Returns `{ ok: true, needSync }` | | `POST` | `/node/usage` | `NodeUsageDto` | Returns `{ ok: true }` | | `POST` | `/node/outbound-usage` | `NodeOutboundUsageDto` | Returns `{ ok: true }` | | `POST` | `/node/session-events` | `SessionEventsDto` | Returns `{ ok: true }` | | `GET` | `/node/config/latest` | none | Returns `NodeConfig` | | `POST` | `/node/config/applied` | `NodeConfigAppliedDto` | Returns `{ ok: true }` | | `POST` | `/node/error` | `NodeErrorDto` | Returns `{ ok: true }` | ## Request payloads ### `RegisterNodeDto` - `code: string` - `name: string` - `host: string` - `ip: string` - `region: string` ### `NodeHeartbeatDto` - `ip?: string` - `revision?: number` The node sends its current revision in heartbeats. Core uses it to decide whether a sync is needed. ### `NodeUsageDto` - `entries: UsageEntryDto[]` `UsageEntryDto` fields: - `userId: number` - `subscriptionId?: number | null` - `nodeProtocolId: number` - `bytesUp: number` - `bytesDown: number` - `recordedAt: string` ### `NodeOutboundUsageDto` - `items: OutboundUsageItem[]` `OutboundUsageItem` fields: - `tag: string` - `bytesUp: number` - `bytesDown: number` ### `SessionEventsDto` - `events: SessionEventEntryDto[]` `SessionEventEntryDto` fields: - `userId: number` - `protocol: VpnProtocol` - `eventType: SessionEventType` - `occurredAt: string` - `ipAddress?: string` ### `NodeConfigAppliedDto` - `revision: number` ### `NodeErrorDto` - `message: string` - `code?: string` - `details?: Record` ## `NodeConfig` The config returned by `GET /node/config/latest` has this structure: ```ts { revision: number clientMap: Record mtprotoClientMap: Record services: { xray?: XrayServiceConfig mtproto?: MtProtoServiceConfig } } ``` Important notes: - `clientMap` maps subscription link UUIDs to the user and subscription — used for xray traffic attribution. - `mtprotoClientMap` maps MTProto link UUIDs to the user and subscription — used for telemt traffic attribution. - `services.xray` contains the Xray inbounds, routing, and outbounds for the node. - `services.mtproto` contains MTProto inbounds for `telemt`. - The node stores the current revision and client maps locally after a successful apply. ## Route semantics - `POST /node/heartbeat` updates `lastSeenAt` and can request a sync when the node revision is behind. - `POST /node/usage` stores per-minute usage rows and increments subscription traffic counters. - `POST /node/outbound-usage` increments `node_outbounds.traffic_bytes_up/down`. - `POST /node/session-events` stores online/offline events in `session_events`. - `POST /node/config/applied` marks the node sync status as `synced`. - `POST /node/error` marks the node as `error` and the sync status as `failed`. ## gRPC interface (`NodeService`) Core exposes a gRPC service that the node agent uses for all real-time communication. The service name is `nodeservice.NodeService` and is defined in `proto/node-service.proto`. Authentication: pass `x-node-auth-key` as gRPC metadata (or in the `authKey` field of the request as fallback). | Method | Request fields | Response fields | Notes | | --- | --- | --- | --- | | `Heartbeat` | `authKey`, `revision?`, `ip?` | `ok`, `needSync` | Equivalent to `POST /node/heartbeat` | | `ReportUsage` | `authKey`, `entries[]` | `ok` | Equivalent to `POST /node/usage` | | `ReportOutboundUsage` | `authKey`, `items[]` | `ok` | Equivalent to `POST /node/outbound-usage` | | `GetLatestConfig` | `authKey` | `revision`, `clientMapJson`, `mtprotoClientMapJson`, `servicesJson` | Config fields are JSON-encoded strings | | `ConfirmConfigApplied` | `authKey`, `revision` | `ok` | Equivalent to `POST /node/config/applied` | | `ReportNodeStats` | `authKey`, system metrics fields | `ok` | Reports CPU/RAM/disk/network/xray/mtproto snapshot; stored in Redis; drives `GET /admin/nodes/:id/runtime-status` and stats history | `ReportNodeStats` fields: `uptimeSeconds`, `cpuPercent`, `cpuCores`, `ramUsed`, `ramTotal`, `diskUsed`, `diskTotal`, `netRxPerSec`, `netTxPerSec`, `netRxTotal`, `netTxTotal`, `xrayRunning`, `xrayUptimeSeconds?`, `mtprotoJson`. The node sends `ReportNodeStats` on every heartbeat cycle. `GET /admin/nodes/:id/runtime-status` returns the latest stored snapshot without polling the node; `?fromNode=true` forces a live HTTP fetch instead.