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

# SDK

> A typed client generated from the operation registry, and the middleware that turns a Lath access token into a user on a request you already have.

<Steps>
  <Step title="Install and construct">
    `createLath` takes the key and returns a client whose shape is the operation registry: `lath.email.send`, `lath.auth.signin.start`, one method per operation.

    `baseUrl` defaults to the production API. `fetch` exists so a test can pass its own, which is how this package's own tests run against an in-process app.

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

  <Step title="Call an operation">
    Every method takes the input object and returns `{ activityId, result }` — the same envelope every surface returns. `activityId` is the audit row for the call, and is empty for reads.

    The second argument carries `idempotencyKey` for anything that must not happen twice.

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

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

    const { result } = await lath.email.send(
      { to: "you@example.com", subject: "Hello", text: "First one." },
      { idempotencyKey: "order-1043-receipt" },
    );
    console.log(result.status);
    ```
  </Step>

  <Step title="Handle a refusal">
    A non-2xx throws `LathError`, which carries the same four things the wire carries: `code` to branch on, `message` for a log, `fix` for a human, and `status`.

    Branch on `code`, never on `message` — the message is written for a person and may be reworded; the code is the contract.

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

    try {
      await lath.email.send({ to: address, subject, text });
    } catch (e) {
      if (e instanceof LathError && e.code === "spending_cap_reached") {
        // e.fix names the operation that raises the cap
        return { retryAfterBilling: true, fix: e.fix };
      }
      throw e;
    }
    ```
  </Step>

  <Step title="Protect your own routes">
    `verifyAccessToken` checks a Lath access token against your environment's published keys, so your API can trust a token your frontend obtained without calling Lath on every request.

    `requireUser` and `getUser` do it for a standard `Request`; `withUser` wraps a handler; `lathHono` and `lathExpress` are the same thing shaped for those two frameworks. `userFromNodeRequest` covers a raw Node request.

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

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

  <Step title="Verify a webhook">
    `verifyWebhook` is in the same package, and takes the raw body rather than a parsed object. See the webhooks guide for why that distinction is the whole of it.

    React components for sign-in and the rest live in `@trylath/react`, which is a separate install.

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

    if (!verifyWebhook(process.env.LATH_WEBHOOK_SECRET!, req.headers, rawBody)) {
      return new Response("bad signature", { status: 401 });
    }
    ```
  </Step>

  <Step title="A console session, not a key">
    A key belongs to one environment, so it already knows where it is acting. A member's console access token belongs to a *person* who may be in several accounts, so it has to name the environment on every call — pass `environmentId` to `createLath` or per call, or the API refuses with `environment_required`.

    This is what lets an operator tool be built on the same typed client a customer uses.

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

    const lath = createLath({ key: consoleAccessToken, environmentId: env });
    ```
  </Step>
</Steps>
