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

# Sign in as a person

> Sign a person in through their browser, get a personal access token, and authenticate SDK calls with it.

export const waitlists = {
  earlyAccess: "",
  payments: "",
  openBanking: "",
  developers: "",
  verifiers: ""
};

<Note>
  **Private preview:** this is rolling out and may not be available on your account yet.{waitlists.earlyAccess ? <> <a href={waitlists.earlyAccess}>Request early access →</a></> : null}
</Note>

<Warning>
  **First-party only today.** Personal access tokens are issued to Sumvin's own apps and approved clients. They are not yet open to third-party apps, so an outside developer can't complete this flow. To give your own agent access to Sumvin, use the [MCP connector](/get-started/connect) or the [CLI](/get-started/cli/install).
</Warning>

Acting for a person takes one credential: a **personal access token**. The person approves it in their own browser; your code never sees their password.

<Steps>
  <Step title="Start the browser sign-in">
    `deviceLogin` needs an unauthenticated client. It calls `onUserCode` once, with the address and short code to show the person, then waits for them to approve.

    ```ts theme={null}
    import { createSumvinClient, deviceLogin } from "@sumvin/sdk";

    const signInClient = createSumvinClient({ baseUrl: "https://api.sumvin.com" });

    const credential = await deviceLogin({
      client: signInClient,
      onUserCode: ({ verificationUri, userCode }) => {
        console.log(`Open ${verificationUri} and enter ${userCode}`);
      },
    });
    ```

    Show the address and code before you do anything else, such as opening a browser, so a person on a remote terminal can still sign in.
  </Step>

  <Step title="Store the token">
    `deviceLogin` returns the token and never stores it. Keep `credential.token` wherever you keep secrets.
  </Step>

  <Step title="Authenticate with it">
    ```ts theme={null}
    import { createSumvinClient, sumvinPat } from "@sumvin/sdk";

    const client = createSumvinClient({
      baseUrl: "https://api.sumvin.com",
      auth: [sumvinPat(() => readStoredToken())],
    });
    ```

    `sumvinPat` takes the token, or a function that returns it.

    In production, personal access tokens only work from approved Sumvin clients, identified by their user-agent. A request that carries the token from any other client, or with no user-agent, is refused with `403` and code `CLI-403-001`.
  </Step>
</Steps>

## When sign-in fails

Each failure is its own error class, and all of them extend `DeviceLoginError`.

| Error                      | Means                                                                        |
| -------------------------- | ---------------------------------------------------------------------------- |
| `DeviceLoginExpiredError`  | The sign-in request expired, was declined, or was already used. Start again. |
| `DeviceLoginNotFoundError` | The API doesn't recognise this sign-in request.                              |
| `DeviceLoginConflictError` | Another process already finished this sign-in.                               |
| `DeviceLoginTimeoutError`  | Nobody approved it before it expired.                                        |

To stop waiting, pass an `AbortSignal` as `signal`.

## Carrying a Stamped Mandate

An agent that holds a [Stamped Mandate](/concepts/stamped-mandates) token sends it with `pintToken`. It always travels **alongside** the personal access token, never instead of it: a request with only a mandate token is refused.

```ts theme={null}
import { createSumvinClient, pintToken, sumvinPat } from "@sumvin/sdk";

const client = createSumvinClient({
  baseUrl: "https://api.sumvin.com",
  auth: [sumvinPat(() => readStoredToken()), pintToken(() => currentMandateToken())],
});
```

A mandate token only ever narrows what the request may do. It never grants more than the person's own access.
