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

> ## Agent Instructions
> Every Lath operation is POST https://platform.trylath.com/<operation name with dots replaced by slashes>, with a JSON body and `Authorization: Bearer <key>`. `email.send` is POST /email/send.
> Branch on `error.code`, never on `error.message`. Every refusal also carries `error.fix`, which names the next step.
> Send an `Idempotency-Key` header on any operation that is not retry-safe, so a retry cannot run it twice.
> A `lath_test_` key emails only the account's own members and sends no SMS; a `lath_live_` key reaches real recipients and is billed.
> The OpenAPI document, generated from the same registry as the routes, is at https://platform.trylath.com/openapi.json.

# Sessions and tokens

> An access token to send with a request, a refresh token to get the next one, and a deliberate trap: presenting a refresh token twice ends every session it belongs to.

## What a sign-in returns

`auth.signin.verify` issues a short-lived access token and a rotating refresh token. The defaults are fifteen minutes and thirty days.

Both are the environment's to set with `auth.settings.set`: an access token between five minutes and an hour, a refresh token between an hour and a year. Shorter access tokens mean more refreshes, not more sign-ins.

## Refreshing, and the reuse rule

`auth.session.refresh` takes a refresh token and returns a new access token and a new refresh token. The one you presented is spent.

Present a spent one again and every session in its family is revoked — not just that token. A refresh token that is used twice has usually been copied, and the safe reading of a copy is that somebody else has it. Store the newest one and never retry a refresh with an old value.

It runs with a publishable key, because the token in the request is the credential.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://platform.trylath.com/auth/session/refresh \
    -H "Authorization: Bearer lath_pk_..." \
    -H "Content-Type: application/json" \
    -d '{"refreshToken":"..."}'
  ```

  ```ts TypeScript theme={null}
  import { createLath } from "@trylath/sdk";

  const lath = createLath({ key: process.env.LATH_PUBLISHABLE_KEY });

  const { result } = await lath.auth.session.refresh({ refreshToken: "..." });
  ```

  ```bash CLI theme={null}
  lath auth session refresh --refreshToken ...
  ```
</CodeGroup>

## The hosted handoff

A sign-in finished on Lath's hosted pages does not hand tokens to the browser. `auth.signin.verify` with `handoff: true` answers with the `redirectTo` the sign-in started with, carrying a one-time `lath_code`.

Your app exchanges that with `auth.session.exchange` for the real tokens. The code works once and expires two minutes after it was issued, so it is useless in a browser history, a server log or a referrer header a minute later.

## Checking a token on your own API

`verifyAccessToken` in `@trylath/sdk` validates a token against your environment's published keys, fetched from `/auth/jwks/{environmentId}` and cached — so your backend does not call Lath on every request. `requireUser` does the same for a standard `Request` and throws a 401 response when there is no valid token.

```ts theme={null}
import { requireUser } from "@trylath/sdk";

const user = await requireUser(req, { environmentId: process.env.LATH_ENV_ID! });
```

## Ending a session

The user's own, with a publishable key: `auth.session.signout` ends the session whose refresh token is presented, along with every refresh it has rotated into. `auth.session.mine` lists their live sessions and marks the one asking; `auth.session.revokeMine` ends one of them by family id, or every one except the one asking. In each case the token in the request is the proof, and there is no way to name somebody else's session.

Yours, from a server: `auth.session.list` shows a user's sessions with the device and network facts, and `auth.session.revoke` ends one session or every live session a user has.

A revoked refresh token stops working immediately. An access token already issued keeps working until it expires, which is what the access lifetime is for — set it shorter if that window matters to you.
