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

# Custody models

> Choose where user accounts live, which SDK import to use, and how the backend auth routes work.

Choose where the user's Unlink account lives first. That choice decides which
SDK import you use.

## Choose a model

* **Non-custodial browser app:** import from `@unlink-xyz/sdk/browser`. The
  user's spending key is created in the browser and never leaves it.
* **Custodial server app:** import from `@unlink-xyz/sdk/client`. Your server
  holds the account and signs on the user's behalf.

Both models use `@unlink-xyz/sdk/admin` on your backend for registration,
authorization tokens, and backend reads. Keep the admin API key server-side.

<Tip>
  Use `@unlink-xyz/sdk/browser` in browser bundles. Keep `@unlink-xyz/sdk/admin`
  on your backend.
</Tip>

Pick a hosted deployment by `environment` name on the
[Supported chains](/supported-chains) page before wiring either model.

## Browser non-custodial

The user signs in the browser. Your backend only registers the user and issues
auth tokens.

* `POST /api/unlink/register`
* `POST /api/unlink/authorization-token`

```ts theme={null}
import { account, createUnlinkClient } from "@unlink-xyz/sdk/browser";

const { account: unlinkAccount } = await account.fromWallet({
  provider: window.ethereum,
  appId: "your-app-id",
  chainId: 84532,
});

const client = createUnlinkClient({
  environment: "base-sepolia",
  account: unlinkAccount,
});

await client.ensureRegistered();
```

The user's spending key stays in the browser.

## App backend

Mount these routes behind your normal app login. This is the canonical wiring for
the two routes the browser client calls.

```ts theme={null}
import {
  createUnlinkAdmin,
  createUnlinkAuthRoutes,
} from "@unlink-xyz/sdk/admin";

const admin = createUnlinkAdmin({
  environment: "base-sepolia",
  apiKey: process.env.UNLINK_API_KEY!,
});

const routes = createUnlinkAuthRoutes({
  admin,
  authenticate: async (request) => getAppSession(request),
  onRegister: async ({ session, registration }) => {
    await db.linkUnlinkAddress(session.userId, registration.address);
  },
  authorizeUnlinkAddress: async ({ session, unlinkAddress }) =>
    db.userOwnsUnlinkAddress(session.userId, unlinkAddress),
});

app.post("/api/unlink/register", (c) => routes.register(c.req.raw));
app.post("/api/unlink/authorization-token", (c) =>
  routes.authorizationToken(c.req.raw),
);
```

The two routes have distinct jobs. `register` links a newly derived Unlink
address to your authenticated app user. `authorization-token` issues the
short-lived tokens the client sends on later calls. Tokens are scoped by
`subject_type`: `unlink_address` authorizes account actions, and `user_storage`
authorizes the [encrypted storage](/accounts-and-keys#user-storage) namespace for
one app `userId`.

## Server or custodial

Use this model when your server is allowed to hold user accounts.

```ts theme={null}
import { createUnlinkAdmin } from "@unlink-xyz/sdk/admin";
import { account, createUnlinkClient } from "@unlink-xyz/sdk/client";

const admin = createUnlinkAdmin({
  environment: "base-sepolia",
  apiKey: process.env.UNLINK_API_KEY!,
});

const unlinkAccount = account.fromMnemonic({ mnemonic });
const unlinkAddress = await unlinkAccount.getAddress();

const client = createUnlinkClient({
  environment: "base-sepolia",
  account: unlinkAccount,
  register: (payload) => admin.users.register(payload),
  authorizationToken: {
    provider: () => admin.authorizationTokens.issue({ unlinkAddress }),
  },
});

await client.ensureRegistered();
```

The account passed to `createUnlinkClient` signs the user's private actions, and
the constructor you choose decides which operations it can perform. See
[Accounts and keys](/accounts-and-keys#account-constructors) for the full
constructor reference.

## Backend reads

Use admin reads for backend dashboards and support tooling, and the user client
for signed actions. See
[Reading data and status](/reading-data#backend-reads).
