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

# How Lath works

> Four surfaces over one registry of operations, three products over one audience, and a shape that is the same everywhere. Twenty minutes here saves an afternoon of surprises later.

## Account, project, environment

An account holds people and the bill. It holds projects. Each project has exactly two environments, live and test, and a key belongs to one environment — which is how a key knows which world it acts on, with no flag to set and none to forget.

**The part worth reading twice is what sits on the project rather than the environment**, because live and test share it: the theme, the templates, the sending domains, the default sender identity, the carrier registration for text messages, and your own OAuth apps. A template edited while you are 'in test' is the template live sends. That is deliberate — a brand and a body of words are not per-environment things — but it is the assumption most people arrive with and it is wrong.

Per environment is everything that is data about your users: users and their identities, contacts, topics, consent, suppressions, segments, messages, broadcasts, automations, webhook endpoints, phone numbers, sign-in settings, and the keys themselves. The same person signing into live and test is two rows with two ids.

## One envelope, one refusal

Every operation on every surface answers `{ activityId, result }`. `activityId` names the audit row the call wrote, and it is an **empty string for reads** — a read changes nothing, so it writes no activity row and may not raise an event.

A refusal is `{ error: { code, message, fix, requestId } }`. `fix` is not optional and not decorative: an error cannot be constructed without one, so every refusal you can receive tells you what to do about it. The same id is on the `lath-request-id` header of every response, refusals included.

MCP returns refusals as results rather than protocol errors, so an agent reads the code and the fix the same way your code does.

## Four surfaces, one registry

There is one list of operations. The REST routes and the MCP tools are built by walking it when the server starts, so neither can be missing something the other has. The CLI's command list and the SDK's methods are generated files, committed to the repository — and a test compares them byte for byte against the registry, so a stale surface is a failed build rather than a surprise at runtime.

That is the whole claim behind "every surface does the same thing": not a promise anybody keeps by hand, but four derivations of one source with a guard on each.

A consequence worth knowing: an operation's REST path is its name with the dots turned into slashes, and its MCP tool name is its name with the dots turned into underscores. Learn one and you can predict the others.

## What a key may do

A permission is a product and a verb — read or write on account, auth, email, sms, audience, developers or billing. A key carries a list of them, and an operation declares the one it needs.

The check happens once, in the runtime every surface shares, after the key is resolved and before the database transaction opens. There is no second place to keep in step and no surface that can skip it.

Refused, you get `forbidden`, naming the permission that was missing and telling you to create a key with it or to rotate this one with `developers.key.rotate` after adding it. MCP additionally hides tools a key could not call, so an agent is not offered work it cannot do — but the check still runs on every call regardless.

## How something that happened reaches your server

An operation raises events while it runs. They are held until it succeeds, then written **inside the same transaction as the work itself** — so an event for something that was rolled back cannot exist.

In that same transaction, one delivery row is written for every enabled endpoint whose filter matches. An endpoint with no event types listed gets everything; otherwise it is an exact match or a `product.*` prefix.

A worker claims due deliveries every couple of seconds and makes one HTTP attempt each, signed, carrying the event id, type and delivery id as headers. A failure is retried on a widening schedule — a minute, five, thirty, two hours, twelve, twenty-four — and is then dead until you replay it. A delivery that was claimed but never finished is requeued after two minutes, so a worker dying mid-attempt costs a delay rather than an event.
