{
  "components": {
    "parameters": {},
    "schemas": {
      "NodeConfig": {
        "properties": {
          "certificates": {
            "items": {},
            "type": "array"
          },
          "hash": {
            "description": "`sha256:<lowercase hex>`; also the ETag",
            "example": "sha256:…",
            "type": "string"
          },
          "inbounds": {
            "items": {
              "$ref": "#/components/schemas/NodeConfigInbound"
            },
            "type": "array"
          },
          "revision": {
            "minimum": 0,
            "type": "integer"
          },
          "runtime": {
            "$ref": "#/components/schemas/NodeRuntime"
          }
        },
        "required": [
          "revision",
          "hash",
          "runtime",
          "inbounds",
          "certificates"
        ],
        "type": "object"
      },
      "NodeConfigInbound": {
        "additionalProperties": {},
        "description": "Protocol parameters with secrets in place (REALITY private key, SS server key …)",
        "properties": {
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "port": {
            "maximum": 65535,
            "minimum": 1,
            "type": "integer"
          },
          "protocol": {
            "enum": [
              "vmess",
              "vless",
              "trojan",
              "shadowsocks",
              "hysteria2",
              "tuic",
              "anytls",
              "naive",
              "socks",
              "http"
            ],
            "type": "string"
          },
          "security": {
            "additionalProperties": {},
            "type": "object"
          },
          "settings": {
            "additionalProperties": {},
            "type": "object"
          },
          "transport": {
            "additionalProperties": {},
            "type": "object"
          },
          "transport_layer": {
            "enum": [
              "tcp",
              "udp",
              "tcp_udp"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "protocol",
          "port",
          "transport_layer"
        ],
        "type": "object"
      },
      "NodeCredentials": {
        "description": "Only during a rotation grace window while the node still uses the previous token",
        "properties": {
          "previous_expires_at": {
            "description": "When the token in use stops working",
            "example": "2026-10-22T08:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "token": {
            "description": "The rotated agent token: persist it and use it from now on",
            "type": "string"
          }
        },
        "required": [
          "token",
          "previous_expires_at"
        ],
        "type": [
          "object",
          "null"
        ]
      },
      "NodeEnrollRequest": {
        "properties": {
          "enrollment_id": {
            "description": "Generated by the node and kept across retries: the same token and id within 10 minutes get the same response",
            "format": "uuid",
            "type": "string"
          },
          "enrollment_token": {
            "example": "enr_…",
            "maxLength": 256,
            "minLength": 1,
            "type": "string"
          },
          "program": {
            "properties": {
              "capabilities": {
                "properties": {
                  "features": {
                    "items": {
                      "maxLength": 64,
                      "minLength": 1,
                      "type": "string"
                    },
                    "maxItems": 64,
                    "type": "array"
                  },
                  "protocols": {
                    "items": {
                      "maxLength": 32,
                      "minLength": 1,
                      "type": "string"
                    },
                    "maxItems": 64,
                    "type": "array"
                  }
                },
                "required": [
                  "protocols",
                  "features"
                ],
                "type": "object"
              },
              "commit": {
                "maxLength": 64,
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "maxLength": 64,
                "minLength": 1,
                "type": "string"
              },
              "os_arch": {
                "maxLength": 64,
                "type": [
                  "string",
                  "null"
                ]
              },
              "version": {
                "maxLength": 64,
                "minLength": 1,
                "type": "string"
              }
            },
            "required": [
              "name",
              "version",
              "capabilities"
            ],
            "type": "object"
          }
        },
        "required": [
          "enrollment_token",
          "enrollment_id",
          "program"
        ],
        "type": "object"
      },
      "NodeEnrollResponse": {
        "properties": {
          "agent_token": {
            "description": "srv_<prefix>_<secret>; persist it before anything else",
            "type": "string"
          },
          "intervals": {
            "$ref": "#/components/schemas/NodeIntervals"
          },
          "next_batch_seq": {
            "minimum": 1,
            "type": "integer"
          },
          "server_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "server_id",
          "agent_token",
          "intervals",
          "next_batch_seq"
        ],
        "type": "object"
      },
      "NodeHeartbeatRequest": {
        "properties": {
          "applied": {
            "description": "The configuration the node runs; null before the first one is applied",
            "properties": {
              "hash": {
                "maxLength": 128,
                "minLength": 1,
                "type": "string"
              },
              "results": {
                "items": {
                  "properties": {
                    "inbound_id": {
                      "format": "uuid",
                      "type": "string"
                    },
                    "message": {
                      "maxLength": 1024,
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "enum": [
                        "ok",
                        "bind_failed",
                        "unsupported",
                        "error"
                      ],
                      "type": "string"
                    }
                  },
                  "required": [
                    "inbound_id",
                    "status"
                  ],
                  "type": "object"
                },
                "maxItems": 1024,
                "type": "array"
              },
              "revision": {
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "revision",
              "hash",
              "results"
            ],
            "type": [
              "object",
              "null"
            ]
          },
          "capabilities": {
            "properties": {
              "features": {
                "items": {
                  "maxLength": 64,
                  "minLength": 1,
                  "type": "string"
                },
                "maxItems": 64,
                "type": "array"
              },
              "protocols": {
                "items": {
                  "maxLength": 32,
                  "minLength": 1,
                  "type": "string"
                },
                "maxItems": 64,
                "type": "array"
              }
            },
            "required": [
              "protocols",
              "features"
            ],
            "type": "object"
          },
          "last_batch_seq": {
            "minimum": 0,
            "type": "integer"
          },
          "network": {
            "properties": {
              "ipv4": {
                "format": "ip",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ipv4_ok": {
                "type": "boolean"
              },
              "ipv6": {
                "format": "ip",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ipv6_ok": {
                "type": "boolean"
              }
            },
            "required": [
              "ipv4_ok",
              "ipv6_ok"
            ],
            "type": "object"
          },
          "program": {
            "properties": {
              "name": {
                "maxLength": 64,
                "minLength": 1,
                "type": "string"
              },
              "started_at": {
                "example": "2026-10-22T08:00:00Z",
                "format": "date-time",
                "type": "string"
              },
              "version": {
                "maxLength": 64,
                "minLength": 1,
                "type": "string"
              }
            },
            "required": [
              "name",
              "version",
              "started_at"
            ],
            "type": "object"
          },
          "sent_at": {
            "description": "Node clock; more than 5 minutes off → clock_skew notice",
            "example": "2026-10-22T08:00:00Z",
            "format": "date-time",
            "type": "string"
          },
          "system": {
            "properties": {
              "cpu": {
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              "disk": {
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              "load1": {
                "maximum": 1000000,
                "minimum": 0,
                "type": "number"
              },
              "load15": {
                "maximum": 1000000,
                "minimum": 0,
                "type": "number"
              },
              "load5": {
                "maximum": 1000000,
                "minimum": 0,
                "type": "number"
              },
              "mem": {
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              "online_users": {
                "maximum": 1000000000,
                "minimum": 0,
                "type": "integer"
              },
              "tcp_conns": {
                "maximum": 1000000000,
                "minimum": 0,
                "type": "integer"
              },
              "udp_conns": {
                "maximum": 1000000000,
                "minimum": 0,
                "type": "integer"
              },
              "uptime_s": {
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "cpu",
              "mem",
              "disk",
              "load1",
              "load5",
              "load15",
              "uptime_s",
              "tcp_conns",
              "udp_conns",
              "online_users"
            ],
            "type": "object"
          },
          "traffic": {
            "properties": {
              "interval_s": {
                "maximum": 86400,
                "minimum": 1,
                "type": "integer"
              },
              "max_rx_bps": {
                "description": "Highest 1-second receive speed in the interval, bytes per second",
                "example": "31457280",
                "pattern": "^(0|[1-9][0-9]{0,13})$",
                "type": "string"
              },
              "max_tx_bps": {
                "example": "31457280",
                "pattern": "^(0|[1-9][0-9]{0,13})$",
                "type": "string"
              },
              "rx_bytes": {
                "example": "31457280",
                "pattern": "^(0|[1-9][0-9]{0,13})$",
                "type": "string"
              },
              "tx_bytes": {
                "example": "31457280",
                "pattern": "^(0|[1-9][0-9]{0,13})$",
                "type": "string"
              }
            },
            "required": [
              "interval_s",
              "rx_bytes",
              "tx_bytes",
              "max_rx_bps",
              "max_tx_bps"
            ],
            "type": "object"
          }
        },
        "required": [
          "sent_at",
          "program",
          "applied",
          "system",
          "traffic",
          "network",
          "last_batch_seq"
        ],
        "type": "object"
      },
      "NodeHeartbeatResponse": {
        "properties": {
          "credentials": {
            "$ref": "#/components/schemas/NodeCredentials"
          },
          "desired_revision": {
            "minimum": 0,
            "type": "integer"
          },
          "intervals": {
            "$ref": "#/components/schemas/NodeIntervals"
          },
          "next_batch_seq": {
            "description": "Highest recorded batch_seq + 1 (resume after state loss)",
            "minimum": 1,
            "type": "integer"
          },
          "notices": {
            "items": {
              "$ref": "#/components/schemas/NodeNotice"
            },
            "type": "array"
          },
          "server_time": {
            "example": "2026-10-22T08:00:00.000Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "server_time",
          "desired_revision",
          "next_batch_seq",
          "intervals",
          "notices",
          "credentials"
        ],
        "type": "object"
      },
      "NodeIntervals": {
        "description": "Seconds between calls of each kind",
        "properties": {
          "config_poll": {
            "type": "integer"
          },
          "heartbeat": {
            "type": "integer"
          },
          "online_push": {
            "type": "integer"
          },
          "usage_push": {
            "type": "integer"
          },
          "users_poll": {
            "type": "integer"
          }
        },
        "required": [
          "config_poll",
          "users_poll",
          "usage_push",
          "online_push",
          "heartbeat"
        ],
        "type": "object"
      },
      "NodeNotice": {
        "properties": {
          "code": {
            "description": "snake_case; ignore codes you do not know",
            "example": "clock_skew",
            "type": "string"
          },
          "data": {
            "additionalProperties": {},
            "type": "object"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code"
        ],
        "type": "object"
      },
      "NodeOnlineRequest": {
        "properties": {
          "items": {
            "items": {
              "properties": {
                "ips": {
                  "items": {
                    "anyOf": [
                      {
                        "format": "ip",
                        "type": "string"
                      },
                      {
                        "format": "ip",
                        "type": "string"
                      }
                    ]
                  },
                  "type": "array"
                },
                "user_id": {
                  "minimum": 1,
                  "type": "integer"
                }
              },
              "required": [
                "user_id",
                "ips"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "observed_at": {
            "example": "2026-10-22T08:00:00Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "observed_at",
          "items"
        ],
        "type": "object"
      },
      "NodeOnlineResponse": {
        "properties": {
          "accepted": {
            "minimum": 0,
            "type": "integer"
          },
          "notices": {
            "items": {
              "$ref": "#/components/schemas/NodeNotice"
            },
            "type": "array"
          },
          "over_limit_users": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "rejected": {
            "items": {
              "properties": {
                "index": {
                  "minimum": 0,
                  "type": "integer"
                },
                "reason": {
                  "enum": [
                    "too_many_ips",
                    "unknown_user"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "index",
                "reason"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "accepted",
          "over_limit_users",
          "rejected"
        ],
        "type": "object"
      },
      "NodeRuntime": {
        "additionalProperties": {},
        "properties": {
          "block_rules": {
            "items": {},
            "type": "array"
          },
          "dns": {
            "items": {},
            "type": "array"
          },
          "ip_strategy": {
            "type": "string"
          },
          "outbounds": {
            "items": {},
            "type": "array"
          },
          "routing_rules": {
            "items": {},
            "type": "array"
          }
        },
        "required": [
          "ip_strategy",
          "dns",
          "outbounds",
          "routing_rules",
          "block_rules"
        ],
        "type": "object"
      },
      "NodeUsageRequest": {
        "properties": {
          "batch_seq": {
            "maximum": 9007199254740990,
            "minimum": 1,
            "type": "integer"
          },
          "items": {
            "items": {
              "properties": {
                "down": {
                  "pattern": "^-?[0-9]{1,40}$",
                  "type": "string"
                },
                "inbound_id": {
                  "format": "uuid",
                  "type": "string"
                },
                "up": {
                  "pattern": "^-?[0-9]{1,40}$",
                  "type": "string"
                },
                "user_id": {
                  "minimum": 1,
                  "type": "integer"
                }
              },
              "required": [
                "inbound_id",
                "user_id",
                "up",
                "down"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "window": {
            "properties": {
              "end": {
                "example": "2026-10-22T08:00:00Z",
                "format": "date-time",
                "type": "string"
              },
              "start": {
                "example": "2026-10-22T08:00:00Z",
                "format": "date-time",
                "type": "string"
              }
            },
            "required": [
              "start",
              "end"
            ],
            "type": "object"
          }
        },
        "required": [
          "batch_seq",
          "window",
          "items"
        ],
        "type": "object"
      },
      "NodeUsageResponse": {
        "properties": {
          "accepted": {
            "minimum": 0,
            "type": "integer"
          },
          "next_batch_seq": {
            "minimum": 1,
            "type": "integer"
          },
          "rejected": {
            "items": {
              "properties": {
                "index": {
                  "minimum": 0,
                  "type": "integer"
                },
                "reason": {
                  "enum": [
                    "unknown_user",
                    "inbound_not_on_server",
                    "negative_value",
                    "value_too_large",
                    "window_invalid"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "index",
                "reason"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "result": {
            "enum": [
              "accepted",
              "duplicate",
              "stale"
            ],
            "type": "string"
          }
        },
        "required": [
          "result",
          "accepted",
          "rejected",
          "next_batch_seq"
        ],
        "type": "object"
      },
      "NodeUser": {
        "properties": {
          "blocked_ips": {
            "description": "CIDRs (IPv4 /32, IPv6 /64): drop existing connections, refuse new ones",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "device_limit": {
            "description": "null: unlimited",
            "type": [
              "integer",
              "null"
            ]
          },
          "id": {
            "description": "The subscription node_user_id; usage and online reports use it",
            "type": "integer"
          },
          "password": {
            "type": [
              "string",
              "null"
            ]
          },
          "speed_limit_mbps": {
            "description": "null: unlimited",
            "type": [
              "integer",
              "null"
            ]
          },
          "ss2022_keys": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Shadowsocks 2022 user PSK (base64) by method",
            "type": [
              "object",
              "null"
            ]
          },
          "uuid": {
            "description": "uuid-based protocols; user name of socks / http / naive",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "uuid",
          "password",
          "ss2022_keys",
          "speed_limit_mbps",
          "device_limit",
          "blocked_ips"
        ],
        "type": "object"
      },
      "NodeUsers": {
        "description": "An empty `users` list means serve nobody (not the same as 304)",
        "properties": {
          "access_version": {
            "minimum": 0,
            "type": "integer"
          },
          "added": {
            "items": {
              "$ref": "#/components/schemas/NodeUser"
            },
            "type": "array"
          },
          "changed": {
            "items": {
              "$ref": "#/components/schemas/NodeUser"
            },
            "type": "array"
          },
          "full": {
            "description": "false: apply added/removed/changed to since_version; replace inbounds completely",
            "type": "boolean"
          },
          "inbounds": {
            "items": {
              "properties": {
                "inbound_id": {
                  "format": "uuid",
                  "type": "string"
                },
                "user_ids": {
                  "items": {
                    "type": "integer"
                  },
                  "type": "array"
                }
              },
              "required": [
                "inbound_id",
                "user_ids"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "removed": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "since_version": {
            "minimum": 0,
            "type": "integer"
          },
          "users": {
            "items": {
              "$ref": "#/components/schemas/NodeUser"
            },
            "type": "array"
          }
        },
        "required": [
          "access_version",
          "users",
          "inbounds",
          "full"
        ],
        "type": "object"
      },
      "Problem": {
        "description": "RFC 9457 problem details. Nodes branch on `code`, never on `title` or `detail`.",
        "properties": {
          "code": {
            "description": "Stable `<module>.<reason>` code, see `x-error-codes`",
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "errors": {
            "description": "Per-field validation errors (422 node.payload_invalid)",
            "items": {
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "code"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "request_id": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "description": "`urn:error:<code>`",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "request_id"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "AgentToken": {
        "description": "Agent token `srv_<prefix>_<secret>` returned by POST /enroll (or delivered by heartbeat)",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "Node protocol v1. Every request sends `Node-Protocol: 1` and `User-Agent: <program>/<version>`; every endpoint but /enroll sends `Authorization: Bearer srv_<prefix>_<secret>`. Request bodies are JSON, optionally `Content-Encoding: gzip`, at most 1 MiB after decompression. Errors are application/problem+json; `x-error-codes` lists what a node can receive. Per-server rate limits: heartbeat + usage + online 10/min together, config 30/min, users 30/min (429 with Retry-After). Retries: exponential backoff from 5 s to 5 min with ±20 % jitter on 5xx and 429.",
    "title": "PPanel Edge node protocol",
    "version": "1"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/node/config": {
      "get": {
        "parameters": [
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          },
          {
            "description": "The ETag last received, verbatim (weak comparison: `W/\"…\"` matches too)",
            "in": "header",
            "name": "if-none-match",
            "required": false,
            "schema": {
              "description": "The ETag last received, verbatim (weak comparison: `W/\"…\"` matches too)",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeConfig"
                }
              }
            },
            "description": "The desired configuration (ETag: \"<hash>\", Cache-Control: no-store)"
          },
          "304": {
            "description": "Unchanged since the ETag sent in If-None-Match (the response carries the ETag)"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.auth_invalid: missing, malformed, unknown or expired agent token"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.disabled: agent disabled or server retired"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported: missing or unsupported Node-Protocol (Upgrade: Node-Protocol/1)"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited (Retry-After): per-server limits,"
          }
        },
        "security": [
          {
            "AgentToken": []
          }
        ],
        "summary": "Desired configuration: runtime, inbounds with secrets, certificates (ETag = hash)",
        "tags": [
          "node-protocol"
        ],
        "x-body": "none",
        "x-body-limit": 0
      }
    },
    "/api/v1/node/enroll": {
      "post": {
        "parameters": [
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeEnrollRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeEnrollResponse"
                }
              }
            },
            "description": "Enrolled; a retry with the same enrollment_id within 10 minutes gets the same body"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.bad_request: unparsable JSON or gzip"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.enrollment_invalid: unknown, used, expired, or the server is retired"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.payload_too_large: more than 1 MiB after decompression"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.unsupported_media_type: not application/json, or a Content-Encoding other than gzip"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.payload_invalid: schema violation, with errors[]"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited: per client address; or the first attempt is still running (Retry-After)"
          }
        },
        "summary": "Exchange the one-time enrollment token for the agent token (retry-safe by enrollment_id)",
        "tags": [
          "node-protocol"
        ],
        "x-audit": "network.agent.enroll",
        "x-body": "json",
        "x-body-encoding": "gzip",
        "x-body-limit": 1048576,
        "x-idempotency": "none",
        "x-rate-limit": "node.enroll.ip"
      }
    },
    "/api/v1/node/heartbeat": {
      "post": {
        "parameters": [
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeHeartbeatRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeHeartbeatResponse"
                }
              }
            },
            "description": "Server time, desired revision, next_batch_seq, intervals, notices, credentials"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.bad_request: unparsable JSON or gzip"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.auth_invalid: missing, malformed, unknown or expired agent token"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.disabled: agent disabled or server retired"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.payload_too_large: more than 1 MiB after decompression"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.unsupported_media_type: not application/json, or a Content-Encoding other than gzip"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.payload_invalid: schema violation, with errors[]"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported: missing or unsupported Node-Protocol (Upgrade: Node-Protocol/1)"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited (Retry-After): per-server limits,"
          }
        },
        "security": [
          {
            "AgentToken": []
          }
        ],
        "summary": "Program, applied configuration and per-inbound results, load, NIC traffic, public addresses",
        "tags": [
          "node-protocol"
        ],
        "x-body": "json",
        "x-body-encoding": "gzip",
        "x-body-limit": 1048576,
        "x-idempotency": "none"
      }
    },
    "/api/v1/node/online": {
      "post": {
        "parameters": [
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeOnlineRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeOnlineResponse"
                }
              }
            },
            "description": "Recorded users, over-limit users and per-user rejections"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.bad_request: unparsable JSON or gzip"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.auth_invalid: missing, malformed, unknown or expired agent token"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.disabled: agent disabled or server retired"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.payload_too_large: more than 1 MiB after decompression"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.unsupported_media_type: not application/json, or a Content-Encoding other than gzip"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.payload_invalid: schema violation, with errors[]"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported: missing or unsupported Node-Protocol (Upgrade: Node-Protocol/1)"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited (Retry-After): per-server limits,"
          }
        },
        "security": [
          {
            "AgentToken": []
          }
        ],
        "summary": "Online address window and global device-limit decisions",
        "tags": [
          "node-protocol"
        ],
        "x-body": "json",
        "x-body-encoding": "gzip",
        "x-body-limit": 1048576,
        "x-idempotency": "none"
      }
    },
    "/api/v1/node/usage": {
      "post": {
        "parameters": [
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeUsageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeUsageResponse"
                }
              }
            },
            "description": "Accepted, replayed or stale batch"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.bad_request: unparsable JSON or gzip"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.auth_invalid: missing, malformed, unknown or expired agent token"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.disabled: agent disabled or server retired"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.batch_conflict"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.payload_too_large: more than 1 MiB after decompression"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.unsupported_media_type: not application/json, or a Content-Encoding other than gzip"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.payload_invalid: schema violation, with errors[]"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported: missing or unsupported Node-Protocol (Upgrade: Node-Protocol/1)"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited (Retry-After): per-server limits,"
          }
        },
        "security": [
          {
            "AgentToken": []
          }
        ],
        "summary": "Idempotent incremental usage batch",
        "tags": [
          "node-protocol"
        ],
        "x-body": "json",
        "x-body-encoding": "gzip",
        "x-body-limit": 1048576,
        "x-idempotency": "none"
      }
    },
    "/api/v1/node/users": {
      "get": {
        "parameters": [
          {
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "pattern": "^(0|[1-9][0-9]{0,15})$",
              "type": "string"
            }
          },
          {
            "description": "Protocol major version: `1`",
            "in": "header",
            "name": "node-protocol",
            "required": true,
            "schema": {
              "description": "Protocol major version: `1`",
              "example": "1",
              "type": "string"
            }
          },
          {
            "description": "The ETag last received, verbatim (weak comparison: `W/\"…\"` matches too)",
            "in": "header",
            "name": "if-none-match",
            "required": false,
            "schema": {
              "description": "The ETag last received, verbatim (weak comparison: `W/\"…\"` matches too)",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeUsers"
                }
              }
            },
            "description": "The user list (ETag: \"<access_version>\", Cache-Control: no-store)"
          },
          "304": {
            "description": "Unchanged since the ETag sent in If-None-Match (the response carries the ETag)"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.auth_invalid: missing, malformed, unknown or expired agent token"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.disabled: agent disabled or server retired"
          },
          "426": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "node.protocol_unsupported: missing or unsupported Node-Protocol (Upgrade: Node-Protocol/1)"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "common.rate_limited (Retry-After): per-server limits,"
          }
        },
        "security": [
          {
            "AgentToken": []
          }
        ],
        "summary": "Users per inbound with credentials, limits and blocked addresses (ETag = access_version)",
        "tags": [
          "node-protocol"
        ],
        "x-body": "none",
        "x-body-limit": 0
      }
    }
  },
  "webhooks": {},
  "x-error-codes": {
    "common.bad_request": {
      "status": 400,
      "title": "Bad request"
    },
    "common.internal_error": {
      "status": 500,
      "title": "Internal error"
    },
    "common.payload_too_large": {
      "status": 413,
      "title": "Request body too large"
    },
    "common.rate_limited": {
      "status": 429,
      "title": "Too many requests"
    },
    "common.unsupported_media_type": {
      "status": 415,
      "title": "Unsupported request body type"
    },
    "node.auth_invalid": {
      "status": 401,
      "title": "Server token invalid"
    },
    "node.batch_conflict": {
      "status": 409,
      "title": "Batch sequence reused with different content"
    },
    "node.disabled": {
      "status": 403,
      "title": "Agent disabled"
    },
    "node.enrollment_invalid": {
      "status": 401,
      "title": "Enrollment token invalid or expired"
    },
    "node.payload_invalid": {
      "status": 422,
      "title": "Invalid node payload"
    },
    "node.protocol_unsupported": {
      "status": 426,
      "title": "Node protocol version not supported"
    }
  }
}
