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

# Next.js

> Send from a server action or a route handler, drop in the sign-in components, and protect an API route with one call. The secret key never reaches the browser.

<Steps>
  <Step title="Install">
    `@trylath/sdk` is the server half — it holds your secret key and calls operations. `@trylath/react` is the browser half and never sees a secret key.

    Two packages because they have opposite security properties, and one package that did both would make it easy to import the wrong one into a client component.

    ```bash theme={null}
    npm i @trylath/sdk @trylath/react
    ```
  </Step>

  <Step title="Send from the server">
    A route handler or a server action is where the secret key lives. Keep it in `LATH_API_KEY` and never in anything prefixed `NEXT_PUBLIC_` — that prefix is what decides whether a value is compiled into the client bundle.

    ```ts theme={null}
    // app/api/welcome/route.ts
    import { createLath } from "@trylath/sdk";

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

    export async function POST(req: Request) {
      const { email } = await req.json();
      const { result } = await lath.email.send({
        to: email,
        subject: "Welcome",
        text: "Glad you are here.",
      });
      return Response.json({ messageId: result.messageId, status: result.status });
    }
    ```
  </Step>

  <Step title="Sign-in, in the browser">
    `LathProvider` takes the **publishable** key and the environment id. `SignIn` renders the whole flow; `SignedIn` and `SignedOut` switch on the session; `UserButton` is the account menu.

    These are safe in a client component by construction — a publishable key can only call the operations marked `auth:public`.

    ```tsx theme={null}
    // app/providers.tsx
    "use client";
    import { LathProvider } from "@trylath/react";

    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <LathProvider
          publishableKey={process.env.NEXT_PUBLIC_LATH_PUBLISHABLE_KEY!}
          environmentId={process.env.NEXT_PUBLIC_LATH_ENV_ID!}
        >
          {children}
        </LathProvider>
      );
    }
    ```
  </Step>

  <Step title="Protect a route handler">
    `requireUser` verifies the caller's access token against your environment's published keys. It does not call Lath on every request — the signature is checked locally against a JWKS, so an authenticated route costs no round trip.

    It throws when the token is missing or invalid, so the happy path below is the only path you write.

    ```ts theme={null}
    // app/api/me/route.ts
    import { requireUser } from "@trylath/sdk";

    export async function GET(req: Request) {
      const user = await requireUser(req, {
        environmentId: process.env.NEXT_PUBLIC_LATH_ENV_ID!,
      });
      return Response.json({ userId: user.userId, amr: user.amr });
    }
    ```
  </Step>

  <Step title="Receive webhooks">
    A route handler must read the raw body — `await req.text()`, not `await req.json()` — because the signature is over the exact bytes that were sent. See the webhooks guide for why re-serialising breaks it.

    ```ts theme={null}
    // app/api/hooks/lath/route.ts
    import { verifyWebhook } from "@trylath/sdk";

    export async function POST(req: Request) {
      const raw = await req.text();
      if (!verifyWebhook(process.env.LATH_WEBHOOK_SECRET!, req.headers, raw)) {
        return new Response("bad signature", { status: 401 });
      }
      return new Response("ok");
    }
    ```
  </Step>
</Steps>
