Relex SDK
Build a legal platform for your law firm or legal department, with portable matter
context and a client-side confidentiality boundary. Your implementation determines
where plaintext exists; preserve that boundary when using native bindings.
Anything a platform needs to do can be done over the Relex REST API, with one
exception: creating a party. Party identities are sealed in the client, under a
password the server never receives, so the sealing has to happen wherever your
users are. The SDK is that sealing scheme, packaged, plus a typed client for the
rest of the API.
A party your platform seals opens in Relex, and one sealed in Relex opens in
your platform. That is enforced by golden test vectors rather than by
convention, so any client that passes them interoperates.
Availability. The library is currently described as private in its source
release instructions. Public package installation is not yet verified; contact
Relex for access rather than assuming an unauthenticated install will work.
Rust/native and WebAssembly share the core implementation.
Node.js, Python and Java bindings exist in the SDK source repository over the same
C ABI. Source availability is distinct from a published package: check access and
release instructions before choosing an installation method. The platform example
uses a mock backend; it is an integration example, not a production deployment.
The ciphertext format is versioned and tested across bindings.
One matter context across your product and connected agents
Your application works with the same Relex cases, knowledge, ontology and saved
work exposed through the REST API and hosted MCP endpoint. Users can authorize a
compatible assistant to continue from that context without rebuilding it in each
chat. The SDK does not import private chat histories from other providers. Follow
the connector guides and portable context guide.
Two credentials, and they are unrelated
This is the part worth reading twice, because it is what makes the rest safe.
An API key authenticates transport. A PII password derives the sealing
key, in the client. The API key can read ciphertext. It can never derive the key
that opens it.
That separation is not a policy or a promise about how we behave. It is a
consequence of the construction: the server stores only ciphertext and has no
key material capable of decrypting it.
// Transport auth: the account's Relex API key.
let client = Client::builder(NativeTransport::new())
.api_key(api_key)
.build()?;
// Key material, derived in the client from the user's PII password.
// The password itself is never sent anywhere.
let vault = Vault::provision(pii_password)?;
client.crypto().put_user(vault.material()).await?;Integrate with what you already have
The client is transport-agnostic. It speaks to the Relex API over whatever HTTP
stack you already run, including your own instrumentation, proxies and retry
policy, because the transport is an interface you can implement rather than a
bundled dependency you have to accept.
Available resources:
cases(): list, get, create, activate, closeparties(): create, get, list, update, delete, existence checks by blind
index, attach to and detach from a casecrypto(): publish and read the key material described abovecredits(): account balance and usage
Sealing needs only the public key, so writing a party never prompts anyone for a
password. Opening one does.
let bundle = PartyBundle::individual("Mary-Anne", "Wilson")
.identifier("passport", "123456789")
.email("[email protected]");
let party_id = client.parties().create(&vault.seal_party(&bundle)?).await?;
// Opening requires the password. The secret key is unwrapped, used, dropped.
let reopened = vault.open_party(pii_password, &client.parties().get(&party_id).await?)?;Bring your own models
Point each role at your own endpoint. Roles exist because the trust
requirements genuinely differ between them, not to give you knobs:
- Redaction sees unredacted text, so it must be private. Your own endpoint
or a private model, and nothing else. There is no configuration in which this
role accepts a public model. Configure nothing and the SDK uses its
deterministic detectors instead of reaching for a model at all. - Light is cheap, high-volume work on already-redacted text: classification,
extraction, short summaries. - Reason is deliberate multi-step work on already-redacted text.
- Main is general orchestration, also on already-redacted text.
Any OpenAI-compatible endpoint works, which in practice means most local
serving stacks work without an adapter.
Anonymise private data
The redaction pipeline runs in the client, before anything is uploaded. It
combines structured detectors with name detection, emits stable placeholders of
the form [PARTY_NAME_1], keeps a reversible pseudonym map sealed under your
own key, and finishes with an egress scrub that catches residual identifiers.
It is held byte for byte against the server engine by a parity test, so the
client and the platform agree on what counts as personal data. Dates are
deliberately treated as context rather than personal data: stripping them
destroys legal meaning.
Re-identification happens at export, not during the work. The model reasons
over placeholders throughout.
Keep data local
Sealing happens on the user's machine. What reaches Relex is ciphertext plus
blind indexes, which are HMACs that let you ask "does a party with this
identifier already exist" without sending the identifier.
Concretely, the following never leave the client: the PII password, the derived
key encryption key, the unwrapped secret key, and plaintext party fields. The
backend rejects plaintext party fields outright rather than accepting and
encrypting them, so a bug in your platform cannot quietly downgrade the
guarantee.
The same matters, whichever agent you use
Relex exposes one MCP endpoint with a two-tool surface, and every agent
connects to that same endpoint: Claude, ChatGPT and Codex, Gemini, Grok,
Perplexity, and any generic MCP client such as Cursor or Windsurf.
Because the workspace holds the matters, the know-how and the sealed parties,
changing agent does not move any of it. Your users' work is not stored inside
whichever model they were using at the time. That applies to platforms you
build on the SDK too: you inherit the same endpoint rather than integrating each
agent yourself.
The v1 wire format
Sealed parties in production were written by this scheme, so these constants are
load-bearing. Changing any of them stops existing data opening.
| Key derivation | Argon2id v0x13, m = 19456 KiB, t = 3, p = 1, 32-byte output, 16-byte salt |
| Key wrapping | AES-256-GCM, nonce(12) ‖ ciphertext ‖ tag(16), base64 with padding |
| Sealed box | eph_pub(32) ‖ nonce(12) ‖ ciphertext ‖ tag(16), X25519 with HKDF-SHA256 |
| Blind index | HMAC-SHA256 over the whitespace-collapsed lowercased value, lowercase hex |
| Recovery key | 20 bytes, unpadded uppercase base32, dashed in blocks of 4 |
The vault also covers recovery keys and organisation key sharing, where the
organisation secret is sealed to each member's public key so granting access
never moves a secret through the granting user's machine in plaintext.
Source access and verification
The reference implementation is maintained at
relexlegal/relex-legal-library.
Confirm access before relying on source review: this URL returned 404 without
authentication when checked on 28 September 2026. A repository link is not proof
that its source and tests are publicly accessible.
With repository access, use the golden vectors under tests/vectors/ to check
wire-format interoperability. That does not by itself verify the deployed web
application or establish an independent security audit. The
privacy verification guide explains how to
inspect browser requests, processing modes and the delivered JavaScript/WASM
using synthetic data.