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.
| Setting | Default | Meaning |
|---|---|---|
max_request_envelope | 1048576 | Request JSON envelope bytes. |
max_response_envelope | 1048576 | Encoded response JSON envelope bytes. |
max_input_bytes | 2097152 | Hard input-byte limit per line, including queued requests and incomplete input. |
max_line_memory | 8388608 | Retained allocation charge per line. |
max_input_fragments | 256 | Input fragment/FIFO entries per line. |
max_queued_requests | 8 | Waiting complete requests, in addition to one active request. |
max_sessions | 256 | Live sessions per instance. |
max_jobs | 64 | Admitted unfinished jobs per instance. |
handler_working_memory | 8388608 | Reserved temporary allowance for each handler. |
max_memory | 67108864 | Aggregate retained memory and working reservations per instance. |
max_unauthenticated_memory | 16777216 | Aggregate retained memory for connections that have not supplied valid credentials. |
frame_timeout_ms | 15000 | Absolute frame assembly deadline from first-fragment arrival, including queue residence. |
request_timeout_ms | 10000 | Complete-request admission through response publication, including FIFO and worker wait. |
output_timeout_ms | 15000 | Response publication through transport handoff, including paused time. |
idle_timeout_ms | 60000 | Idle 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 + 4andS >= 1024.max_line_memory >= I + max(2*(R + 1), 3*R + 2) + S + 128 KiB.Hcovers 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.
- Run the API client: a complete loopback setup, token files and the generic Python client.
- Manage authentication accounts: read/list, create, edit/disable and password workflows with explicit recovery after uncertain replies.
- API transports: direct TCP, HTTP/1.1 body framing and reliable streams over UDP.
- Register a node operation: the implementation contract for exposing another node through this gateway.
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.
| Property | Value |
|---|---|
| node flags | kNodeFlagChainEnd, kNodeFlagBlocksSplice |
can_have_prev | true |
can_have_next | false |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayerNone |
required_padding_left | 0 bytes |