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

# SMS rules

> Texting is the one product with a gatekeeper outside Lath. Here is what has to be true before a message leaves, what happens to one that cannot, and what a text actually costs.

## Nothing sends until a carrier registration is active

`sms.registration.submit` files one registration per project — `standard` for a company, `sole_proprietor` for a person. A standard registration needs a tax id and a sole proprietor is refused if it sends one. The tax id is used for the filing and not kept.

`sms.registration.get` answers with the status and, more usefully, a `blocker`: one sentence saying what is stopping SMS right now. It is null only when the status is `active`. While the carrier is deciding, the blocker reads "with the carrier: nothing to do but wait, usually days"; if the carrier wants more, it carries the carrier's own message.

Refiling replaces the registration. Submitting while one is already active is refused with `already_registered`, and while one is in flight with `registration_in_flight`.

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

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

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

  const { result } = await lath.sms.registration.get({});
  ```

  ```bash CLI theme={null}
  lath sms registration get
  ```
</CodeGroup>

## A blocked text is stored, not refused

This is the part that surprises people. `sms.send` with no registration and no number does **not** return an error. The message is written, counted and queued, and the result carries a `blocked` object saying why it cannot go yet — "No carrier registration has been filed for this project yet", or "The registration is active but this environment has no phone number".

It is done that way so nothing is lost while the channel is opening: the messages your app produced during the wait send once it opens, rather than having to be reconstructed.

Consent is a different field. A recipient who has opted out gets `status: "suppressed"`, which is not a blocker and never sends — see the consent guide.

## Quiet hours hold marketing, and only marketing

Quiet hours are **on by default**, 21:00 to 08:00, with no timezone set. A send inside the window is held until the window ends, not refused and not dropped: the result's `sendAfter` is the real release time.

Only a text with a `topic` is held — that is what marketing means here. A transactional text, sent without a topic, is never held, because a sign-in code at 23:00 is the whole point of a sign-in code at 23:00.

The zone comes from the contact's own `timezone` property when it has one, otherwise from the environment's setting. With no zone anywhere, a single send goes as asked rather than guessing; an SMS broadcast is refused instead, because guessing one zone for thousands of people is how a campaign wakes a continent.

`sms.settings.get`, `sms.settings.set` and `sms.settings.reset` are the controls. Reset returns to on, 21:00–08:00, no timezone.

## STOP, HELP and START

An inbound text is matched only when the keyword is the whole message — "Please stop texting me" is not a STOP, and is delivered to you as an ordinary inbound message.

STOP also answers to STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE and OPTOUT, and writes a suppression. START also answers to UNSTOP, YES and OPTIN, and lifts it. HELP and INFO change nothing about consent.

Automatic replies are off by default, because the carrier's own opt-out handling already answers these. You can set your own text for each keyword — it may use `{{project}}`, is capped at 300 characters, and is billed as the text it is. Every inbound message raises `sms.inbound.received`, and a stop or start raises its own event as well.

## What a text costs, and how long it can be

Billing counts **segments**, not messages. Plain GSM-7 text fits 160 characters in one segment, then 153 per segment after that. One character outside that alphabet — an emoji, a curly quote — turns the whole message into UCS-2, where the numbers are 70 and 67. A few GSM characters (`{`, `}`, `[`, `]`, `~`, `|`, `\`, `€`) cost two units each.

A message may be at most ten segments, which is roughly 1,530 GSM characters or 670 otherwise; over that is refused with `sms_too_long`, and the refusal names the count and the encoding so you can see which rule you hit. The body field itself is capped at 1,600 characters.

An inbound message is never billed.

## Numbers and throughput

Every environment is asked for a number the moment it is created, so the usual case needs no call at all. `sms.number.list` shows it and its status — `pending` while the carrier is being asked, `active` once it can send, `failed` with the reason if the carrier refused. `sms.number.search` and `sms.number.buy` get another; `sms.number.release` gives one back, and refuses to release the last active one.

A test environment cannot send a text at all. That is deliberate: there is no sandbox carrier, so a test send would either cost real money or lie about having gone.

Each environment may send ten texts a second and five hundred an hour; over either is refused rather than queued. SMS broadcasts are not counted against it and cap at 50,000 recipients.
