Skip to main content

Wire protocol

Edit on GitHub →

The normative specification every RateLimitly client implements. Client libraries and their API references live on the docs index.

On this page
 ____       _       _ _           _ _   _        __        ___          
|  _ \ __ _| |_ ___| (_)_ __ ___ (_) |_| |_   _  \ \      / (_)_ __ ___ 
| |_) / _` | __/ _ \ | | '_ ` _ \| | __| | | | |  \ \ /\ / /| | '__/ _ \
|  _ < (_| | ||  __/ | | | | | | | | |_| | |_| |   \ V  V / | | | |  __/
|_| \_\__,_|\__\___|_|_|_| |_| |_|_|\__|_|\__, |    \_/\_/  |_|_|  \___|
                                          |___/                         
 ____            _                  _
|  _ \ _ __ ___ | |_ ___   ___ ___ | |
| |_) | '__/ _ \| __/ _ \ / __/ _ \| |
|  __/| | | (_) | || (_) | (_| (_) | |
|_|   |_|  \___/ \__\___/ \___\___/|_|

Ratelimitly Wire Protocol (MVP)

Ratelimitly rate limiter clients/servers exchange request/response messages through User Datagram Protocol (UDP) sockets as single datagrams per message with a maximum payload of ~1200 bytes to all but eliminate the possibility of IP fragmentation on typical networks with Maximum Transmission Units (MTUs) of 1500 bytes.

Request/Response Messages

Clients send request messages to Ratelimitly servers, expecting the servers to process them and return corresponding response messages. All request and response messages consist of a sequence of records, including tenant and authentication data. Individual messages contain information specific to their intended purpose, represented as PDU (Protocol Data Unit) with a common prefix header (called the general PDU header) that is used to discriminate the specific message data that follows it.

Ordering of records in a message

All request and response messages MUST obey the following sequence of records: Tenant Header TLV, Auth Header TLV, and (one) Ratelimitly PDU. If the records are not in order, the server MUST drop the packet.

TLV (Type-Length-Value) is an encoding scheme with record type, the record value’s length, and the value itself. PDU (Protocol Data Unit) represents a Ratelimitly operation. Both TLV and PDU are specified below.

Little-endian ordering of numbers

Integers are represented (stored, transmitted) with the least significant byte first. For example, a 32-bit integer 0x01234567 must be represented as a sequence of bytes 0x67, 0x45, 0x23, 0x01.

When memory bytes are printed sequentially from left to right (e.g. in a hex dump), little-endian representation appears backwards when visualized. Such ordering was taken into account for numeric constants that were intentionally defined as two ASCII characters, easily recognizable in a binary view.

Size Field Semantics

All size fields in the protocol include the complete structure size (header + body):

  • tlv_size: Includes the 4-byte TLV header plus the TLV body
  • pdu_size: Includes the 8-byte PDU header plus the PDU body
  • This applies consistently to all TLV and PDU structures throughout the protocol

Normative formulas:

  • tlv_body_len = tlv_size - 4
  • pdu_body_len = pdu_size - 8
  • Valid TLV requires tlv_size >= 4
  • Valid PDU requires pdu_size >= 8
  • Receiver MUST parse exactly tlv_size bytes for a TLV and exactly pdu_size bytes for a PDU

Data Alignment and Padding

The protocol is optimized for zero-copy parsing with strict alignment requirements:

  • Guard blocks: 40 bytes (8-byte aligned for optimal performance)
  • Resource blocks: 28 bytes (4-byte aligned, required for zero-copy parsing)
  • Service latency blocks: 36 bytes (4-byte aligned, no trailing padding)
  • TLV structures: Minimal padding where needed for alignment

The 2-byte padding in resource blocks is essential for high-performance implementations using zero-copy parsing in Rust (#[repr(C)]) and Zig. Guard and service latency block fields already maintain proper 4-byte alignment without trailing padding. This ensures that arrays of fixed-size blocks maintain proper alignment for direct memory mapping from UDP packets.

Zero-Copy Parsing Support

The protocol is designed to support zero-copy parsing for maximum performance:

// Rust zero-copy structures
#[repr(C)]
struct GuardBlock {
    latency_tracker_id: [u8; 16],
    ttl_ms: u32,
    max_samples: u32,
    buffer_size: u32,
    min_sample_threshold: u32,
    latency_threshold: u32,
    current_latency: u32,
}

#[repr(C)]
struct ServiceLatencyBlock {
    latency_tracker_id: [u8; 16],
    ttl_ms: u32,
    max_samples: u32,
    buffer_size: u32,
    min_sample_threshold: u32,
    observed_latency: u32,
}
// Zig zero-copy structures
const GuardBlock = extern struct {
    latency_tracker_id: [16]u8,
    ttl_ms: u32,
    max_samples: u32,
    buffer_size: u32,
    min_sample_threshold: u32,
    latency_threshold: u32,
    current_latency: u32,
};

const ServiceLatencyBlock = extern struct {
    latency_tracker_id: [16]u8,
    ttl_ms: u32,
    max_samples: u32,
    buffer_size: u32,
    min_sample_threshold: u32,
    observed_latency: u32,
};

These structures are designed for direct casting from UDP packet buffers without additional parsing overhead.

Tenant Header

The Tenant Header contains tenant identification and request metadata that must be accessible without decryption for performance optimization. This includes the tenant or server identifier, the request deduplication identifier, and the request creation timestamp used for freshness validation.

The Tenant Header does not define the replay window for mutating requests. Replay-window control is carried by the mutating PDU body via dedup_ttl_ms.

+------------+-------------------+--------------+---------+----------------------------------------------------------+
| Tenant Header                                                                                                      |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
|            | TLV Field         | Size (bytes) | Type    | Description                                              |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| TLV Header | tlv_type          | 2            | Integer | Tenant TLV type value will be 0x4C52 ("RL" when viewed   |
|            |                   |              |         | in hexdump)                                              |
|            |                   |              |         | to be easily identifiable.                               |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | tlv_size          | 2            | Integer | Overall size of TLV parameter                            |
|            |                   |              |         | including header and value(s). Value is 40 for this TLV  |
|            |                   |              |         | (TLV body length = `tlv_size - 4` = 36).                 |
+------------+-------------------+--------------+---------+----------------------------------------------------------+
| TLV Body   | key_id            | 8            | Integer | Key ID (request) / Server ID (response)                  |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | unique_id         | 16           | Binary  | Unique logical request identifier used for correlation   |
|            |                   |              |         | and deduplication                                        |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | time_stamp        | 8            | Integer | Milliseconds since Epoch when the request was composed;  |
|            |                   |              |         | used for request freshness validation                    |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | steering_feedback | 1            | Boolean | Source port steering: 0 = change port, 1 = keep port     |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | tenant_mgmt_flag  | 1            | Boolean | Tenant management flag: 0 = regular operation, 1 = admin |
|            +-------------------+--------------+---------+----------------------------------------------------------+
|            | padding           | 2            | Binary  | Padding for 4-byte alignment (MUST be zero)              |
+------------+-------------------+--------------+---------+----------------------------------------------------------+

Dual-Purpose key_id Field:

  • Requests: Contains 64-bit key identifier (tenant/namespace identifier)
  • Responses: Server overwrites with its structured 64-bit server identifier
  • HA Usage: Clients use server-provided value to identify and validate server instances

Structured server_id Format:

For all responses, the key_id field MUST contain a structured server_id encoded as an unsigned 64-bit integer:

  • Bits 63..23 (41 bits): start_s_since_2025
  • Bits 22..15 (8 bits): listener_id
  • Bits 14..0 (15 bits): node_id

Definitions:

  • start_s_since_2025 = server_start_s - 1735689600
  • 1735689600 is January 1, 2025 00:00:00 +0000 UTC in Unix seconds
  • server_start_s is the server thread startup time in Unix seconds
  • listener_id is an 8-bit server-local listener discriminator in the range 0..255
  • node_id is a deployment-scoped 15-bit server-node identifier in the range 0..32767

Normative encoding:

  • server_id = (start_s_since_2025 << 23) | (listener_id << 15) | node_id
  • Servers MUST encode server_id exactly as above
  • Servers MUST use 1-second resolution for server_start_s
  • Servers MUST ensure listener_id <= 255
  • Servers on different nodes that may appear in the same SRV answer set SHOULD use different node_id values
  • Different listener threads on the same server node that may appear in the same SRV answer set SHOULD use different listener_id values
  • Two simultaneously live server threads that may appear in the same SRV answer set MUST NOT intentionally reuse the same tuple (server_start_s, listener_id, node_id)

This layout preserves age ordering in the high bits while keeping the low bits focused on server identity and collision reduction rather than routing.

Source Port Steering:

The steering_feedback field is a 1-byte boolean that controls source port behavior for package steering.

Tenant Management Flag:

The tenant_mgmt_flag field is a 1-byte boolean that selects the administrative authentication domain for the packet. The field name is retained for wire compatibility, but value 1 now covers administrative/control-plane traffic in general, not only tenant mutation requests.

  • Value 0 (false): Regular tenant operation (rate limiting, latency reporting)
  • Value 1 (true): Administrative operation authenticated with the dedicated admin key
  • Values 2-255: Reserved for future use

Padding and Alignment:

The remaining 2 bytes of padding MUST be set to zero for future extensibility and maintain 4-byte alignment for zero-copy parsing:

  • Values: MUST be 0x00
  • Purpose: Future protocol extensions and memory alignment optimization

Usage Patterns:

  • Request: Client sets steering_feedback based on session requirements
  • Request: Client sets tenant_mgmt_flag = 1 for all administrative PDUs
  • Response: Server may update steering_feedback with recommended port behavior
  • Authentication: Server uses tenant_mgmt_flag to select appropriate decryption key
  • Session Affinity: Use steering_feedback = 1 when maintaining client session state
  • Load Balancing: Use steering_feedback = 0 to allow optimal port distribution

Bech32 Credential Formats

Ratelimitly uses Bech32 strings for provisioning secrets and API keys. These strings are configuration artifacts, not on-wire TLVs by themselves. The human-readable part (HRP) selects the credential format; the payload layout is fixed by that HRP.

All multi-byte integers embedded in Bech32 payloads are encoded in little-endian byte order before Bech32 conversion.

rl-secret

rl-secret carries only raw secret material and no tenant metadata. It is intended for out-of-band configuration, primarily the tenant-management AES key supplied when starting a ratelimitly server.

Payload field Size (bytes) Type Description
secret 32 Binary Raw 32-byte AES-256 key material

rl-none

rl-none carries a versioned API-key identifier and quota word. It is used for API keys with no request authentication.

Payload field Size (bytes) Type Description
format_version 1 Integer API-key payload schema; MUST be 1
key_id 8 Integer API-key identifier
quotas 4 Integer Packed quota word defined below

rl-cookie carries the same version and quota word plus the cookie secret material. The secret is the 32-byte cookie value used directly by the protocol, not a textual password.

Payload field Size (bytes) Type Description
format_version 1 Integer API-key payload schema; MUST be 1
key_id 8 Integer API-key identifier
secret 32 Binary Raw 32-byte cookie value
quotas 4 Integer Packed quota word defined below

rl-aes

rl-aes carries the same version and quota word plus a raw 32-byte AES-256 key.

Payload field Size (bytes) Type Description
format_version 1 Integer API-key payload schema; MUST be 1
key_id 8 Integer API-key identifier
secret 32 Binary Raw 32-byte AES-256 key
quotas 4 Integer Packed quota word defined below

API-key quota word, format version 1

The quota word is a little-endian u32. Bit zero below is the least-significant bit.

Bits Width Code Decoded quota
0..4 5 rate_exp rate_buckets_max = 2^rate_exp; codes above 24 are invalid
5..9 5 latency_exp latency_services_max = 2^latency_exp; codes above 24 are invalid
10..14 5 labels_exp metrics_labels_max = 2^labels_exp
15..18 4 buffer_exp latency_buffer_size_max = 2^buffer_exp
19..26 8 dedup_units dedup_ttl_ms_max = dedup_units * 10; only codes 1..200 are valid
27..31 5 window_exp rate_window_size_ms_max = 2^window_exp for 0..30; code 31 means 4294967295

The two c-map-backed cardinalities are encoded as logical capacity exponents, not internal c-map bucket exponents. This matches the allocator’s power-of-two geometry and prevents a credential from advertising an arbitrary capacity that the server cannot allocate exactly. All four exponent-encoded count quotas MUST be exact powers of two when written. dedup_ttl_ms_max MUST be a multiple of 10 ms in the range 10..2000 ms. rate_window_size_ms_max MUST be an exact power of two through 2^30, or 4294967295 for the complete u32 window range.

The canonical default quota word is 0xf8f2b150 (bytes 50 b1 f2 f8) and decodes to 65,536 rate buckets, 1,024 latency services, 4,096 metrics labels, a latency buffer of 32, a 300 ms deduplication cap, and the complete u32 rate-window range. Canonical payload and Bech32 examples are recorded in api_key_v1_test_vectors.json is the normative, cross-language conformance set. It covers the representable boundaries, every control-plane plan, reserved exponent values, invalid deduplication codes, unsupported versions, and invalid payload lengths. Rust server and control-plane tests consume it directly. Standalone C and Python repositories consume generated copies; check_api_key_v1_vectors.py verifies that those copies have not drifted.

Credential Validation Rules

  • Unknown HRPs MUST be rejected.
  • rl-secret MUST contain exactly 32 payload bytes.
  • rl-secret is unchanged and does not carry a format-version byte.
  • rl-none MUST contain exactly 13 payload bytes.
  • rl-cookie and rl-aes MUST each contain exactly 45 payload bytes.
  • API keys MUST carry format_version = 1. Legacy unversioned payloads and unknown versions MUST be rejected; runtime readers MUST NOT infer defaults.
  • In tenant-management ADD mutations, the decoded key_id inside rl-none, rl-cookie, or rl-aes MUST equal the key_id from the outer Tenant Header.
  • Packed quota codes MUST satisfy the version-1 validation rules above.

Auth Header

Auth can be one of the following TLV structures. It MUST immediately follow the tenant TLV. If authentication fails, Ratelimitly MUST act as a blackhole by dropping the request and sending no response.

4.1 No Authentication

Used for closed systems where security is not an issue or for testing.

TLV Field Size (bytes) Type Description
tlv_type 2 Integer Auth None 0x414E (“NA” in hexdump)
tlv_size 2 Integer Overall size of TLV parameter (always 4; header only, no body)

Cookie authentication can be used in scenarios where man-in-the-middle attacks are considered highly unlikely. The integrity of the packet is not guaranteed by the Cookie.

+------------+-----------+--------------+---------+---------------------------------------------+
|            | TLV Field | Size (bytes) | Type    | Description                                 |
+------------+-----------+--------------+---------+---------------------------------------------+
| TLV Header | tlv_type  | 2            | Integer | Auth Cookie 0x4143 ("CA" when viewed in     |
|            |           |              |         | hexdump)                                    |
|            +-----------+--------------+---------+---------------------------------------------+
|            | tlv_size  | 2            | Integer | Overall size of TLV parameter               |
|            |           |              |         | including header and value(s)               |
|            |           |              |         | This value is always 36 (4 + 32).           |
|            |           |              |         | TLV body length = `tlv_size - 4` = 32.      |
+------------+-----------+--------------+---------+---------------------------------------------+
| TLV Body   | cookie    | 32           | Binary  | The Cookie is the SHA256 hash of the secret |
|            |           |              |         | shared by the client and server.            |
|            |           |              |         |                                             |
|            |           |              |         | SHA256("secret password") = Cookie          |
|            |           |              |         | The Cookie is not stored as a hex string,   |
|            |           |              |         | it is stored as raw binary data.            |
+------------+-----------+--------------+---------+---------------------------------------------+

Cookie-authenticated API keys use an rl-cookie Bech32 credential. The on-wire Cookie Auth TLV carries the same 32-byte cookie value provisioned in that API key.

AES-256-GCM Encryption

AES-256-GCM provides authenticated encryption (confidentiality + integrity) with unique symmetric keys per tenant. This is the recommended authentication method for production deployments.

+------------+----------------+--------------+---------+----------------------------------------------------+
|            | TLV Field      | Size (bytes) | Type    | Description                                        |
+------------+----------------+--------------+---------+----------------------------------------------------+
| TLV Header | tlv_type       | 2            | Integer | Auth AES-256-GCM 0x4541 ("AE" when viewed in       |
|            |                |              |         | hexdump)                                           |
|            +----------------+--------------+---------+----------------------------------------------------+
|            | tlv_size       | 2            | Integer | Overall size of TLV parameter                      |
|            |                |              |         | including header and value(s)                      |
|            |                |              |         | This value is always 32 (4-byte header + 28-byte   |
|            |                |              |         | body).                                             |
|            |                |              |         | TLV body length = `tlv_size - 4` = 28.             |
+------------+----------------+--------------+---------+----------------------------------------------------+
| TLV Body   | nonce          | 12           | Binary  | 96-bit nonce for AES-GCM encryption                |
|            +----------------+--------------+---------+----------------------------------------------------+
|            | auth_tag       | 16           | Binary  | 128-bit authentication tag from AES-GCM            |
+------------+----------------+--------------+---------+----------------------------------------------------+

AES-authenticated API keys use an rl-aes Bech32 credential. The on-wire AES Auth TLV carries nonce and authentication tag only; the API key’s AES-256 secret is supplied out of band by the rl-aes credential.

AES-256-GCM Normative Framing

The following rules are normative for interoperability:

  • Encryption scope: The PDU bytes are encrypted. Tenant TLV and Auth TLV remain plaintext.
  • Ciphertext placement: Ciphertext replaces the plaintext PDU in-place and has the same byte length as the plaintext PDU.
  • AAD bytes: AAD is the exact byte sequence tenant_tlv || auth_tlv_prefix, where auth_tlv_prefix is tlv_type(2) || tlv_size(2) || nonce(12).
  • Auth tag: auth_tag is the 16-byte AES-GCM tag computed over ciphertext and the AAD above.
  • Nonce uniqueness: Nonce MUST be unique per AES key. Reusing a nonce with the same key is forbidden.
  • Auth failure behavior: If tag verification fails, server MUST drop the packet and send no response.
  • Replay handling: Replay decisions are defined by server-side deduplication keyed by (pdu_type, tenant_mgmt_flag, key_id, unique_id); AES nonce is not a replay-decision key.

General PDU Format

The general format of a Ratelimitly Protocol Data Unit (PDU) consists of a PDU header followed by a body as outlined in the following:

+------------------------------------------------------------------------+
| Ratelimitly PDU                                                        |
+---------------------------------------------------+--------------------+
| General PDU Header (mandatory)                    | Optional           |
+----------+----------+--------------+--------------+--------------------+
| pdu_type | pdu_size | <<reserved>> | <<reserved>> | PDU body           |
+----------+----------+--------------+--------------+--------------------+
| 2 bytes  | 2 bytes  | 2 bytes      | 2 bytes      | pdu_size - 8 bytes |
|          |          |              |              | (body only)        |
+----------+----------+--------------+--------------+--------------------+

pdu_size is always the total PDU length (header + body).

Ratelimitly PDU Definitions

This section defines the various operational PDUs that make up the Ratelimitly protocol.

PDU Type Summary

The following PDU types are defined in this protocol:

PDU Type Hex Value ASCII Description
Rate Request 0x5452 “RT” Request rate-limited tokens with guards
Rate Response 0x5252 “RR” Response with token grants and latency metrics
Latency Report 0x524C “LR” Fire-and-forget latency metric report
Tenant Mutation 0x544D “TM” Tenant configuration mutation (add/update/remove)
Tenant Ack 0x5441 “TA” Acknowledgment response for tenant mutations
Metrics Query 0x514D “MQ” Administrative metrics snapshot request
Metrics Response 0x524D “MR” Administrative metrics snapshot response

Rate Request

The essence of the Ratelimitly protocol is that clients request rate-limited tokens with dynamic rules and latency guards. Clients specify resource IDs, rate limits, and latency thresholds directly in each request. Ratelimitly servers evaluate guard conditions and grant tokens based on current resource state.

rate_request PDU

The format of the Ratelimitly rate_request PDU is defined in the following table:

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x5452 (“RT” when viewed in hexdump)
pdu_size 2 Integer Defines the overall size of the rate_request PDU (PDU body length = pdu_size - 8)
dedup_ttl_ms 4 Integer Required replay-window request in milliseconds
guard_count 2 Integer Number of guard blocks that follow
resource_count 2 Integer Number of resource blocks that follow
Guard Blocks Variable Binary guard_count × 40-byte guard blocks
Resource Blocks Variable Binary resource_count × 28-byte resource blocks
TLV Parameters Variable TLV Optional metrics label

dedup_ttl_ms (Rate Request)

dedup_ttl_ms is a required per-request replay window for mutating rate-limit requests.

Client intent:

  • the client composes the request with a concrete replay window
  • the client should not resend the request after time_stamp + dedup_ttl_ms

Quota interaction:

  • each tenant credential carries a quota field dedup_ttl_ms_max
  • clients should not set dedup_ttl_ms above dedup_ttl_ms_max

Server behavior:

  • the server accepts, rejects, or clamps the requested value according to implementation policy
  • while deduplication is healthy, the server guarantees replay behavior for the accepted window
  • if deduplication becomes unhealthy, replay guarantees are suspended and this must be surfaced by logs and metrics

This field controls replay behavior only. It does not replace request freshness validation via time_stamp.

unique_id

The unique_id field is a 16-byte identifier for a logical request instance. Clients may resend the same mutating request after packet loss by reusing the same unique_id.

Deduplication Key:

Replay and deduplication decisions are scoped by:

  • pdu_type
  • tenant_mgmt_flag
  • key_id
  • unique_id

This prevents collisions between different mutating request classes that happen to reuse the same key_id and unique_id.

Healthy-State Guarantee:

While the server deduplication subsystem is healthy, the server guarantees that a duplicate mutating request received within its accepted dedup_ttl_ms window will replay the cached result rather than being reprocessed.

Degraded-State Behavior:

Under deduplication-capacity pressure or replay-state loss, implementations may evict live deduplication entries. When that happens:

  • the server MUST emit ERROR logs
  • the server MUST expose unhealthy deduplication state via metrics and/or health status
  • replay guarantees for live requests are suspended until the deduplication subsystem returns to healthy operation

For Rate Request, degraded-state reprocessing is semantically undesirable but possible once replay state has been lost. For Tenant Mutation, degraded-state re-execution is acceptable because the operation semantics are idempotent enough for this proof-of-concept.

A 16-byte field is provided to allow clients to populate it with a globally unique value for each request. We recommend using Universally Unique IDentifier version 4 (UUIDv4), defined as a 128-bit non-string binary value (16-bytes). For rendering purposes, the recommended human readable representation is a lowercase hex dump as a sequence of 16 bytes in the network byte order; as a 32 character ASCII string with no spaces and no leading 0x. This guarantees consistency between client and server for troubleshooting and correlation of logs. Example: 00004c2fd0624dea93a44654506faba7. Note that client and server do not need to agree on endianness, as long as the values are unique.

time_stamp

The time_stamp field is an 8-byte integer that defines the time at which the request message was composed in milliseconds since the UNIX Epoch, 1970-01-01 00:00:00 +0000 (UTC).

This field is used for request freshness validation and trivial rejection of stale packets. It does not define the deduplication or replay window.

Replay-window control for mutating requests is carried explicitly in the mutating PDU body via dedup_ttl_ms.

Deduplication Health Model

The protocol defines replay semantics for mutating request PDUs under the assumption that the server deduplication subsystem is healthy.

A healthy deduplication subsystem guarantees replay for admitted mutating requests during the accepted dedup window.

Implementations may degrade under deduplication-capacity pressure or replay-state loss. When that occurs:

  • replay guarantees are suspended
  • the server MUST emit ERROR logs
  • the server MUST expose unhealthy deduplication metrics and/or health state

Clients and operators should treat unhealthy deduplication state as a degradation of duplicate-suppression guarantees.

Guard Blocks

Guard blocks are fixed 40-byte structures that specify latency thresholds and latency tracking configuration. Guards act as preconditions for rate limiting - if any guard fails (current latency exceeds threshold), the entire request is rejected and no tokens are consumed from any resource. This enables load shedding by preventing token consumption when the system is under stress.

Guard Semantics:

  • Load Shedding: Guards protect against consuming tokens when latency indicates system stress
  • Atomic Evaluation: All guards must pass for any tokens to be granted
  • Real-time Metrics: Server provides current latency observations for client decision-making
  • Self-Contained Configuration: Each guard carries its own latency tracking parameters

Guard Block (40 bytes total):

Field name Size (bytes) Type Description
latency_tracker_id 16 Binary Canonical content-defined latency-tracker identifier
ttl_ms 4 Integer Time-to-live for latency samples in milliseconds
max_samples 4 Integer Maximum number of samples to keep for latency tracking
buffer_size 4 Integer Circular buffer size for latency tracking
min_sample_threshold 4 Integer Minimum insertion rate required for reliable minimum
latency_threshold 4 Integer Maximum acceptable latency (units must be uniform)
current_latency 4 Integer Server fills with current observed latency (response only)
Content-defined latency-tracker identity

Latency trackers do not require prior server-side registration. A client starts with an application-defined latency-tracker name, represented as a byte string, and the state-defining tracker configuration. A latency-tracker name identifies one logical latency signal within a tenant; it is not an r-server name, a DNS name, or necessarily a network service.

Before constructing either a guard block or a service latency block, a conforming client MUST derive latency_tracker_id as:

first_16_bytes(
  BLAKE2s-256(
    "ratelimitly.latency-tracker.v1\0"
    || uint32_le(latency_tracker_name_length)
    || latency_tracker_name_bytes
    || uint32_le(ttl_ms)
    || uint32_le(max_samples)
    || uint32_le(buffer_size)
    || uint32_le(min_sample_threshold)
  )
)

Here:

  • the quoted domain expands to 30 ASCII bytes followed by one zero byte (31 bytes total);
  • every length and configuration value is an unsigned 32-bit integer encoded in little-endian order;
  • latency_tracker_name_length is the number of bytes, not characters;
  • string-oriented APIs MUST encode names as UTF-8, while byte-oriented APIs MUST hash the exact supplied bytes, including embedded zero bytes;
  • the final effective configuration participates in the identity; when buffer_size is omitted and resolved from the API-key limit, the resolved value MUST be used;
  • tenant identity is omitted because every latency-tracker map is tenant-isolated.

latency_threshold, current_latency, and observed_latency MUST NOT participate in the identity. A threshold is a request-specific query over a tracker, current_latency is a server result, and observed_latency is one reported sample.

BLAKE2s-256 means the standard 32-byte digest. Implementations then retain its first 16 bytes; they MUST NOT substitute the distinct BLAKE2s variant configured with a 16-byte digest size. Shared known-answer vectors are in latency_tracker_id_vectors.json.

An r-server MUST use the received 16-byte latency_tracker_id directly as the tenant-local state key. A live entry also retains ttl_ms, max_samples, buffer_size, and min_sample_threshold. If a guard or report presents the same live ID with a different value for any of those fields, the server MUST treat the containing PDU as malformed:

  • a rate request must not mutate guard counters or consume resource tokens;
  • a latency-report PDU must not record any partial set of samples;
  • the mismatch is neither a guard failure nor a rate denial;
  • because the MVP has no general error PDU, the server drops the packet;
  • implementations SHOULD count the event with a bounded diagnostic metric and SHOULD NOT emit an unbounded per-packet log.

Once the latency-tracker state expires, the server may forget the binding and a later request may establish a new one.

Resource Blocks

Resource blocks are fixed 28-byte structures that specify rate limits and token requests. Each resource block contains:

Field name Size (bytes) Type Description
bucket_id 16 Binary Canonical content-defined resource identifier
window_size_ms 4 u32 Rate-limiting window in milliseconds
rate_limit 4 u32 Maximum tokens in the window (request) / actual rate (response)
tokens_requested 2 Integer Number of tokens requested (request) / deficit (response)
padding 2 Integer Padding for 4-byte alignment (zero-copy parsing)
Content-defined resource identity

Resources do not require prior server-side registration. A client starts with an application-defined logical bucket name, represented as a byte string, and the requested window_size_ms and rate_limit. Before constructing the resource block, a conforming client MUST derive bucket_id as:

first_16_bytes(
  BLAKE2s-256(
    "ratelimitly.resource.v1\0"
    || uint32_le(bucket_name_length)
    || bucket_name_bytes
    || uint32_le(window_size_ms)
    || uint32_le(rate_limit)
  )
)

Here:

  • the quoted domain expands to 23 ASCII bytes followed by one zero byte (24 bytes total);
  • bucket_name_length, window_size_ms, and rate_limit are unsigned 32-bit integers encoded in little-endian order;
  • bucket_name_length is the number of bytes, not characters;
  • string-oriented APIs MUST encode bucket names as UTF-8, while byte-oriented APIs MUST hash the exact supplied bytes, including embedded zero bytes;
  • tokens_requested is operation-specific consumption and MUST NOT participate in the identity;
  • tenant identity is omitted because every rate-state map is tenant-isolated.

BLAKE2s-256 means the standard 32-byte digest. Implementations then retain its first 16 bytes; they MUST NOT substitute the distinct BLAKE2s variant configured with a 16-byte digest size. Shared known-answer vectors are in resource_id_vectors.json.

The derivation always uses the configured request rate_limit. A response reuses that wire field for actual_rate; clients MUST NOT derive a future identifier from the response value.

An r-server MUST use the received 16-byte bucket_id directly as the tenant-local state key. A live entry also retains the request’s window_size_ms and rate_limit. If another request presents the same bucket_id with a different value for either field, the server MUST treat the request as malformed:

  • no resource in the request may consume tokens or otherwise mutate rate state;
  • the server MUST NOT encode the mismatch as a rate denial;
  • because the MVP has no general error PDU, the server drops the packet and the client observes request failure;
  • implementations SHOULD count the event with a bounded diagnostic metric and SHOULD NOT emit an unbounded per-packet log.

This validation detects client errors and the extremely unlikely truncated-hash collision while the entry is live. Once its rate state expires, the server may forget the binding. There is no separate resource TTL field: window_size_ms determines when inactive rate state expires.

Rate Request TLV Parameters

The rate request PDU supports an optional metrics label TLV for aggregating user-facing metrics.

Metrics Label TLV Parameter

The metrics label TLV parameter is used to specify a label for user-facing metrics aggregation.

Field name Size (bytes) Type Description
tlv_type 2 Integer 0x4C4D (“ML” when viewed in hexdump)
tlv_size 2 Integer Overall size of TLV parameter (TLV body length = tlv_size - 4)
str_length 2 Integer Number of bytes in byte string
label varies bytes UTF-8 string for metrics aggregation
padding varies bytes Optional padding to 4-byte boundary for alignment

rate_response PDU

The format of the Ratelimitly rate_response PDU is defined in the following table. The response is built by modifying the request PDU in-place to achieve zero-allocation performance.

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x5252 (“RR” when viewed in hexdump)
pdu_size 2 Integer Defines the overall size of the rate_response PDU (PDU body length = pdu_size - 8)
<<reserved>> 2 Integer MUST be set to zeroes (reserved for future use)
<<reserved>> 2 Integer MUST be set to zeroes (reserved for future use)
guard_count 2 Integer Number of guard blocks (copied from request)
resource_count 2 Integer Number of resource blocks (copied from request)
Guard Blocks Variable Binary Guard blocks with current_latency filled (40 bytes each)
Resource Blocks Variable Binary Resource blocks with tokens_requested→deficit, rate_limit→actual_rate
TLV Parameters Variable TLV Optional server data (may differ from or omit request TLVs)

Response Semantics

The rate_response PDU uses payload-driven status determination with atomic token consumption:

  • Success: All guard blocks have current_latency < latency_threshold AND all resource blocks have deficit = 0
    • Token Consumption: All requested tokens are atomically consumed by the server
  • Guard Failure: Any guard block has current_latency >= latency_threshold
    • Token Consumption: No tokens are consumed from any resource
  • Rate Limited: Any resource block has deficit > 0
    • Token Consumption: No tokens are consumed from any resource
    • Partial Information: Response shows which specific buckets lack capacity

Atomic Semantics: Token consumption is all-or-nothing. If any guard fails or any resource has insufficient tokens, the entire request is rejected and no tokens are consumed from any resource. This prevents partial resource consumption in multi-resource requests.

High Availability (HA) Support

The dual-purpose key_id field enables High Availability deployments where multiple R-server instances run simultaneously:

  • Server Identification: Each server overwrites the key_id field with its unique server identifier in responses
  • Age Extraction: Clients can decode the server startup time directly from the high bits of server_id
  • Client Correlation: Clients can compare the response identity with server identities learned through discovery
  • Policy Input: The full identity and decoded startup time are available to a client-side response-selection policy
  • Zero-Allocation Compatibility: Reusing existing field preserves in-place modification for response generation
  • Collision Resistance: Port and node identity reduce accidental collisions between near-simultaneous restarts or replicas

The dual-purpose field provides optimal efficiency while enabling deterministic HA server identification. It does not define a response-selection, stability, quorum, or reliability policy.

server_id Decoding:

Clients and operators can decode response server_id values as follows:

  • start_s_since_2025 = server_id >> 23
  • listener_id = (server_id >> 15) & 0xFF
  • node_id = server_id & 0x7FFF
  • server_start_s = 1735689600 + start_s_since_2025

Client-Policy Boundary:

The wire protocol requires correct response authentication, request correlation, and the response server identity described above. It does not require a client to broadcast, wait for a quorum, prefer a particular server, track reliability, or filter a recently restarted server.

The parameterized oldest-first replicated-delivery strategy in r-client.md is one client usage of these fields. It is intentionally specified outside the wire protocol.

HA Commit Safety (Open Study)

This protocol does not define cross-server commit coordination, quorum semantics, or exactly-once mutation across independent r-servers. In particular, per-server deduplication does not deduplicate a request across different servers.

Some deployments may later study stronger HA modes based on a single commit authority, strongly consistent shared state, read-only replicas, quorum decisions, or another coordination mechanism. Those are potential deployment directions, not requirements or guarantees of this wire protocol.

The client-side replicated usage documented in r-client.md intentionally provides a weaker eventual-convergence model: clients may send resource requests to multiple independent r-servers, and temporary disagreement is possible until state converges through new traffic and TTL expiry. Client response-validation rules (authentication, request correlation, and trusted server identity) do not by themselves provide commit safety.

Response TLV Parameters

The response may optionally include TLV parameters that differ from the request:

  • Omitted entirely: Server can drop TLV section to save bandwidth
  • Server metrics: Performance counters, debug information
  • Future extensions: Reserved for protocol evolution

Clients must not assume response TLVs match request TLVs.

Guard Block Response

In the response, guard blocks are updated with current server-observed latency metrics:

  • current_latency is filled with real-time latency data for the latency_tracker_id
  • Client behavior: Clients MUST set current_latency = 0 in requests (ignored by server)
  • Server behavior: Server sets current_latency to observed value or 0 if no data available
  • If current_latency >= latency_threshold, the entire request is rejected

Resource Block Response

In the response, resource blocks are updated with token grant results and rate information:

  • tokens_requested field is reused as deficit (tokens that could not be granted)
  • rate_limit field is reused as actual_rate (current tokens consumed in the window)
  • Field reuse semantics:
    • Request: tokens_requested = tokens needed, rate_limit = minimum required rate
    • Response: tokens_requested = deficit (unfulfilled tokens), rate_limit = current actual rate
  • deficit = 0 means all requested tokens were granted
  • deficit > 0 means some or all tokens were refused due to rate limiting
  • actual_rate shows current bucket utilization for client decision-making

Latency Report PDU

The latency report PDU is a fire-and-forget message used by clients to report observed latency metrics to the server for load shedding decisions.

Fire-and-Forget Behavior: Servers MUST NOT send responses to latency report PDUs. Clients send these reports and do not expect any acknowledgment.

Latency Report PDU:

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x524C (“LR” when viewed in hexdump)
pdu_size 2 Integer Defines the overall size of the latency_report PDU (PDU body length = pdu_size - 8)
reserved 2 Integer MUST be set to zeroes (reserved for future use)
reserved 2 Integer MUST be set to zeroes (reserved for future use)
service_count 2 Integer Number of service latency blocks that follow
padding 2 Integer Padding for 4-byte alignment
Service Blocks Variable Binary service_count × 36-byte service latency blocks

For latency reports, pdu_size MUST equal 12 + service_count * 36.

Service Latency Block (36 bytes each)

Each service latency block contains the complete service definition plus observation:

Service Latency Block (36 bytes):

Field name Size (bytes) Type Description
latency_tracker_id 16 Binary Canonical content-defined latency-tracker identifier
ttl_ms 4 Integer Time-to-live for latency samples in milliseconds
max_samples 4 Integer Maximum number of samples to keep for latency tracking
buffer_size 4 Integer Circular buffer size for latency tracking
min_sample_threshold 4 Integer Minimum insertion rate required for reliable minimum
observed_latency 4 Integer Client-observed latency (units must be uniform)

Administrative PDUs

Administrative PDUs use the same authenticated UDP channel as tenant mutation traffic, but are not limited to tenant lifecycle operations. This administrative channel is intended for control-plane and operator actions such as tenant configuration changes and metrics collection.

All administrative PDUs MUST be authenticated and encrypted using AES-256-GCM with a dedicated administrative key.

Administrative Authentication

All administrative PDUs must use AES-256-GCM authentication (TLV_AUTH_AES = 0x4541) with a dedicated administrative key that is separate from regular tenant keys.

Admin Key Configuration:

tenant_management:
  auth:
    type: aes
    key: "rl-secret1..."

Authentication Process:

  1. Encryption: Administrative interfaces encrypt administrative PDUs using the tenant management key
  2. Decryption: Server decrypts and authenticates operations using the same tenant management key
  3. Authorization: Only requests encrypted with the correct tenant management key are processed
  4. Key Material: The tenant management key is provisioned as an rl-secret... Bech32 string carrying exactly one raw 32-byte AES key

Key Usage Rules:

  • Administrative operations (tenant_mgmt_flag=1): MUST use the dedicated tenant management key
  • Regular operations (tenant_mgmt_flag=0): Use tenant-specific keys from tenant configuration
  • Key configuration: Tenant management key is provided at server startup as rl-secret
  • Runtime keys: Tenant-specific keys are delivered at runtime via tenant management requests as rl-none, rl-cookie, or rl-aes

Security Considerations:

  • Tenant management key must be kept separate from regular tenant keys
  • Only authorized administrative interfaces should have access to this key
  • Different servers can use different tenant management keys for isolation
  • Key rotation should be coordinated between administrative interfaces and servers

Tenant Mutation PDU

The tenant_mutation PDU initiates tenant configuration operations:

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x544D (“TM” when viewed in hexdump)
pdu_size 2 Integer Overall size of the tenant_mutation PDU (PDU body length = pdu_size - 8)
operation 1 Integer Tenant operation type (see operation types below)
flags 1 Integer Operation flags (reserved, MUST be zero)
dedup_ttl_ms 4 Integer Required replay-window request in milliseconds
Mutation Data Variable Binary Operation-specific payload

dedup_ttl_ms (Tenant Mutation)

dedup_ttl_ms is a required replay-window request for tenant-management mutations.

Its purpose is reply recovery rather than mutation correctness. Tenant-management operations in this proof-of-concept are idempotent enough that re-execution is acceptable if replay state has been lost.

Healthy-state behavior:

  • the server should cache the first Tenant Ack for the request key and replay it for duplicates received within the accepted dedup window

Degraded-state behavior:

  • if replay state has been lost, the server may execute the mutation again and return a fresh Tenant Ack
  • replay-state loss must be surfaced through ERROR logs and unhealthy deduplication metrics/state

Operation Types:

Operation Code Name Description
0x03 TENANT_ADD Add new tenant configuration
0x05 TENANT_REMOVE Remove tenant configuration

Thread Steering:

The key_id field in the Tenant Header contains the target tenant ID for the operation, ensuring:

  • ADD operations: New tenant assigned to appropriate thread via hash(tenant_id) % num_threads
  • REMOVE operations: Deletions occur on the thread that owns the tenant

This maintains thread affinity and mutual exclusion for all tenant operations.

Mutation Data Formats:

ADD Tenant (Operation 0x03):

Behavior: Upsert operation - creates new tenant or overwrites existing tenant with the same ID.

Field name Size (bytes) Type Description
name_len 1 Integer Length of tenant name (0-255)
name Variable Binary UTF-8 encoded tenant name
tenant_key_len 1 Integer Length of tenant credential string (0-255)
tenant_key Variable Binary UTF-8 Bech32 tenant credential: rl-none, rl-cookie, or rl-aes

The tenant_key embeds all tenant authentication and quota data:

  • authentication method (via HRP)
  • key_id
  • secret material, if required by the auth method
  • rate_buckets_max
  • latency_services_max
  • metrics_labels_max
  • latency_buffer_size_max
  • dedup_ttl_ms_max
  • rate_window_size_ms_max

Deterministic validation rules:

  • Unknown tenant-key HRPs MUST cause the entire tenant mutation to be rejected.
  • tenant_key MUST decode according to the payload shape mandated by its HRP.
  • The decoded key_id MUST equal the Tenant Header key_id.
  • Rejected tenant mutations MUST follow tenant mutation failure behavior (no acknowledgment response).

Before admitting an ADD, a server MUST calculate the runtime storage implied by the decoded quotas and compare it with its current allocation budget and available system/cgroup memory. If the estimate overflows or exceeds available headroom, the server MUST reject the mutation before publishing any tenant state. An operating-system allocation failure MUST likewise be handled as a tenant mutation failure, not a process panic. Such failures increment the server status field allocation_failures_total.

REMOVE Tenant (Operation 0x05):

No additional payload required. The tenant ID to remove is specified in the key_id field of the Tenant Header.

Simplified Configuration Data Format

Tenant mutation payloads use length-prefixed binary fields for optimal parsing. The tenant_key field is a UTF-8 Bech32 string whose decoded payload carries the tenant authentication material and quotas.

Quota configuration is embedded directly in the Bech32 tenant credential. This keeps tenant authentication material and tenant quotas in one canonical string.

Quota Enforcement Semantics

Quota enforcement uses request-level atomic behavior, with one exception for metrics label cardinality control:

  • Rate bucket quota exceeded (rate_buckets_max): Request is rejected atomically (no tokens consumed).
  • Latency service quota exceeded (latency_services_max): Request/report is rejected atomically.
  • Latency shape exceeded: Request/report is rejected atomically if buffer_size > latency_buffer_size_max.
  • Rate-window shape exceeded: A rate request is rejected atomically if any resource has window_size_ms > rate_window_size_ms_max. Conforming clients MUST validate the complete logical request before DNS resolution, serialization, or transmission. The server independently validates every resource before evaluating guards or creating/updating either rate-counter or latency-tracker state. An over-quota authenticated request receives no response.
  • Metrics labels quota exceeded (metrics_labels_max): Request is still processed, but label is rewritten to the fixed overflow label overflow.

This preserves strict all-or-nothing resource semantics while bounding metrics-label cardinality without turning otherwise valid requests into hard failures.

rate_buckets_max and latency_services_max bound live logical entries even when an implementation uses a larger minimum physical table for efficient map geometry. Physical spare slots MUST NOT increase either API-key quota.

Servers expose rejected request shapes through the bounded global Prometheus counter ratelimitly_api_key_quota_rejections_total. It intentionally has no API-key, resource, or service labels.

The server MUST use latency_buffer_size_max as the inline monotonic-point capacity of every latency-service arena slot for the tenant. A service may request a smaller active buffer_size, but that does not change the tenant’s uniform slot stride. The arena backing is heap/mmap memory; the slot itself MUST contain no reference or pointer to external sample storage.

Authentication Type Selection:

The tenant authentication method is selected by the tenant_key HRP:

  • rl-none = no tenant request authentication
  • rl-cookie = Cookie authentication
  • rl-aes = AES-256-GCM authentication

Upsert Semantics:

The ADD operation provides upsert behavior:

  • CREATE: If tenant ID doesn’t exist, creates new tenant
  • OVERWRITE: If tenant ID exists, completely replaces existing configuration

Tenant Acknowledgment PDU

The tenant_ack PDU provides acknowledgment responses only for successful tenant mutation operations:

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x5441 (“TA” when viewed in hexdump)
pdu_size 2 Integer Overall size of the tenant_ack PDU (always 12; PDU body length = pdu_size - 8 = 4)
operation 1 Integer Original operation type that was processed
status 1 Integer Always SUCCESS (0x00) - only sent for successful operations
reserved 2 Integer MUST be set to zeroes (reserved for future use)
reserved 2 Integer MUST be set to zeroes (reserved for future use)
padding 2 Integer Padding for 4-byte alignment (MUST be zero)

Status Codes:

Status Code Name Description
0x00 SUCCESS Operation completed successfully

Acknowledgment Behavior:

  • Success: Server sends tenant_ack PDU with status SUCCESS (0x00)
  • Any Failure: Server sends no response (blackhole behavior for authentication failures, validation errors, etc.)

Tenant mutation failures include (non-exhaustive): authentication/tag failure, malformed payload, non-zero reserved fields, unknown operation, invalid Bech32 credential, mismatched key_id, and invalid quota values.

This design ensures that only successful operations generate network traffic, maintaining the fire-and-forget performance characteristics while providing confirmation of successful mutations.

Status Query PDU

The status_query PDU requests a single queue-local status snapshot from the administrative channel. This PDU is read-only, does not mutate tenant/API-key state, and does not reuse Tenant Ack.

This is the canonical UDP health/status probe for control-plane and operator tooling. Implementations MUST NOT use synthetic tenant mutations as a status probe when this PDU is available.

A Status Query is intentionally not scoped to an API key. The request Tenant Header key_id field SHOULD be zero and MUST NOT be interpreted as an API key identifier by the server. The response Tenant Header key_id is overwritten with the responding server_id, allowing the client to identify which listener produced the snapshot.

One Status Query produces at most one Status Response. The response is a fixed-width binary PDU so the one-frame guarantee is mechanical: no strings, interface names, labels, API-key lists, or other variable-length values are included in V1.

Status queries are scoped to the listener queue that receives the UDP datagram. The server MUST NOT require cross-queue aggregation or hot-path synchronization to answer the query. Node-wide values included in the response, such as Linux memory snapshots, MUST be cheap point-in-time readings or cached snapshots.

Authentication failures remain blackhole/no-response behavior. Status codes in the response apply only after successful administrative authentication and PDU parsing.

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x5153 (“SQ” when viewed in hexdump)
pdu_size 2 Integer Overall size of the status_query PDU; MUST be 8
schema_version 1 Integer Status schema version; MUST be 1 for V1
query_kind 1 Integer Query variant; 0x00 = CURRENT_STATUS
reserved 2 Integer MUST be zero

query_kind values:

query_kind Name Query Data
0x00 CURRENT_STATUS empty

Status Response PDU

The status_response PDU returns one fixed-width V1 status record for an authenticated administrative status request.

The V1 PDU size is exactly 448 bytes. With the existing administrative response envelope this is approximately:

  • Tenant TLV: 40 bytes
  • AES Auth TLV: 32 bytes
  • encrypted status_response PDU: 448 bytes
  • total UDP payload: 520 bytes

This remains below the current 1200-byte packet budget.

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x5253 (“SR” when viewed in hexdump)
pdu_size 2 Integer Overall size of the status_response PDU; MUST be 448
schema_version 1 Integer Status schema version; MUST be 1 for V1
status_code 1 Integer 0x00 = OK; non-zero values reserved for future errors
status_flags 2 Bitset Validity/status bitset for optional snapshot fields
wire_protocol_version 2 Integer Wire protocol version understood by this server
record_len 2 Integer Bytes after the first 8-byte status header; MUST be 440
server_id 8 Integer Structured response server id
node_id 4 Integer Configured server node id
listener_id 2 Integer Listener id encoded into server_id
queue_index 2 Integer Listener queue index that answered
udp_port 2 Integer UDP port that answered
degraded_reason_code 2 Integer 0 = none; non-zero values reserved for degraded reasons
started_at_ms 8 Integer Unix epoch milliseconds for server/listener start
uptime_ms 8 Integer Listener/server uptime in milliseconds
build_fingerprint 8 Integer Fixed build id/hash prefix; zero if unavailable
health_flags 4 Bitset Ready/datapath/accepting flags
backend 2 Integer 0 = unknown, 1 = XDP, 2 = UDP fallback
xdp_mode 2 Integer 0 = n/a, 1 = zero-copy, 2 = copy, 3 = disabled
ifindex 4 Integer Interface index, or u32::MAX if unavailable
rx_queue_id 4 Integer RX queue id, or u32::MAX if unavailable
worker_cpu 4 Integer Worker CPU id, or u32::MAX if unavailable
reserved0 4 Integer MUST be zero
rx_packets 8 Integer Queue-local packets received by this listener
tx_packets 8 Integer Queue-local packets answered by this listener
rx_dropped 8 Integer Queue-local receive/drop counter
tx_dropped 8 Integer Queue-local transmit/drop counter
parse_errors 8 Integer Queue-local protocol parse failures
auth_failures 8 Integer Queue-local authentication failures
admin_requests_total 8 Integer Queue-local administrative request attempts
system_memory_total_bytes 8 Integer Node memory total, or u64::MAX if unknown
system_memory_available_bytes 8 Integer Node memory available, or u64::MAX if unknown
cgroup_memory_limit_bytes 8 Integer Cgroup memory limit, or u64::MAX if unconstrained/unknown
process_rss_bytes 8 Integer Process resident set size, or u64::MAX if unknown
rl_memory_budget_bytes 8 Integer RateLimitly memory budget, or u64::MAX if unknown
rl_memory_allocated_bytes 8 Integer RateLimitly allocated/estimated runtime state bytes
rl_memory_headroom_bytes 8 Integer Budget minus allocated bytes, or u64::MAX if unknown
dedup_cache_bytes 8 Integer This listener’s dedup allocation/estimate
api_key_rate_map_bytes 8 Integer Admitted API-key rate-map allocation/estimate
api_key_latency_map_bytes 8 Integer Admitted API-key latency-map allocation/estimate
api_key_latency_buffer_bytes 8 Integer Admitted latency-buffer allocation/estimate
api_key_metrics_label_bytes 8 Integer Admitted metrics-label allocation/estimate
other_runtime_bytes 8 Integer Remaining estimated RateLimitly runtime state
allocation_failures_total 8 Integer Allocation/admission failures
api_keys_loaded 8 Integer API keys loaded on this listener
api_key_capacity 8 Integer Admitted API-key capacity/budget, or u64::MAX if unknown
api_key_table_generation 8 Integer Monotonic generation for API-key table changes
api_key_table_hash 8 Integer Fixed hash of loaded API-key ids and quotas
rate_bucket_quota_sum 8 Integer Sum across admitted API keys
latency_service_quota_sum 8 Integer Sum across admitted API keys
metrics_label_quota_sum 8 Integer Sum across admitted API keys
latency_buffer_quota_sum 8 Integer Sum across admitted API keys
rate_requests_total 8 Integer Queue-local rate request counter
rate_success_total 8 Integer Queue-local successful rate request counter
rate_limited_total 8 Integer Queue-local rate-limited request counter
guard_failed_total 8 Integer Queue-local guard-failed request counter
latency_reports_total 8 Integer Queue-local accepted latency-report service-block counter
dedup_hits_total 8 Integer Queue-local dedup hit counter
dedup_misses_total 8 Integer Queue-local dedup miss counter
tenant_mutations_total 8 Integer Queue-local tenant/API-key mutation counter
tenant_mutation_failures_total 8 Integer Queue-local tenant/API-key mutation failure counter
dedup_ttl_ms_max 4 Integer Server-side maximum accepted dedup_ttl_ms for this listener, or 0 if unknown
reserved1 60 Binary MUST be zero in V1; reserved for fixed-width growth

dedup_ttl_ms_max is the listener/server cap, not the API-key quota embedded in an rl-* credential. Clients SHOULD bound request retry/replay windows by min(api_key.dedup_ttl_ms_max, status.dedup_ttl_ms_max) when the DEDUP_TTL_MS_MAX_VALID status flag is set. If that flag is not set, clients MUST treat dedup_ttl_ms_max as unknown; this preserves compatibility with older V1 servers that still return zero in the formerly reserved bytes.

status_flags validity bits:

Bit Name Meaning
0 SYSTEM_MEMORY_TOTAL_VALID system_memory_total_bytes is known
1 SYSTEM_MEMORY_AVAILABLE_VALID system_memory_available_bytes is known
2 CGROUP_MEMORY_LIMIT_VALID cgroup_memory_limit_bytes is constrained and known
3 PROCESS_RSS_VALID process_rss_bytes is known
4 RL_MEMORY_BUDGET_VALID rl_memory_budget_bytes is known
5 RL_MEMORY_ALLOCATED_VALID rl_memory_allocated_bytes is known
6 RL_MEMORY_HEADROOM_VALID rl_memory_headroom_bytes is known
7 DEDUP_TTL_MS_MAX_VALID dedup_ttl_ms_max is known

Fields whose validity bit is not set MUST be treated as unknown by clients, regardless of the numeric value carried in the field. Clients SHOULD prefer rl_memory_headroom_bytes over raw Linux MemAvailable for allocation decisions when the headroom validity bit is set.

health_flags bits:

Bit Name Meaning
0 READY Listener is initialized enough to answer status
1 DATAPATH_RUNNING Datapath/listener loop is running
2 ACCEPTING_RATE_REQUESTS Listener accepts rate request PDUs
3 ACCEPTING_LATENCY_REPORTS Listener accepts latency report PDUs
4 ACCEPTING_TENANT_MUTATIONS Listener accepts tenant/API-key mutation PDUs

For V1, CP may consider a queue healthy when all of the following are true:

  • the response authenticates under the instance’s administrative key
  • status_code == 0
  • health_flags includes READY, DATAPATH_RUNNING, ACCEPTING_RATE_REQUESTS, ACCEPTING_LATENCY_REPORTS, and ACCEPTING_TENANT_MUTATIONS
  • udp_port and queue_index match the probed queue endpoint

Metrics Query PDU

The metrics_query PDU requests a bounded metrics snapshot from the administrative channel. This PDU is read-only. It does not participate in replay guarantees and does not reuse Tenant Ack.

This administrative UDP path is the primary protocol interface for metrics retrieval. Implementations may also expose an optional HTTP/Prometheus endpoint, but that surface is secondary and mainly useful for testing, development, or local operator convenience.

Metrics queries are intentionally scoped to the responding listener thread. This matches the current Rust implementation, where metrics are collected and exposed per listener thread today. The protocol MUST NOT require cross-thread aggregation or synchronization for metrics retrieval.

Every Metrics Query is also scoped to exactly one tenant. The Tenant Header key_id field in the request MUST contain the target tenant_id. This keeps metrics requests on the same queue-local routing path as other tenant-scoped operations. The response still overwrites the Tenant Header key_id with the responding server_id, allowing the client to identify which listener produced the page.

One Metrics Query produces at most one Metrics Response. Pagination is therefore request-driven: the client issues another MQ after the last entry from the previous MR.

Within one listener processing batch, an implementation MUST process at most one Metrics Query for a given tenant. This preserves queue-local batching without creating repeated per-tenant metrics work inside one batch.

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x514D (“MQ” when viewed in hexdump)
pdu_size 2 Integer Overall size of the metrics_query PDU (PDU body length = pdu_size - 8)
metric_family 1 Integer Requested metric family (see family codes below)
query_kind 1 Integer Variant tag within the family (see below)
reserved 2 Integer MUST be zero
Query Data Variable Binary Family-specific payload

Metric Family Codes:

Family Code Name Record Type Returned
0x01 TENANT_SUMMARY One Tenant Summary record
0x02 TENANT_LABEL_COUNTERS Tenant Label Counter records
0x03 TENANT_SERVICE_LATENCY_COUNTERS Tenant Service Latency Counter records

metric_family and query_kind form a tagged union.

Query Variants

TENANT_SUMMARY:

query_kind Name Query Data
0x00 SUMMARY_CURRENT empty

TENANT_LABEL_COUNTERS:

query_kind Name Query Data
0x00 LABELS_FROM_START empty
0x01 LABELS_FROM_CURSOR cursor_len: u16, reserved: u16, cursor, cursor_padding

TENANT_SERVICE_LATENCY_COUNTERS:

query_kind Name Query Data
0x00 SERVICES_FROM_START empty
0x01 SERVICES_FROM_CURSOR cursor_len: u16, reserved: u16, cursor, cursor_padding

Pagination rules:

  • The server MUST fill each MR page with the maximal contiguous slice that fits in one datagram under the implementation packet-size budget.
  • The server MUST return records in an implementation-defined order that is consistent enough for best-effort cursor continuation on that queue:
    • TENANT_SUMMARY: exactly one record when the tenant exists on the queue
    • TENANT_LABEL_COUNTERS: implementation-defined label order
    • TENANT_SERVICE_LATENCY_COUNTERS: implementation-defined service-record order
  • Pagination is cursor-based and the cursor is opaque to the client.
  • For both TENANT_LABEL_COUNTERS and TENANT_SERVICE_LATENCY_COUNTERS, the client resumes by taking the opaque cursor from *_PAGE_MORE and issuing the corresponding *_FROM_CURSOR query.
  • Clients MUST NOT interpret, modify, or construct cursor contents on their own. They MUST echo the cursor bytes exactly as received.
  • The server MAY implement cursors statefully or statelessly.
  • The server SHOULD make cursor continuation best-effort across storage mutation.
  • A cursor MAY become invalid or expire under implementation-defined conditions. The response MUST then use the explicit cursor-invalid page kind for the requested family.
  • A Metrics Query does not create a durable snapshot across pages. Each page is a best-effort point-in-time view when encoded.
  • If the response server_id changes between pages, the client SHOULD restart pagination from the beginning because the listener thread likely restarted.
  • Global or cross-tenant counters are intentionally out of scope for the UDP administrative metrics protocol. They may remain available only through optional Prometheus export or logs.

Metrics Response PDU

The metrics_response PDU returns either a page of records or an explicit query error for an authenticated administrative metrics request.

Authentication failures remain blackhole/no-response behavior. Status codes below apply only after successful administrative authentication and PDU parsing.

Field name Size (bytes) Type Description
pdu_type 2 Integer 0x524D (“MR” when viewed in hexdump)
pdu_size 2 Integer Overall size of the metrics_response PDU (PDU body length = pdu_size - 8)
metric_family 1 Integer Echo of the requested metric family
page_kind 1 Integer Variant tag within the family (see below)
reserved 2 Integer MUST be zero
Page Data Variable Binary Family-specific page payload

metric_family and page_kind form a tagged union.

Response Variants

TENANT_SUMMARY:

page_kind Name Page Data
0x00 SUMMARY_PAGE one Tenant Summary record
0x01 METRICS_NOT_FOUND empty
0x02 METRICS_INVALID_QUERY empty
0x03 METRICS_UNSUPPORTED empty
0x04 METRICS_INTERNAL_ERROR empty

TENANT_LABEL_COUNTERS:

page_kind Name Page Data
0x00 LABELS_PAGE_MORE next_cursor_len: u16, reserved: u16, next_cursor, cursor_padding, one or more Tenant Label Counter records
0x01 LABELS_PAGE_LAST one or more Tenant Label Counter records
0x02 LABELS_EMPTY empty
0x03 METRICS_NOT_FOUND empty
0x04 METRICS_INVALID_QUERY empty
0x05 METRICS_UNSUPPORTED empty
0x06 METRICS_INTERNAL_ERROR empty
0x07 METRICS_RECORD_TOO_LARGE empty
0x08 METRICS_CURSOR_INVALID empty

TENANT_SERVICE_LATENCY_COUNTERS:

page_kind Name Page Data
0x00 SERVICES_PAGE_MORE next_cursor_len: u16, reserved: u16, next_cursor, cursor_padding, one or more Tenant Service Latency Counter records
0x01 SERVICES_PAGE_LAST one or more Tenant Service Latency Counter records
0x02 SERVICES_EMPTY empty
0x03 METRICS_NOT_FOUND empty
0x04 METRICS_INVALID_QUERY empty
0x05 METRICS_UNSUPPORTED empty
0x06 METRICS_INTERNAL_ERROR empty
0x07 METRICS_RECORD_TOO_LARGE empty
0x08 METRICS_CURSOR_INVALID empty

Empty variants mean:

  • the tenant exists on the queue
  • the query is valid
  • there are no records in the requested family at the requested start position or cursor position

METRICS_RECORD_TOO_LARGE means:

  • the tenant exists on the queue
  • the query is valid
  • the next record in the requested family cannot fit into a single UDP response page under the implementation packet-size budget
  • clients SHOULD treat this as a hard stop for that cursor position rather than retrying the same query unchanged

METRICS_CURSOR_INVALID means:

  • the tenant exists on the queue
  • the requested cursor is syntactically well-formed at the PDU level
  • but the server cannot continue that pagination stream from the supplied cursor

Record counts are intentionally omitted from the wire format. Records are parsed until the end of the PDU body using the family-specific entry layout.

Metrics Record Formats

Tenant Summary record (family 0x01)

This record encodes the tenant-scoped summary counters currently exposed by the Rust collector for one tenant on one queue.

Field name Size (bytes) Type Description
requests_success 8 Integer ratelimitly_tenant_requests_total{result="success"}
requests_rate_limited 8 Integer ratelimitly_tenant_requests_total{result="rate_limited"}
requests_guard_failed 8 Integer ratelimitly_tenant_requests_total{result="guard_failed"}
requests_auth_failed 8 Integer ratelimitly_tenant_requests_total{result="auth_failed"}
service_latency_reports_total 8 Integer Total accepted latency-report service blocks for the tenant on this queue

The counter semantics for this record are:

  • requests_success: increments once per successful rate-limit request accepted for the tenant
  • requests_rate_limited: increments once per rate-limit request rejected for insufficient bucket capacity
  • requests_guard_failed: increments once per rate-limit request rejected by latency guards
  • requests_auth_failed: increments once per tenant request that fails authentication
  • service_latency_reports_total: increments once for each accepted latency-report service block for the tenant

The UDP administrative metrics protocol intentionally excludes:

  • global or cross-tenant counters
  • any metric that would require cross-thread aggregation
  • Prometheus-derived gauges that are not primary counter state, including:
    • ratelimitly_batch_size_avg
    • ratelimitly_batch_processing_time_avg_us
    • ratelimitly_batch_processing_rate_items_per_second

These values are explicitly out of scope for the binary administrative metrics protocol. If operators still want them emitted directly by the server, they SHOULD be moved to informational or debug logging rather than encoded as protocol metrics.

Tenant Label Counter record (family 0x02)

Field name Size (bytes) Type Description
requests_success 8 Integer Label-scoped success count
requests_rate_limited 8 Integer Label-scoped rate-limited count
requests_guard_failed 8 Integer Label-scoped guard-failed count
requests_auth_failed 8 Integer Label-scoped auth-failed count
label_len 2 Integer Label length in bytes
padding 2 Integer Padding to 4-byte alignment
label Variable Binary UTF-8 label bytes
label_padding Variable Binary Zero padding to 4-byte alignment

This record maps to:

  • ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="success"}
  • ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="rate_limited"}
  • ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="guard_failed"}
  • ratelimitly_tenant_label_requests_total{tenant_id=<request tenant>,label=...,result="auth_failed"}

Tenant Service Latency Counter record (family 0x03)

Field name Size (bytes) Type Description
latency_tracker_id 16 Binary Canonical content-defined latency-tracker identifier
ttl_ms 4 Integer Time-to-live for latency samples in milliseconds
max_samples 4 Integer Maximum number of samples to keep for latency tracking
buffer_size 4 Integer Circular buffer size for latency tracking
min_sample_threshold 4 Integer Minimum insertion rate required for reliable minimum
report_count 8 Integer Number of latency report blocks received for this service spec
checks_total 8 Integer Number of latency guard evaluations performed for this service spec
checks_passed 8 Integer Number of latency guard evaluations that passed for this service spec
checks_failed 8 Integer Number of latency guard evaluations that failed for this service spec

For this record family, latency_tracker_id is the uniqueness key. The remaining tracker-definition fields are repeated as validated metadata for diagnostics and interpretation.

Administrative metrics implementations MAY render the ID and definition for humans as:

  • <latency_tracker_id_hex>:<ttl_ms>:<max_samples>:<buffer_size>:<min_sample_threshold>

The counter semantics for this record are:

  • report_count: increments once for each latency-report block accepted for the service spec
  • checks_total: increments once for each guard evaluation against the service spec in a rate-limit request
  • checks_passed: increments when that guard evaluation does not reject the guard
  • checks_failed: increments when current_latency >= latency_threshold and latency_threshold > 0

Metrics PDU Examples

The following examples show plaintext MQ and MR PDUs before AES-GCM encryption and before wrapping with the packet-level Tenant Header and Auth TLV. In a real administrative packet:

  • the request Tenant Header key_id is the target tenant_id
  • the response Tenant Header key_id is overwritten with the responding server_id
  • the full packet is carried on the administrative AES-authenticated channel

All integers below are little-endian.

Example 1: TENANT_SUMMARY current snapshot

Request meaning:

  • target tenant: 42
  • ask for the current summary counters for that tenant on the addressed queue

Plaintext MQ bytes:

4d 51 08 00  01 00  00 00
^-----^----  ^--^   ^---^
 type  size  fam/k  reserved

Field decode:

  • pdu_type = 0x514D (MQ)
  • pdu_size = 8
  • metric_family = 0x01 (TENANT_SUMMARY)
  • query_kind = 0x00 (SUMMARY_CURRENT)
  • reserved = 0x0000

Response meaning:

  • success = 100
  • rate_limited = 3
  • guard_failed = 1
  • auth_failed = 0
  • service_latency_reports_total = 9

Plaintext MR bytes:

4d 52 30 00  01 00  00 00
64 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
09 00 00 00 00 00 00 00

Field decode:

  • pdu_type = 0x524D (MR)
  • pdu_size = 48
  • metric_family = 0x01 (TENANT_SUMMARY)
  • page_kind = 0x00 (SUMMARY_PAGE)
  • requests_success = 100
  • requests_rate_limited = 3
  • requests_guard_failed = 1
  • requests_auth_failed = 0
  • service_latency_reports_total = 9

Example 2: first page of TENANT_LABEL_COUNTERS

Request meaning:

  • target tenant: 42
  • ask for the first label page in server order

Plaintext MQ bytes:

4d 51 08 00  02 00  00 00

Field decode:

  • metric_family = 0x02 (TENANT_LABEL_COUNTERS)
  • query_kind = 0x00 (LABELS_FROM_START)

Response meaning:

  • this page is not the last page
  • the server provides an opaque cursor for the next page
  • it carries two records:
    • label "" with counters (8, 1, 0, 0)
    • label "api" with counters (50, 2, 1, 0)

Plaintext MR layout:

MR header:
  pdu_type      = 0x524D
  pdu_size      = 92
  metric_family = 0x02
  page_kind     = 0x00   ; LABELS_PAGE_MORE
  reserved      = 0x0000

Cursor prefix:
  next_cursor_len = 4
  reserved        = 0
  next_cursor     = de ad be ef

Record 1:
  requests_success      = 8
  requests_rate_limited = 1
  requests_guard_failed = 0
  requests_auth_failed  = 0
  label_len             = 0
  padding               = 0
  label                 = ""
  label_padding         = ""

Record 2:
  requests_success      = 50
  requests_rate_limited = 2
  requests_guard_failed = 1
  requests_auth_failed  = 0
  label_len             = 3
  padding               = 0
  label                 = "api"
  label_padding         = 00

Resume rule:

  • the client takes the opaque next_cursor from the page
  • the next request uses LABELS_FROM_CURSOR

Example 3: resume TENANT_LABEL_COUNTERS from opaque cursor

Plaintext MQ bytes:

4d 51 10 00  02 01  00 00  04 00 00 00  de ad be ef

Field decode:

  • pdu_type = 0x514D (MQ)
  • pdu_size = 16
  • metric_family = 0x02 (TENANT_LABEL_COUNTERS)
  • query_kind = 0x01 (LABELS_FROM_CURSOR)
  • cursor_len = 4
  • cursor = de ad be ef

Suppose the remaining label set begins with overflow and all remaining records fit in one datagram. Then the response uses LABELS_PAGE_LAST.

Plaintext MR layout:

MR header:
  pdu_type      = 0x524D
  pdu_size      = 52
  metric_family = 0x02
  page_kind     = 0x01   ; LABELS_PAGE_LAST
  reserved      = 0x0000

Record 1:
  requests_success      = 7
  requests_rate_limited = 4
  requests_guard_failed = 0
  requests_auth_failed  = 0
  label_len             = 8
  padding               = 0
  label                 = "overflow"

Example 4: empty TENANT_SERVICE_LATENCY_COUNTERS from start

Request meaning:

  • target tenant: 42
  • ask for the first service-counter page
  • the tenant exists, but there are no recorded service counters yet

Plaintext MQ bytes:

4d 51 08 00  03 00  00 00

Plaintext MR bytes:

4d 52 08 00  03 02  00 00

Field decode:

  • metric_family = 0x03 (TENANT_SERVICE_LATENCY_COUNTERS)
  • page_kind = 0x02 (SERVICES_EMPTY)