> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usepatchwork.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Direct integration

> The browser talks to Patchwork. Your backend mints a short-lived session token.

Direct is the default for a product UI. The browser never sees a workspace API key.

```
browser  ── mint (your user session) ──▶  your backend     signs with your private key
browser  ── Bearer <token> ────────────▶  Patchwork        verifies with your public key
Patchwork ── token + signature ────────▶  your tool        runs as the user
```

## 1. Create a signing keypair

Generate an RSA (or EC) keypair. The private key signs tokens and never leaves your servers. The public key is what Patchwork uses to verify — it is not a secret.

```bash theme={null}
openssl genrsa -out patchwork-private.pem 2048
openssl rsa -in patchwork-private.pem -pubout -out patchwork-public.pem
```

Allowed algorithms: `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`. Symmetric (`HS*`) is rejected.

## 2. Publish your public key

Serve the public key as a JWKS document at a stable URL. Patchwork fetches and caches it, matching each token to a key by `kid`. Rotation is adding a new key alongside the old one.

```json theme={null}
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "your-app-2026-01",
      "n": "0vx7ag…",
      "e": "AQAB"
    }
  ]
}
```

If you cannot host a JWKS URL, paste the public key in the dashboard instead.

In **Connections**, create a host (`name` + `base_url`) and set workspace ingress: JWKS URI, allowed origins, request secret, mint URL. Allowed origins are required so the browser can call Patchwork — no wildcards.

## 3. Mint a session token

Add one endpoint to your backend, authenticated by your existing user session. It returns a short-lived JWT signed with your private key.

<ParamField path="iss" type="string" required>
  Your API key public id (`key_…`), not the secret.
</ParamField>

<ParamField path="aud" type="string[]" required>
  Must include `patchwork`. Include your own API audience as well so the same token can pass your tool middleware.
</ParamField>

<ParamField path="sub" type="string" required>
  Your user id. Becomes the thread subject.
</ParamField>

<ParamField path="conn" type="uuid">
  Connection id this token may call. A UUID — not a name, not a URL. Required when the agent has unpinned customer tools.
</ParamField>

<ParamField path="exp" type="integer" required>
  Unix timestamp. Keep it short — about 120 seconds. The browser remints as needed.
</ParamField>

<ParamField path="kid" type="string" required>
  JWT header. Names the signing key so we match it in your JWKS.
</ParamField>

```js theme={null}
import jwt from "jsonwebtoken";

app.post("/patchwork/mint", requireUser, (req, res) => {
  const token = jwt.sign(
    {
      iss: PATCHWORK_API_KEY_ID,
      aud: ["patchwork", "your-api"],
      sub: req.user.id,
      conn: CONNECTION_ID,
    },
    PRIVATE_KEY,
    { algorithm: "RS256", expiresIn: 120, keyid: "your-app-2026-01" },
  );

  res.json({ token, expires_in: 120, subject_ref: req.user.id });
});
```

<Warning>
  Do not put a workspace secret (`sk_…`) in the browser. The session token is the only credential the client holds, and it expires in minutes. A token must authorize no more than the user could do themselves.
</Warning>

## 4. Call Loom from the browser

```js theme={null}
async function mint() {
  const res = await fetch("/patchwork/mint", { method: "POST" });
  const { data } = await res.json();
  return data.token;
}

const thread = await fetch("https://api.usepatchwork.co/v1/loom/threads", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${await mint()}`,
  },
  body: JSON.stringify({ agent: "agent_…" }),
}).then((r) => r.json());

const accepted = await fetch(
  `https://api.usepatchwork.co/v1/loom/threads/${thread.data.id}/messages`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${await mint()}`,
    },
    body: JSON.stringify({ content: "Send 50 USDC to Jane" }),
  },
).then((r) => r.json());
```

The message response is `202` with a `run_id`. Poll `GET /v1/loom/runs/:id` or subscribe to the thread channel — see [Realtime](/api-reference/realtime).

## 5. Verify tool calls you host

Every call we make to your backend carries the session token **and** a `Patchwork-Signature` over your request secret. A stolen token is useless without the secret.

```js theme={null}
import crypto from "crypto";
import jwt from "jsonwebtoken";

function verifyPatchwork(req, res, next) {
  const header = req.get("Patchwork-Signature");
  if (!header) return res.status(401).end();

  const { t, v1 } = Object.fromEntries(
    header.split(",").map((pair) => pair.split("=")),
  );
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end();

  const digest = crypto.createHash("sha256").update(req.rawBody ?? "").digest("base64");
  const payload = `${t}.${req.method}.${req.path}.${digest}`;
  const expected = crypto
    .createHmac("sha256", REQUEST_SECRET)
    .update(payload)
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) {
    return res.status(401).end();
  }

  const claims = jwt.verify(bearer(req), PUBLIC_KEY, {
    algorithms: ["RS256"],
    audience: "your-api",
  });

  req.user = { id: claims.sub };
  next();
}
```

Verify the signature, verify the token, then hydrate your normal request context. Your existing authorization runs unchanged.

## React

[`@usepatchwork/react`](https://www.npmjs.com/package/@usepatchwork/react) wraps the browser side. You provide `url` and a `mint` function that returns the session token. Optional `connection` pins the host.

```tsx theme={null}
<PatchworkProvider
  url="https://api.usepatchwork.co"
  connection={CONNECTION_ID}
  mint={async () => {
    const res = await fetch("/patchwork/mint", { method: "POST" });
    const { data } = await res.json();
    return data.token;
  }}
>
  <Chat />
</PatchworkProvider>
```

`useAgent` gives you `messages`, `threads`, `send`, `openThread`, and `newChat`. See the [package readme](https://github.com/chromablue-labs/crossbar/tree/main/packages/react).
