# Relex SDK Documentation - Relex

> Build your own legal platform on Relex: client-side PII sealing, client-side anonymisation, bring-your-own models, and a typed client for the Relex API.

#### On this page

- [Two credentials, and they are unrelated](#clause-2)
- [Integrate with what you already have](#clause-3)
- [Bring your own models](#clause-4)
- [Anonymise private data](#clause-5)
- [Keep data local](#clause-6)
- [The same matters, whichever agent you use](#clause-7)
- [The v1 wire format](#clause-8)
- [Source](#clause-9)

# Relex SDK

Build your own legal platform on Relex, without your clients' identities ever
leaving your users' machines.

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 SDK ships for Rust today, native and WebAssembly. Bindings
for Node, Python and Java are in progress over the same C ABI, so they expose
this same interface rather than a reimplementation of it. The wire format below
is frozen at v1 and will not change under you.

## 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.

```rust
// 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, close
- `parties()`: create, get, list, update, delete, existence checks by blind
index, attach to and detach from a case
- `crypto()`: publish and read the key material described above
- `credits()`: account balance and usage

Sealing needs only the public key, so writing a party never prompts anyone for a
password. Opening one does.

```rust
let bundle = PartyBundle::individual("Mary-Anne", "Wilson")
    .identifier("passport", "123456789")
    .email("mary.wilson@example.com");

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

The implementation is public and Apache-2.0:
[github.com/relexyou/relex-legal-library](https://github.com/relexyou/relex-legal-library).
The golden vectors under `tests/vectors/` are the interoperability contract; if
your own client passes them, it interoperates with Relex.

---

_[View this page on relex.legal](https://relex.legal/docs/legal-sdk)_
