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.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.
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
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 thetarget, 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
connect_via_relay: trueis the only switch. There is noprovider: "relay". It stays at the root oftarget, not insidemetadata.target.endpointis 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_keyis"". The bridge authenticates to your internal LLM, not Enkrypt. Credentials for the internal LLM go inmetadata.relay.target_headers, which the bridge forwards verbatim. Never put them here.metadata.relay.target_endpointis the URL the bridge actually POSTs to inside your network.metadata.relay.model_nameis the identifier your internal LLM expects. It need not matchtarget.model_name, which is only the label the run is reported under.metadata.relay.target_headersis optional. Omit the key entirely rather than sending an empty object.
run_id and status: "queued".
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
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
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
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
endpointis your LLM’s URL as the bridge sees it. It fills bothtarget.endpoint(the label) andmetadata.relay.target_endpoint(what the bridge POSTs to).providerdefaults toopenai_compatible. It has no runtime effect underconnect_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. Usetarget_headersinstead.
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
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 inlinetarget 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 a400, not a 422, and it is raised when a /rt/model/*
endpoint uses the model rather than when you save it.
400
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:
Related
- Configuration Reference — every
target.metadata.relayfield - Payload Examples — the relay payload alongside every other Red Team payload shape
- Relay Bridge Connect — the WSS socket and its headers
- Relay Bridge Status — the status endpoint
- Python SDK reference —
RelayBridge, the bridge translation hooks, and the rest of the bridge environment contract - Testing Models Across Providers — target configuration for publicly reachable models
- Quickstart — the full run cycle

