Skip to main content
Some models cannot be reached from the internet at all — a vLLM server on a private subnet, an internal inference gateway, a model running on a laptop. The Relay bridge red-teams those without opening a hole in your firewall. You run a small bridge process inside your own network. It holds one outbound WSS connection to Enkrypt on port 443. Enkrypt pushes OpenAI-shaped chat.completions requests down that socket, the bridge calls your internal LLM over your own intranet, and the response returns the same way. Nothing in Enkrypt’s cloud ever dials your model, and your firewall never has to accept an inbound connection.
target.metadata.relay, used throughout this page, is being rolled out. If a run returns 422 naming target.metadata.relay, your environment is on the previous release: send the relay block as target.relay instead, which stays supported and is described under Inline target. Everything else on this page is unchanged.
Substitute whichever host your Enkrypt contact gives you for the examples below.

Prerequisites

  • An Enkrypt AI API key from app.enkryptai.com
  • A machine inside your network that can reach the LLM and can make outbound HTTPS connections on port 443
  • The bridge itself, which ships in the Python SDK
Shell

Step 1 — Run the bridge

pip install enkryptai-sdk installs an enkryptai-relay console script. Run it on the machine inside your network. It needs an API key and a bridge id, and nothing else.
On success the bridge logs:
There is no user id to configure. Authentication is the API key alone. The gateway resolves the owning account from it and stamps that identity on both the bridge socket and the runs you submit — that is how a run finds the right bridge, and why two customers can both name a bridge my-laptop without colliding.

Step 2 — Confirm the bridge is connected

JSON
The answer is scoped to your own account, so connected: false covers “not running”, “never existed” and “registered by somebody else” alike rather than leaking which it was. This is also the only reachability check that works for a relay target — see Checking reachability below.

Step 3 — Point a red team run at the bridge

Routing through the bridge is a property of the target, not a separate endpoint. There are two ways to express it.

Inline target

POST /rt/redteam, with the relay block in the request body:
POST /rt/redteam
The relay-specific parts:
  • connect_via_relay: true is the only switch. There is no provider: "relay". It stays at the root of target, not inside metadata.
  • target.endpoint is kept for labelling and audit only. Nothing in Enkrypt’s cloud ever dials it, so it may be an address that only resolves inside your network.
  • target.api_key is "". The bridge authenticates to your internal LLM, not Enkrypt. Credentials for the internal LLM go in metadata.relay.target_headers, which the bridge forwards verbatim. Never put them here.
  • metadata.relay.target_endpoint is the URL the bridge actually POSTs to inside your network.
  • metadata.relay.model_name is the identifier your internal LLM expects. It need not match target.model_name, which is only the label the run is reported under.
  • metadata.relay.target_headers is optional. Omit the key entirely rather than sending an empty object.
The response is a run_id and status: "queued".
target.metadata accepts relay and nothing else. Any other key — metadata.tenant_id, metadata.aws_region, provider-specific metadata in general — is a 422. The /rt APIs cannot express provider-specific metadata at all, and accepting it silently would mean the run attacks a differently-configured target and reports success on it. Unknown keys inside relay are a 422 too.
target.relay — the bare spelling, without the metadata wrapper — is an older form and is still accepted, so existing integrations keep working. Prefer metadata.relay: it is the spelling used everywhere else in the chain, including what a saved model stores. Full field reference: target.metadata.relay.

Saved model

Store the relay configuration once and reuse it. POST /models/add-model:
POST /models/add-model
Then run it with POST /rt/model/redteam and no target in the body, naming the saved model in headers. The gateway resolves the stored model into the target for you and returns 202 with a run_id.
cURL
Give endpoint all four keysscheme, host, port and base_path. All four are required. Some providers have built-in defaults that paper over an omission; openai_compatible has none, so leaving any one of them out fails the save.
connect_via_relay and metadata.relay are a pair, and metadata.relay cannot share the metadata object with other provider metadata. A model that sets the flag without a relay block, a relay block without the flag, or a relay block alongside provider metadata is rejected with a 400 naming the model when a /rt/model/* endpoint tries to use it. That is deliberate: a half-configured relay would otherwise be dialled directly, at an address that only resolves inside your network.

Step 4 — Track the run and read the results

These are the same endpoints as any other run — nothing about reading a relay run is special.
cURL
See the Quickstart for the run lifecycle’s status values, what the summary and record shapes contain, and how to read an ASR.
The submission response carries status_url, results_url and websocket_url fields spelled with a /v1/... prefix. Those are internal upstream paths, and following them literally through the gateway does not work. Use the /rt/runs/... paths above. This is a known wart in the response, not a mistake on your side.

Step 5 — The SDK equivalent

RTModelConfig.via_relay() builds the whole relay target, emitting metadata.relay, so the three values you actually own are the three you pass:
Python SDK
  • endpoint is your LLM’s URL as the bridge sees it. It fills both target.endpoint (the label) and metadata.relay.target_endpoint (what the bridge POSTs to).
  • provider defaults to openai_compatible. It has no runtime effect under connect_via_relay — the relay wire is OpenAI-shaped end to end — but it survives as metadata.
  • remote_model_name= overrides only the name sent to your internal LLM, for when it differs from the label you want the run reported under.
  • Passing a non-empty api_key= raises, on purpose: a key there would be shipped to Enkrypt and then ignored, because the bridge is what authenticates to your LLM. Use target_headers instead.
Pass the target to run_redteam exactly as you would a hosted one; routing through the bridge is a property of the target, so no client method changes. RTModelConfig.hosted() is the symmetric constructor for a publicly reachable endpoint, which makes moving a run between a hosted and an internal model a one-line edit.

If the bridge drops mid-run

The run pauses; it does not fail. Calls made in that window are retried, the bridge reconnects on backoff, and no test case is lost. You can restart the bridge process, reboot the host, or lose the network for a while and pick the run up where it left off.

Checking reachability

POST /rt/model-health and POST /rt/model/model-health do not support relay targets. Both probes dial target.endpoint directly. On a relay target that field is only a placeholder for an address inside your own network, so the probe always reports unhealthy however healthy the bridge is.Use the bridge status endpoint from Step 2 instead: GET /redteam/relay/v1/relay/bridges/{bridge_id}/status.
Submitting the run itself is unaffected: the gateway knows not to health-probe a relay target on the way in.

Troubleshooting

Exact 422 strings for an inline target

An inline target is validated by the Red Team service. Every failure comes back as 422 Unprocessable Entity with a detail array; these are the strings verbatim, so you can match on them.
422
The Value error, prefix is real — it is part of the msg string the API returns, not documentation punctuation. The unprefixed sentence is repeated in ctx.error, which is the easier field to match on exactly.The two extra_forbidden rows are the same message for two different mistakes: a key other than relay under metadata (row 3), and an unrecognised key inside the relay block — timeout_s, offline_grace_s, a relay endpoint or token — which do not exist as settings at all (row 4). The loc array is what tells them apart, and its last element names your key.

Exact 400 strings for a saved model

A saved model is projected into a target by the gateway before the service sees it, so a relay misconfiguration there is a 400, not a 422, and it is raised when a /rt/model/* endpoint uses the model rather than when you save it.
400
Every message opens with the subject — Saved model '<your model name>', or The submitted target_model_configuration if you sent a legacy target block inline — and then reads: