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

# Keys and permissions

> Two kinds of key, seven products, and one rule underneath both: a key can only grant what the caller creating it already holds.

## Secret and publishable

A secret key is `lath_live_` or `lath_test_` and 43 more characters. It carries the permissions you name and belongs on a server.

A publishable key is `lath_live_pk` or `lath_test_pk` and 40 more, so anyone reading a bundle can tell the two apart at a glance. It holds exactly one permission, `auth:public`, which reaches only the end-user sign-in operations — `auth.signin.start`, `auth.signin.verify`, `auth.session.refresh`, `auth.session.exchange`, `auth.session.signout`. That is why it is safe in a browser, an app or a CLI.

The kind is fixed when the key is made. There is no call that turns one into the other.

## What there is to grant

A permission is a product and a verb: `email:write`, `auth:read`, `billing:read`. The products are account, auth, email, sms, audience, developers and billing, and both verbs exist for each, so the full list is derived rather than written down anywhere.

`auth:public` is not in that list. It is the publishable key's built-in permission and is never granted to a secret key by name.

You cannot grant what you do not hold. A key without `billing:write` cannot create one that has it, and the refusal says so: `permission_exceeds_caller`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://platform.trylath.com/developers/key/create \
    -H "Authorization: Bearer lath_live_..." \
    -H "Content-Type: application/json" \
    -d '{"name":"web backend","kind":"secret","permissions":["email:write","auth:read"]}'
  ```

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

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

  const { result } = await lath.developers.key.create({
    name: "web backend",
    kind: "secret",
    permissions: ["email:write", "auth:read"],
  });
  ```

  ```bash CLI theme={null}
  lath developers key create \
    --name 'web backend' \
    --kind secret \
    --permissions email:write \
    --permissions auth:read
  ```
</CodeGroup>

## The plaintext is shown once

`developers.key.create` returns the key itself in that one response. No operation returns it again — `developers.key.list` reports the prefix, the permissions and when it was last used, and never a plaintext or a hash.

Store it before you make the next call. If it is lost, the way back is `developers.key.rotate`, not a lookup.

## Changing a key without replacing it

`developers.key.update` changes a key's name and its permissions. The key itself does not change, so nothing holding it has to be redeployed and no request fails while it happens.

That is the one to reach for when a service needs one more scope. Rotation is for a key you no longer trust.

## Rotate, and revoke

`developers.key.rotate` issues a new key with the same name and the same permissions and revokes the old one in the same transaction. The new plaintext is returned once, exactly as create returns it. The old key stops working immediately — there is no overlap window, so deploy the new one promptly.

`developers.key.revoke` ends a key on its own. It is refused when it is the last key in the environment holding `developers:write`, because an environment that can no longer manage its own keys cannot be recovered through the API.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://platform.trylath.com/developers/key/rotate \
    -H "Authorization: Bearer lath_live_..." \
    -H "Content-Type: application/json" \
    -d '{"keyId":"..."}'
  ```

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

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

  const { result } = await lath.developers.key.rotate({ keyId: "..." });
  ```

  ```bash CLI theme={null}
  lath developers key rotate --keyId ...
  ```
</CodeGroup>
