Skip to main content

APIServer

APIServer exposes explicitly registered node operations over a reliable byte stream. It is a terminal node and opens no socket itself. Access uses independent static operator credentials and exact per-operation permissions. Target nodes own their request and result schemas: see AuthenticationClient status and the AuthenticationServer management API.

Placement​

TcpListener -> APIServer
TcpListener -> HttpServer -> APIServer
UdpListener -> TcpOverUdpServer -> APIServer

Direct UdpListener -> APIServer is unsupported: API frames require an ordered reliable stream. The UDP composition requires a matching TcpOverUdpClient on the caller side. See the transport examples for complete compositions and HTTP body handling.

APIServer authenticates callers but provides no transport encryption. Bind local examples to loopback; remote access requires an authenticated encrypted transport with peer identity verification. API tokens do not prevent unauthenticated resource consumption. Restrict endpoint reachability or authenticate peers in the transport; bounded input allocation does not eliminate connection floods.

Configuration​

This is an APIServer node fragment. Its allowlist assumes an AuthenticationClient named auth-client in a loaded configuration whose logical name is edge-europe, and an AuthenticationServer named auth-db in a configuration named central:

{
"name": "management-api",
"type": "APIServer",
"settings": {
"clients": [
{
"name": "monitor",
"token": "<64 hexadecimal characters representing 32 random bytes>",
"allow": [
{"config": "edge-europe", "node": "auth-client", "operations": ["status"]},
{"config": "central", "node": "auth-db", "operations": ["status", "get-user", "list-users"]}
]
}
]
}
}

Replace the token placeholder with a fresh random token, for example from python3 -c 'import secrets; print(secrets.token_hex(32))'. The token is separate from traffic-user passwords and AuthenticationClient's upstream secret. Diagnostics remain accessible while AuthenticationClient is offline or rejected.

APIServer clears each decoded request token immediately after credential verification, including refusal. Schema-invalid requests clear unused tokens during parsing. It also clears owned decoded bodies, buffered frame and response bytes, and configured binary tokens before releasing them. Consumed or discarded input bytes are cleared before their buffers return to the pool. Complete parsed JSON trees are cleared on both acceptance and later validation refusal. Detached API jobs clear their owned request/response copies at final release, including cancellation; returned handler response bytes are cleared before pool reuse. This does not clear startup configuration trees, parser-internal partial trees from syntax errors, request buffers transferred to handlers, buffers already transferred to neighboring nodes, OS copies, or every copy in the process. Handlers remain responsible for the request buffers they consume.

clients must be nonempty. Client names and decoded 32-byte tokens must be unique. Every allow entry names one exact logical configuration, public top-level node and nonempty operation list; wildcard permissions are unsupported. Startup preparation rejects missing targets or unregistered operations. Runtime requests without the exact permission return forbidden; admitted requests reuse the immutable descriptor resolved during preparation. An existing TunnelApi symbol does not automatically expose an external operation. Each write needs its own explicit operation permission; read, edit, create and password-administration permissions do not imply one another.

Whenever an APIServer is configured, every loaded configuration needs a nonempty unique logical name. Selectors never name files or load another configuration. Configuration/node labels are case-sensitive and bounded to 256 UTF-8 bytes. Generated internal child nodes are private. A node-only selector works only when its name is unique across all declared top-level nodes, before filtering by registration or permissions. Use qualified selectors when names may repeat.

Optional Limits​

All fields below are positive finite integers. Startup rejects inconsistent geometry that cannot accommodate a bounded request and response. Byte budgets include allocator charge and temporary copies where applicable; envelope limits exclude their four-byte prefixes.

SettingDefaultMeaning
max_request_envelope1048576Request JSON envelope bytes.
max_response_envelope1048576Encoded response JSON envelope bytes.
max_input_bytes2097152Hard input-byte limit per line, including queued requests and incomplete input.
max_line_memory8388608Retained allocation charge per line.
max_input_fragments256Input fragment/FIFO entries per line.
max_queued_requests8Waiting complete requests, in addition to one active request.
max_sessions256Live sessions per instance.
max_jobs64Admitted unfinished jobs per instance.
handler_working_memory8388608Reserved temporary allowance for each handler.
max_memory67108864Aggregate retained memory and working reservations per instance.
max_unauthenticated_memory16777216Aggregate retained memory for connections that have not supplied valid credentials.
frame_timeout_ms15000Absolute frame assembly deadline from first-fragment arrival, including queue residence.
request_timeout_ms10000Complete-request admission through response publication, including FIFO and worker wait.
output_timeout_ms15000Response publication through transport handoff, including paused time.
idle_timeout_ms60000Idle connection with no frame, request or output in progress.

The enforced per-setting maxima are 64 MiB for each request/response envelope, 256 MiB for input, 1 GiB for each line/handler memory allowance, 2 GiB aggregate memory, 65,536 sessions/jobs/input fragments, 1,024 queued requests, and UINT32_MAX (4,294,967,295) milliseconds for each timeout. max_unauthenticated_memory is bounded by max_memory and the geometry below. Client configuration allows 1–256 clients, 1–512 allow entries per client and 1–64 operations per entry. Client names and configuration/node labels are nonempty and at most 256 UTF-8 bytes; operation names are nonempty and at most 64 bytes. Tokens must encode exactly 32 bytes as 64 hexadecimal characters; * labels/operations are rejected.

These maxima cannot be selected independently. With R = max_request_envelope, S = max_response_envelope, I = max_input_bytes, H = handler_working_memory, and J = H + 2*R + 2*S + 128 KiB, startup requires:

  • I >= R + 4 and S >= 1024.
  • max_line_memory >= I + max(2*(R + 1), 3*R + 2) + S + 128 KiB.
  • H covers the worker-pool allocation for a 4096-byte result plus 8192 bytes of metadata.
  • max_memory >= max_line_memory + J.
  • max_line_memory <= max_unauthenticated_memory <= max_memory - J.

A length header records the declared envelope size without allocating its body. Frame storage grows geometrically for received bytes; reservations cover both old and new capacities during copying. Parsing separately accounts for the assembled frame, JSON tree and decoded body, alongside input carriers, output and detached jobs. Teardown releases actual allocated capacity.

Byte-pressure watermarks exclude only the header and body bytes already copied into the current incomplete frame: input_bytes - (header_size + frame_used). Those bytes still count toward the hard input limit. Queued complete requests, raw backlog and fragment/entry limits continue to exert pressure. Bounded parser continuations Resume the source when only incomplete input remains, so a permitted large request can finish even when its size exceeds the high watermark.

Predictive pressure also pauses input when retained older complete/active requests leave too little input budget for the next frame: before its header is known, reserve room conceptually for max_request_envelope + 4; afterward use its declared complete size. Already buffered bytes are counted once. Completion and output drain release this hold and Resume autonomously. An incomplete frame alone cannot stay paused waiting for its own missing bytes. Pause is advisory: late, coalesced or noncooperating input still faces the unchanged hard caps and absolute frame deadline.

max_jobs=64 is a ceiling, not guaranteed concurrent capacity. With current defaults, the shared 64 MiB memory domain fits roughly seven small jobs with their 8 MiB handler allowances before substantial session storage; retained input, output and actual allocation geometry can reduce that count. Handler working allowance remains independent of the job ceiling.

Before a connection supplies valid credentials, every retained allocation also consumes the separate max_unauthenticated_memory allowance. Its default is the larger of max_line_memory and one quarter of max_memory; startup requires room for one complete line and leaves aggregate headroom for a handler job. Successful credential verification releases this extra charge, including when the requested operation is forbidden. Every later envelope still verifies its own credentials. Anonymous capacity exhaustion closes the affected line; existing authenticated connections retain access to the remaining aggregate budget. Session-count and CPU limits remain shared, so endpoint reachability restrictions are still useful.

Working memory must accommodate the actual worker-pool allocation for a 4096-byte result plus 8192 bytes of parsing metadata. A smaller encoded response cap can still return a bounded response_too_large error for a valid result.

Runtime JSON parsing keeps strict UTF-8, NUL, duplicate-key, depth/value and complete-input checks without configuration-parser stderr diagnostics. Host configuration/startup parsing retains its diagnostics. Invalid UTF-8 or NUL from a UTF-8 handler returns invalid_response with no result body; a valid oversized encoded result returns response_too_large. Both are post-dispatch errors, so a write may have committed. Base64 operations may return arbitrary bytes.

Zero does not disable a protection. Trickle input does not reset frame assembly time. Malformed framing, malformed envelope/schema and invalid authentication close after at most one bounded error. Gateway timeout and invalid_encoding errors also close the connection. The request deadline ends when a complete response is published; delivery then has its separate output deadline. Disconnect and timeout abandon execution but retain job reservations until queued cancellation or a running bounded handler actually settles.

Protocol And Examples​

Requests and responses use a four-byte big-endian length followed by a UTF-8 JSON envelope. The envelope identifies the caller, target and operation and carries an opaque node body. APIServer checks authentication, exact permissions and operation metadata; the target parses its body. Request IDs correlate replies and do not make writes idempotent. See the API protocol guide for the complete framing, envelope, error and delivery contract.

Operation schemas remain in their owning node references. A lost write reply can follow a successful in-memory commit; consult that operation's recovery rules before deciding whether to submit another request.

Lifecycle And Metadata​

APIServer borrows every accepted normal line and never calls lineDestroy(). It initializes session state before establishing the previous side and produces replies downstream. Keep client pipelining shallow enough that queued requests can finish within request_timeout_ms. A queued timeout closes the connection to preserve response order. One operation executes at a time per connection; admitted pipelined requests wait in FIFO order. Output Pause/Resume controls its reply producer while hard budgets and deadlines remain active. Detached cross-worker jobs carry session/request tokens; only the connection's worker resolves a live session and accesses its line. Shutdown closes global API admission before target quiescence, and actual line owners drain accepted lines before final registry destruction.

PropertyValue
node flagskNodeFlagChainEnd, kNodeFlagBlocksSplice
can_have_prevtrue
can_have_nextfalse
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayerNone
required_padding_left0 bytes