Skip to main content
Version: 5.x

Discovery

A v5 client does not hardcode relay limits. Its first call to a relay origin is the auth request, and the answer carries a discovery document that lists what the matched domain accepts and enforces.

GET /api/mte-relay?frameVersions=2

The relay negotiates the frame version, 2 in this release, before any Kyber work, then issues a token and the document. A client that already holds a token adds clientId=<token> to refresh it; a valid token keeps the same client id, and an invalid or aged one gets a new id. Every refusal is listed under Check order and refusals.

The response is JSON:

{
"clientId": "<54 character token>",
"relay": {
"discoverySchema": 1,
"buildVersion": "5.0.5",
"frameVersions": [2],
"features": ["http", "keepalive", "plain-headers", "browser-cookies", "pass-through", "relay-error-header"],
"mteProfile": "mte/...;kyber=1024;probe=...",
"kyberStrength": 1024,
"sequenceWindow": -63,
"timeWindow": 1000,
"maxFrameBytes": 65536,
"maxMessageBytes": 65536,
"maxMetadataBytes": { "MKE": 61439, "MTE": 7168 },
"maxPairsPerBatch": 100,
"maxPairsPerClient": 200,
"maxPairBytes": 1073741824,
"maxPairSeconds": 86400,
"maxRequestSeconds": 30,
"maxResponseSeconds": 0,
"idleTimeoutSeconds": 90,
"pingIntervalSeconds": 25,
"drainSeconds": 30,
"sessionTimeoutSeconds": 7200,
"tokenMaxAgeSeconds": 86400,
"transports": ["http"],
"stateMode": "shared",
"routeHeader": "X-MTE-Relay-Route",
"maxRequestBodyBytes": 0,
"forwardBrowserCookies": true
}
}

The values above are the defaults. The auth endpoint needs no credentials, so everything in the document is public. It never contains upstreams, secrets, custom headers, CORS settings, Redis settings, or host names.

Fields​

FieldSourceWhat it tells a client
discoverySchemafixedVersion of the document layout. 1 in this release.
buildVersionimage version fileThe relay build that answered, for support and logs. Omitted when the image has no version file, never 0.0.0. Clients key behavior on features, never on this string.
frameVersionsfixedFrame versions the relay accepts: [2].
featuresfixedRegistry feature names this build offers. The HTTP relay offers http, keepalive, plain-headers, browser-cookies, pass-through, and relay-error-header.
mteProfileMTE libraryThe MTE library build: version, token size, DRBG, cipher, hash, verifiers, Kyber strength, and a probe digest. A client compares it with its own and refuses to pair on a settings mismatch, and the pairing request repeats it so the relay can refuse with 490 profile_mismatch.
configVersioncontrol planeThe applied control plane configuration version. Absent on a relay configured only from its environment.
kyberStrengthKYBER_STRENGTHKyber strength both ends must use.
sequenceWindow, timeWindowSEQUENCE_WINDOW, TIME_WINDOWPairing inputs. Both ends build their decoders with these values, so a mismatch means no pair at all.
maxFrameBytes, maxMessageBytesMAX_FRAME_BYTESLargest frame payload. Clients size their DATA frames from it.
maxMetadataBytesMAX_METADATA_BYTES_MKE, MAX_METADATA_BYTES_MTEBound on the request metadata JSON per encode type.
maxPairsPerBatch, maxPairsPerClientMAX_PAIRS_PER_BATCH, MAX_PAIRS_PER_CLIENTPairing limits.
maxPairBytes, maxPairSecondsMAX_PAIR_BYTES, MAX_PAIR_SECONDSRekey thresholds for connection transports.
maxRequestSeconds, maxResponseSeconds, idleTimeoutSeconds, pingIntervalSecondsthe timers, or the domain's own request_seconds, response_seconds, idle_seconds, ping_secondsDeadlines the relay applies.
drainSecondsDRAIN_SECONDSDrain window on connections.
sessionTimeoutSecondsSESSION_TIMEOUT_MINUTES times 60Idle timeout for pairs. Clients schedule keepalive from it.
tokenMaxAgeSecondsTOKEN_MAX_AGE_SECONDSToken lifetime.
transportsfixed["http"] for the HTTP relay.
stateModefixedshared, always: the relay keeps pair state in its state store rather than on a connection. SocketX sends owner.
routeHeaderfixedX-MTE-Relay-Route, the routing header clients send.
maxRequestBodyBytesMAX_REQUEST_BODY_MIBInclusive plaintext body cap in bytes. 0 means unlimited. Clients refuse a larger body before sending it.
passThroughRoutesthe domain's pass_through_routesPaths a client sends as plain requests. Omitted when empty.
forwardPlainHeadersthe domain's forward_plain_headersPlain headers the relay forwards upstream, lowercased. Omitted when empty.
forwardBrowserCookiesthe domain's forward_browser_cookiesWhether browser cookies reach the upstream.

Values that come from live settings, such as maxRequestBodyBytes and the domain fields, are read on every auth, so a control plane change shows up on the next auth without a restart.

Check order and refusals​

The relay checks a discovery request in a fixed order, the same order SocketX uses on its discovery endpoint, so both products refuse the same request with the same code and the same message:

  1. A CORS preflight (OPTIONS) on a Host that DOMAIN_MAP matches answers 204, with CORS headers only for an allowed Origin, unless the domain sets cors_preflight_passthrough.
  2. Any method other than GET or HEAD answers 405. The method is checked before the Host, so a POST or an OPTIONS to an unknown Host gets 405, not 480.
  3. The Host is resolved. A malformed Host answers 490 invalid_request, and a Host that no DOMAIN_MAP entry matches answers 480 no_domain_for_host, both as relay errors: an ERROR frame body and the X-MTE-Relay-Error header. Cloudflare, Application Gateway, and Front Door replace plain 4xx bodies with their own pages; an ERROR frame with a relay status survives them.
  4. The per source rate limit. Discovery is limited to 600 requests per minute per source with a burst of 600. Over the budget the relay answers 488 rate_limited with Retry-After and the message too many requests; back off with jitter. RATE_LIMITS=false turns every token bucket limiter off. Behind a load balancer set TRUSTED_PROXIES, or every client shares one budget. See Limits and scaling.
  5. Version negotiation. The relay intersects frameVersions with the frame versions it accepts, [2] in this release, before any Kyber work. An empty intersection answers 478 frame_version_unsupported with the message server accepts [2], client offered [..]; minimum SDK versions: android 5.0.0, ios 5.0.0, browser 5.0.0, go relay 5.0.0, where [..] is the list the client sent. A request with no frameVersions at all gets no frameVersions offered (a pre version 2 SDK); server accepts [2]; minimum SDK versions: android 5.0.0, ios 5.0.0, browser 5.0.0, go relay 5.0.0, and the HEAD probe a version 4 SDK sends gets the same, as the status and the header only. A frameVersions value that is not a comma separated list of integers from 1 to 255 answers 490 invalid_request.
  6. The token and the document. A valid clientId token keeps its client id. An invalid or aged token gets a new client id and raises the auth_invalid_token security event at severity low; a valid or absent token raises nothing. The mint is logged at debug.

An Origin is never refused. A disallowed Origin gets the document without CORS headers, so a browser cannot read it; curl and native clients are unaffected. The origin rules are the same on the relay and on SocketX.

Configuration version header​

When a worker runs under the control plane, every relay response carries X-MTE-Relay-Config-Version, the applied configuration version. The protocol registry says a client that sees a higher value than the configVersion it learned at auth should read discovery again. Browser engine 5.0.0 and the outbound relay client do not act on the header yet. The browser engine keeps the bounds it adopted at its first auth until the client is created again, for example on the next page load. It raises one discovery-drift warning when a later auth reports a different sequenceWindow, timeWindow, maxFrameBytes, or maxMetadataBytes. A changed maxRequestBodyBytes raises no warning; the engine keeps enforcing the value from its first auth. The header is absent on a relay configured only from its environment.

What the SDKs do with it​

The mte-relay-browser engine (5.0.0) reads discovery on the first request to each relay origin and:

  • throws MteRelaySetupError when the relay does not accept frame version 2, when the mteProfile settings differ, or when kyberStrength differs from its own;
  • uses the relay's sequenceWindow and timeWindow, and warns once when you configured a different sequenceWindow;
  • clamps its pool to maxPairsPerClient;
  • schedules keepalive at one third of sessionTimeoutSeconds, between 1 and 10 minutes, unless you set keepAliveIntervalMs;
  • throws MteRelayRequestTooLargeError for a body larger than maxRequestBodyBytes before sending it;
  • sends requests that match passThroughRoutes as plain fetch calls;
  • warns once for each unencryptedHeaders name the relay will not forward.

See the JavaScript client for the options involved.