MTE Relay Client for Python using Requests
Current SDK release: 5.0.0.
Introduction
The MTE Relay Python client wraps requests. Your application calls client.get(), client.post(), or another familiar method and receives a requests.Response. The client encodes the request, sends it through MTE Relay, and decodes the response.
Your app --> MteRelayClient --> MTE Relay --> Upstream API
(plain) (MTE frames) (decodes) (plain HTTP)
This client speaks frame version 2, used by MTE Relay 5.
Prerequisites
- Python 3.8 or later
- A running MTE Relay server that supports frame version 2
- An MTE license (company name and license key)
- The Python MTE SDK files and matching native client library for your platform
Installation
Use the client source checkout for release 5.0.0. The wrapper's runtime Python dependency is requests; install its dependencies with:
python -m pip install -r requirements.txt
Keep the supplied mte/ SDK directory alongside mte_relay_requests/. The native library is mte.dll on Windows, libmte.so on Linux, or libmte.dylib on macOS. The client looks in the SDK directory and its platform library subdirectory. Use a client MTE library build with the relay's server build; incompatible builds fail to decode from the first exchange.
Quick Start
from mte_relay_requests import MteRelayClient, MteRelayConfig
config = MteRelayConfig(
company_name="Your Company",
license_key="your-license-key",
)
with MteRelayClient(config) as client:
response = client.get("https://relay.example.com/api/users")
print(response.status_code, response.json())
response = client.post(
"https://relay.example.com/api/users",
json={"name": "Alice"},
)
print(response.status_code)
The first request to a relay origin authenticates and creates encoder/decoder pairs. To do that work before the first application request, call client.init("https://relay.example.com"). Call client.close() when finished if you do not use a context manager.
Usage
Requests Interface
The client supports get, post, put, patch, delete, head, options, trace, and connect. Each delegates to client.request(method, url, **kwargs). It also supports prepare_request(request) and send(prepared_request, **kwargs) for two-step flows.
response = client.request(
"POST",
"https://relay.example.com/api/login",
json={"email": "john@example.com", "password": "example-password"},
params={"redirect": "false"},
headers={"Authorization": "Bearer example-token"},
timeout=10,
)
request() accepts familiar requests options including headers, data, json, params, cookies, files, auth, timeout, allow_redirects, proxies, verify, cert, stream, and hooks. Requests prepares the URL, headers, and body before the client encodes them. Unsupported keyword arguments are ignored with a warning. Application headers, including Authorization, travel inside encrypted request metadata; the outer relay request has its own transport headers.
Route Through a Separate Relay Origin
By default, a request is sent to a relay at the request URL's origin. Use origin_map when the upstream API and relay have different origins:
config = MteRelayConfig(
company_name="Your Company",
license_key="your-license-key",
origin_map={
"https://api.example.com": "https://relay.example.com",
},
)
Each relay origin has its own client token and pair pool. An unmapped origin uses its own origin as the relay.
Stream a Response
With stream=True, the client returns after verifying the RESPONSE frame. iter_content() or iter_lines() then yields plaintext as each DATA frame is decoded. Large response bodies and server-sent events can be consumed incrementally.
with client.get("https://relay.example.com/api/events", stream=True) as response:
for line in response.iter_lines():
if line:
print(line)
A streamed response holds its pair until the body reaches its END frame or the response is closed. If you close it early, the client retires that pair and replenishes the pool. Close responses you do not fully consume.
Configuration
MteRelayConfig accepts these parameters:
| Parameter | Default | Description |
|---|---|---|
company_name, license_key | "" | MTE license credentials |
min_pairs | 7 | Minimum pairs maintained per relay origin |
base_pairs | derived | Initial pairs; defaults to min(min_pairs * 2, max_pairs) |
max_pairs | 25 | Maximum pairs per relay origin |
refill_pairs | 0 | Pairs added in a refill cycle; 0 selects an automatic amount |
use_mke | True | Use MKE instead of standard MTE |
kyber_strength | 1024 | Local MTE profile strength; must match relay discovery; accepts 512, 768, or 1024 |
sequence_window | -63 | Fallback decoder sequence window before discovery |
time_window | 1000 | Fallback decoder time window in milliseconds before discovery |
http_timeout | 30.0 | Default HTTP timeout in seconds |
pair_wait_seconds | None | Wait for a free pair; None uses http_timeout, 0 fails immediately |
keepalive_enabled | True | Run background session keepalive |
keepalive_interval_seconds | 600.0 | Keepalive interval in seconds; 0 derives the cadence from discovery |
origin_map | None | Request-origin to relay-origin mapping |
num_pairs remains accepted as a compatibility alias for min_pairs when min_pairs is left at its default. After authentication, the client uses the relay's advertised MTE windows, Kyber strength, frame and metadata limits, and per-batch pair limit. The local window settings are fallbacks. The local MTE profile, including Kyber strength, must match the relay's advertised profile.
When all pairs are busy, the client waits up to pair_wait_seconds while the pool grows toward max_pairs. If no pair becomes available, it raises CapacityExhaustedError. This is local capacity pressure; retry when capacity is available rather than repairing a pair.
How It Works
Frame Protocol
- Discover and authenticate:
GET /api/mte-relay?frameVersions=2returns a signed client token and the relay's discovery settings. The client checks the advertised frame version and MTE profile. - Pair:
POST /api/mte-pairestablishes encoder/decoder pairs with Kyber public keys, personalization strings, the MTE profile, and frame version. - Exchange data: Each application request becomes
POST /withContent-Type: application/octet-stream. Its body contains one REQUEST frame followed by zero or more DATA frames. The reply contains one RESPONSE frame followed by zero or more DATA frames.
Frame version 2 uses a 12-byte envelope containing a signature, version, frame kind, flags, reserved bytes, and payload length. REQUEST and RESPONSE carry encrypted metadata. A DATA frame carries one encoded body chunk; its END flag is bound into the encoded plaintext. The last frame in each direction marks END, including a bodiless REQUEST or RESPONSE. Large bodies are split into DATA frames to respect the relay's advertised frame limit.
An unframed relay response is a transport error, regardless of its HTTP status.
Sessions and Keepalive
The client checks out one pair for the full request and response exchange. Other requests use other available pairs. This prevents concurrent requests from advancing the same encoder or decoder state.
When keepalive is enabled, the background loop sends one POST /api/mte-keepalive per ready relay session. The server refreshes all of that client's pairs in that call; no MTE codec state is advanced. The call can also carry IDs of pairs the client has retired. Failed keepalives are logged and the drop list is retried on the next cycle. The client refreshes its token before the relay's advertised token age expires, retaining the pool when the client ID stays the same.
If a pair expires, the next request may receive 470 pair_not_found. The client repairs that pair and returns the error response so the caller can decide whether to repeat the application request.
Error Handling
Relay-generated failures use an ERROR frame and repeat the code and reason in X-MTE-Relay-Error. The client uses the code and reason to determine its action. It never transparently repeats an application request.
| Code | Typical reason | Client action |
|---|---|---|
| 470–474 | Pair missing or encoder/decoder state failure | Replace the affected pair |
| 475 | invalid_client_id, invalid_token | Re-authenticate and rebuild the pool |
| 475 | unknown_key | Stop; the key was revoked |
| 476, 485, 488 | Pool, upstream capacity, connection, or rate limit | Back off |
| 477 | invalid_frame / eof_before_end | Surface an invalid frame; replace the pair after an incomplete request body |
| 478 | frame_version_unsupported | Surface the incompatibility |
| 479, 483 | Replay or header mismatch | Surface a security error |
| 480, 482 | Routing or metadata error | Surface the error |
| 481 | Frame or body too large | Surface; the HTTP pair remains usable |
| 484 | Target policy rejection | Surface or stop, depending on the reason |
| 486 | pair_busy / not_owner | Report retry action / back off |
| 487, 489 | Rekey overdue / target reset | Report reconnect action / surface |
| 490 | Invalid request, pair, or MTE profile | Surface the setup error |
| 500, 502 | Relay or upstream failure | Surface the error |
| 503 | State store unavailable / write failed | Report retry action / replace the affected pair |
For a relay error response, inspect response.mte_relay_error (a RelayError when available), response.mte_relay_action (the registry action), and response.mte_relay_repair (what this call actually did). The action and repair outcome are also exposed in x-mte-relay-action and x-mte-relay-repair response headers. Repair outcomes include pair-replaced, pair-already-repaired, pair-replace-failed, full-repair-success, full-repair-failed, backoff, and none. A successful repair makes a later retry possible; it does not mean the original application request was retried.
If a failure occurs after a streamed response's headers have been returned, reading its body raises ResponseBodyError, a requests.exceptions.ChunkedEncodingError subclass. Its relay_error and action attributes provide relay details when available. For a buffered response, an ERROR frame that interrupts the body is returned as a relay error response.
Logging
The library uses the mte_relay_requests Python logger:
import logging
logging.getLogger("mte_relay_requests").setLevel(logging.INFO)
Authentication, pairing, repair, and routine early stream closure are logged at INFO; relay and keepalive failures are logged at WARNING or ERROR. Enable DEBUG when diagnosing frame or pair selection behavior.
Support
Contact Eclypses for the MTE SDK, license, and client distribution. See the requests documentation for the underlying HTTP interface.