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

# Error codes

> Every refusal the API can make — 142 of them, by HTTP status, each with its fix.

Every error is the same shape on every surface: `{ error: { code, message, fix } }`. Branch on `code` — it is the contract. The message is written for a person and may be reworded.

<Tip>
  Every refusal carries a `fix`. That is enforced by the type: it is a required argument, so an error that does not say what to do next cannot be constructed. An agent holding one has its next step named for it.
</Tip>

## HTTP 400

| Code                        | Message                                                                                                                | Fix                                                                                                                                                                     |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `audience_too_large`        | This audience has \{total} contacts; a single broadcast sends to at most \{MAX\_RECIPIENTS}.                           | Narrow the segment and send in parts.                                                                                                                                   |
| `confirmation_mismatch`     | That is not this organization's name. To delete it, pass name exactly as "\{org.name}".                                | Nothing was changed. The name is a confirmation, because this removes every member's access at once and cannot be undone.                                               |
| `contrast_too_low`          | \{failures.length} colour pair\{…} fall below WCAG AA (4.5:1): \{…}.                                                   | Set \{…}, or choose different backgrounds. Every value shown was computed to pass.                                                                                      |
| `environment_required`      | A dashboard session must name the environment it is acting in.                                                         | Send the \{CONSOLE\_ENV\_HEADER} header with an environmentId from your workspaces.                                                                                     |
| `inbound_empty`             | The message is empty.                                                                                                  | Send the raw RFC 822 message as the body.                                                                                                                               |
| `inbound_no_recipient`      | The envelope recipient is missing or not an address.                                                                   | Pass RCPT TO as envelopeTo (JSON) or x-lath-envelope-to (message/rfc822). The To header is the sender's text and never routes a message.                                |
| `inbound_no_sender`         | The message has no From header and no envelope sender.                                                                 | Every message needs a sender.                                                                                                                                           |
| `invalid_definition`        | *built at the call site*                                                                                               | Fix the segment definition and retry.                                                                                                                                   |
| `invalid_domain`            | "\{input}" is not a bare domain name.                                                                                  | Pass the domain only, like mail.example.com, without a scheme, path or port.                                                                                            |
| `invalid_input`             | *built at the call site*                                                                                               | Correct the fields named and retry.                                                                                                                                     |
| `invalid_json`              | The request body is not valid JSON.                                                                                    | Send a JSON object with content-type application/json.                                                                                                                  |
| `missing_variables`         | The message references \{…} but no value was given.                                                                    | Add \{…} to variables. Nothing was sent.                                                                                                                                |
| `no_usable_signin_method`   | This would leave the environment with no sign-in method anyone can use. \{why}                                         | Leave at least one method that is both enabled and available — auth.settings.get reports availability for each, and emailCode is always available. Nothing was changed. |
| `not_a_link`                | This challenge is a code, not a link.                                                                                  | Enter the code in the app instead.                                                                                                                                      |
| `passkey_rejected`          | The authenticator's response could not be verified: \{…}                                                               | Make sure the page runs on one of the project's allowed origins and try again.                                                                                          |
| `password_breached`         | That password has appeared in \{…} known data breaches.                                                                | Choose a password you have never used anywhere else.                                                                                                                    |
| `password_too_long`         | The password has \{chars} characters; the limit is \{rules.maxLength}.                                                 | Use \{rules.maxLength} characters or fewer.                                                                                                                             |
| `password_too_short`        | The password has \{chars} characters; at least \{rules.minLength} are required.                                        | Use \{rules.minLength} characters or more. A sentence works well.                                                                                                       |
| `sms_too_long`              | This message is \{size.segments} segments (\{size.units} \{…} in \{size.encoding}), and the limit is \{MAX\_SEGMENTS}. | *names the specific value; arrives on the response*                                                                                                                     |
| `state_mismatch`            | That sign-in did not come from this browser.                                                                           | Start again with auth.oauth.start.                                                                                                                                      |
| `template_invalid`          | Inline content has \{problems.length} syntax problem\{…}: \{…}.                                                        | Fix each named line, or escape literal braces by writing them outside \{\{ }}.                                                                                          |
| `template_missing_required` | An override of \{name} must show \{…} in every body it has: \{…}. Without it the message cannot do its job.            | Add the variable to each named part. Variables available: \{…}, plus project and style.\*.                                                                              |
| `wrong_code`                | That code is not right. \{…} attempts left.                                                                            | Ask the user to re-enter the code from the message.                                                                                                                     |

## HTTP 401

| Code                   | Message                                                                                 | Fix                                                                                                                      |
| ---------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `code_expired`         | That code has expired.                                                                  | Codes live two minutes. Start the sign-in again.                                                                         |
| `code_invalid`         | That code is not recognised in this environment.                                        | Codes are single-use and environment-bound; start the sign-in again.                                                     |
| `code_used`            | That code was already exchanged.                                                        | A code works once. Start the sign-in again if the tokens were lost.                                                      |
| `invalid_credentials`  | That address and password do not match.                                                 | Check both, or sign in with a code instead.                                                                              |
| `mfa_token_invalid`    | This sign-in is not waiting for a second factor.                                        | Start the sign-in again.                                                                                                 |
| `refresh_reused`       | This refresh token was already used once. Every session in its family has been revoked. | Sign the user in again. A refresh token used twice means it may have leaked; check where the copy came from.             |
| `session_expired`      | This refresh token has expired.                                                         | Sign the user in again.                                                                                                  |
| `session_impersonated` | An impersonated session cannot be refreshed.                                            | It lasts up to thirty minutes and then ends. Call auth.user.impersonate again to continue, which records a fresh reason. |
| `session_invalid`      | That refresh token is not recognised.                                                   | Sign the user in again.                                                                                                  |
| `session_revoked`      | The session behind this code was revoked.                                               | Sign the user in again.                                                                                                  |
| `token_invalid`        | This dashboard session has been signed out.                                             | Sign in to the dashboard again.                                                                                          |
| `unauthenticated`      | No valid key was presented.                                                             | Send `Authorization: Bearer <key>` with a key from Developers → Keys or developers.key.create.                           |

## HTTP 402

| Code                   | Message                                                                                                                                                                                                           | Fix                                                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `billing_past_due`     | This account has an unpaid invoice \{standing.days} days overdue, so bulk sending is paused. Transactional messages — sign-in codes, receipts, anything your own users triggered — are unaffected and still send. | Add a working card with billing.payment.setup. Sending resumes as soon as the outstanding invoice is collected. |
| `spending_cap_reached` | This would bring \{…} spending to \{…} against a cap of \{…}. Nothing was sent.                                                                                                                                   | Raise or remove the cap with billing.cap.set, or wait for the next month.                                       |

## HTTP 403

| Code                        | Message                                                                                             | Fix                                                                                                                                                                                                               |
| --------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `destination_not_allowed`   | This deployment does not send to +\{code} numbers, and \{to} is one.                                | The allowed country codes are \{…}. An operator changes them with LATH\_SMS\_ALLOWED\_COUNTRIES. This list exists because the profitable destinations for SMS fraud are the ones a normal product never sends to. |
| `forbidden`                 | You are not a member of the account that owns this environment.                                     | Pick an environment from your workspaces, or ask an owner to invite you.                                                                                                                                          |
| `invite_wrong_account`      | This invitation was sent to \{inv.email}, which is not one of your verified addresses.              | Sign in with that address, or add it to this account first.                                                                                                                                                       |
| `invite_wrong_address`      | This invitation is for \{inv.email}, and you are signed in as \{s.email}.                           | Sign in to the console as \{inv.email} to accept it.                                                                                                                                                              |
| `live_only`                 | \{operation} changes what live users receive, so it needs the live environment.                     | Call it with the live key (or the live environment in the dashboard); test keys can draft and read.                                                                                                               |
| `method_disabled`           | Sign in with \{input.provider} is turned off in this environment's sign-in settings.                | Enable it with auth.settings.set \{"methods":\{"\{input.provider}":true}}, or use a method auth.settings.get lists as offered.                                                                                    |
| `mfa_required`              | \{what} needs a session that passed two-step sign-in.                                               | Sign in again and enter the code from your authenticator app (or a recovery code), then retry.                                                                                                                    |
| `owner_only`                | Only an owner can invite another owner.                                                             | Invite them as an admin, or ask an owner.                                                                                                                                                                         |
| `permission_exceeds_caller` | You cannot grant \{…}: this caller does not hold \{…}.                                              | Create the key from a caller that holds every permission it should carry, or ask an owner.                                                                                                                        |
| `reauth_required`           | \{what} needs a recent sign-in.                                                                     | Sign in again (a fresh code, password or passkey), then retry.                                                                                                                                                    |
| `signup_closed`             | This environment does not accept new users, and no account exists for that address.                 | Create the user first with auth.user.create, or allow sign-ups with auth.settings.set \{"signup":\{"allow":true}}.                                                                                                |
| `signup_domain_blocked`     | This environment does not create accounts for addresses at \{verdict.domain}.                       | *names the specific value; arrives on the response*                                                                                                                                                               |
| `test_mode_recipient`       | \{addr} was invited to this account but has not accepted yet, so a test environment cannot mail it. | Open the invitation on that address and sign in once — account.member.accept — and the seat becomes active. Until an address answers, nothing shows anyone reads it.                                              |
| `user_banned`               | This user is banned and cannot sign in.                                                             | Unban with auth.user.unban if that is wrong.                                                                                                                                                                      |

## HTTP 404

| Code                  | Message                                                                                                                              | Fix                                                                                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_code_unknown` | That code is not waiting for approval. It may have expired or been typed wrong.                                                      | Ask the device to start again and read the new code.                                                                                                                                               |
| `domain_unknown`      | \{…} is not a domain any project has turned receiving on for.                                                                        | Inbound is accepted only for a domain a customer has verified with email.domain.verify and then enabled with email.domain.receiving.set. Verifying a domain to send from does not make it receive. |
| `invite_invalid`      | This invitation is not open.                                                                                                         | Ask an owner to invite you again.                                                                                                                                                                  |
| `not_found`           | Project not found.                                                                                                                   | Check the key belongs to a live project.                                                                                                                                                           |
| `topic_unknown`       | There is no topic "\{input.topic}" in this environment, so consent for it could never be shown to the person or used by a broadcast. | Create it first with audience.topic.create \{"key":"\{input.topic}","name":"…"}, or use audience.topic.list to see the keys that exist.                                                            |

## HTTP 409

| Code                     | Message                                                                                                                               | Fix                                                                                                                                                                                               |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account_closed`         | This account was closed on \{…}\{…}, so nothing sends from it. Nothing was sent.                                                      | Reopen it with account.reopen, or from the account page in the dashboard. Messages, contacts and invoices were all kept.                                                                          |
| `account_empty`          | The account has no project yet.                                                                                                       | Ask an owner to create a project first.                                                                                                                                                           |
| `account_open`           | This account is not closed.                                                                                                           | Nothing to do.                                                                                                                                                                                    |
| `active_signing_key`     | \{input.kid} is the key signing new tokens right now, so retiring it would stop every current sign-in from verifying.                 | Call auth.signingKey.rotate first — that mints a new key and makes it active — then retire this one.                                                                                              |
| `address_suppressed`     | \{…} \{address} \{…} suppressed, so \{what} cannot be delivered: \{may.detail}.                                                       | *names the specific value; arrives on the response*                                                                                                                                               |
| `already_a_reversal`     | That row is itself a reversal.                                                                                                        | Reverse the original instead, or record a new credit if the intent is to give money back again.                                                                                                   |
| `already_archived`       | The topic \{input.key} is already archived.                                                                                           | Nothing to do. audience.topic.restore brings it back if that was not what you meant.                                                                                                              |
| `already_member`         | \{email} is already a member of \{org.name}.                                                                                          | Change their role with auth.org.member.update instead.                                                                                                                                            |
| `already_registered`     | This project's carrier registration is already active.                                                                                | Nothing to do. Use sms.registration.get to read it.                                                                                                                                               |
| `already_retired`        | Signing key \{input.kid} is already retired.                                                                                          | Nothing to do. auth.signingKey.list shows when it was.                                                                                                                                            |
| `already_reversed`       | That credit has already been reversed.                                                                                                | The reversal is \{already.id}. Record a new credit if the intent is to give money back again.                                                                                                     |
| `already_sent`           | "\{b.name}" has finished sending; there is nothing left to cancel.                                                                    | Messages already delivered cannot be recalled. Suppress addresses you must not mail again with audience.suppression.add.                                                                          |
| `automation_archived`    | Automation "\{a.name}" is archived and cannot change.                                                                                 | Create a new automation.                                                                                                                                                                          |
| `automation_not_active`  | Automation "\{a.name}" is \{a.status}.                                                                                                | Only an active automation can be paused.                                                                                                                                                          |
| `broadcast_not_draft`    | Broadcast "\{b.name}" is \{b.status} and cannot change.                                                                               | Create a new broadcast instead.                                                                                                                                                                   |
| `channel_unavailable`    | Phone numbers cannot be added until SMS sending is wired.                                                                             | Add an email address meanwhile.                                                                                                                                                                   |
| `contact_exists`         | A contact with email \{email} already exists (\{r.rows\[0].id}).                                                                      | Use audience.contact.update on that contact, or audience.contact.import which merges by address.                                                                                                  |
| `delivery_in_flight`     | This delivery is \{exists.status} and will be attempted on its own.                                                                   | Wait for it to finish, then replay if it failed.                                                                                                                                                  |
| `domain_ambiguous`       | \{…} is verified in \{owners.length} projects, so its inbound mail has no single owner.                                               | Remove the domain from every project but one with email.domain.remove; mail for it is refused until then.                                                                                         |
| `domain_exists`          | \{domain} is already added to this project.                                                                                           | Read it with email.domain.get \{"domainId":"\{dup.rows\[0].id}"} or verify it with email.domain.verify.                                                                                           |
| `domain_not_verified`    | \{row\.domain} is \{row\.status}, and only a verified domain can receive mail.                                                        | *names the specific value; arrives on the response*                                                                                                                                               |
| `email_taken`            | An account already exists for this email.                                                                                             | Sign in to that account instead, or use a different email for a new one.                                                                                                                          |
| `endpoint_disabled`      | This webhook endpoint is disabled, so a test would never be delivered.                                                                | Create a new endpoint with developers.webhook.create, or test a different one.                                                                                                                    |
| `factor_not_pending`     | This factor is \{f.status}.                                                                                                           | Enrol a new one with auth.factor.totp.enroll.                                                                                                                                                     |
| `idempotency_mismatch`   | This idempotency key was already used with different input.                                                                           | Use a new idempotency key for new input, or resend the original input.                                                                                                                            |
| `identity_exists`        | \{email} is already one of this user's addresses.                                                                                     | Nothing to add.                                                                                                                                                                                   |
| `identity_taken`         | \{email} cannot be added to this account.                                                                                             | Use a different address, or sign in to the account that address already belongs to.                                                                                                               |
| `label_taken`            | \{label}.\{SENDING\_PARENT} already belongs to another project.                                                                       | Choose a different label, or send from a domain you own after email.domain.add and email.domain.verify.                                                                                           |
| `last_identity`          | This is the only address on the account; it cannot be removed.                                                                        | Add another address with auth.identity.add first.                                                                                                                                                 |
| `last_key`               | This is the last key with developers:write in this environment.                                                                       | Create another key with developers:write first, then revoke this one.                                                                                                                             |
| `last_owner`             | This is the account's only active owner.                                                                                              | Make another active member an owner first with account.member.update, then change or remove this one.                                                                                             |
| `member_exists`          | \{email} is already an active member (\{existing.role}).                                                                              | Change their role with account.member.update if that is what you meant.                                                                                                                           |
| `mfa_code_required`      | This account already has a second factor.                                                                                             | Finish signing in with auth.mfa.verify and a code from the app, then manage factors with the access token.                                                                                        |
| `mfa_enrol_required`     | This account has no second factor yet and this environment requires one.                                                              | Enrol with auth.factor.totp.enroll and auth.factor.totp.activate using this mfaToken.                                                                                                             |
| `mfa_factor_missing`     | The account's second factor is gone.                                                                                                  | Start the sign-in again.                                                                                                                                                                          |
| `mfa_required_by_org`    | \{…} require\{…} two-step sign-in of members.                                                                                         | Leave the organization, or enrol a replacement first.                                                                                                                                             |
| `mfa_required_here`      | This environment requires two-step sign-in for everyone.                                                                              | Enrol a replacement first with auth.factor.totp.enroll, or ask the operator to change the setting.                                                                                                |
| `no_{ch}_address`        | *built at the call site*                                                                                                              | Nothing to do. The preferences page only offers a channel the contact has an address for.                                                                                                         |
| `no_billing_customer`    | This account has never started adding a card.                                                                                         | Call billing.payment.setup and complete the page it returns.                                                                                                                                      |
| `no_draft`               | Template "\{input.name}"\{…} has no draft to publish.                                                                                 | Save one with email.template.set\{…} first.                                                                                                                                                       |
| `no_factor`              | This account has no active second factor.                                                                                             | Enrol one with auth.factor.totp.enroll.                                                                                                                                                           |
| `no_password`            | This account has no password.                                                                                                         | Nothing to remove.                                                                                                                                                                                |
| `no_redirect`            | This link has nowhere to send you.                                                                                                    | The application must start email\_link sign-ins with a redirectTo.                                                                                                                                |
| `not_archived`           | The topic \{input.key} is not archived.                                                                                               | Nothing to do; it is already in use.                                                                                                                                                              |
| `not_paused`             | "\{b.name}" is not paused.                                                                                                            | Nothing to resume. email.broadcast.get shows its state.                                                                                                                                           |
| `not_sending`            | "\{b.name}" is \{…}.                                                                                                                  | *names the specific value; arrives on the response*                                                                                                                                               |
| `owner_elsewhere`        | \{m.email} already owns another account; one address owns at most one account.                                                        | Make them an admin here, or have them transfer the other account first.                                                                                                                           |
| `passkey_unconfigured`   | Passkeys need an origin to bind to and none is configured.                                                                            | Set redirect.allowedOrigins (or passkey.rpId and passkey.origins) with auth.settings.set.                                                                                                         |
| `publishable_key`        | A publishable key carries exactly one permission (auth:public), and it cannot be changed.                                             | Create a secret key for anything more and keep it on your server.                                                                                                                                 |
| `redirect_not_allowed`   | \{verdict.origin} is not an allowed redirect origin for this \{t.mode} environment.                                                   | Add it once with auth.settings.set \{"redirect":\{"allowedOrigins":\[\{…}]}}. Test environments accept [http://localhost](http://localhost) without listing it.                                   |
| `registration_in_flight` | This registration is with the carrier and cannot be changed until they answer.                                                        | Wait for status to become active or action\_needed, then refile if asked.                                                                                                                         |
| `run_finished`           | This run is already \{r.status}.                                                                                                      | Nothing to cancel.                                                                                                                                                                                |
| `sender_unverified`      | \{input.from} is not on a verified sending domain of this project. Its own sending domain is \{…}, and any address there may be used. | Send from any address at \{…}, or add and verify the domain: email.domain.add \{"domain":"\{…}"}, create the records it returns, then email.domain.verify.                                        |
| `slug_taken`             | The slug "\{input.slug}" is already used in this environment.                                                                         | Choose another slug, or omit it to have one derived from the name.                                                                                                                                |
| `suppression_protected`  | This suppression is a \{s.reason} and cannot be lifted by hand.                                                                       | It lifts automatically when the person opts in again: record their consent with audience.consent.grant. Sending to a known bounce or complaint address damages deliverability for every customer. |
| `topic_exists`           | A topic with key "\{input.key}" already exists.                                                                                       | Use audience.topic.update to change it, or a different key.                                                                                                                                       |

## HTTP 410

| Code                   | Message                                                          | Fix                                                            |
| ---------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------- |
| `challenge_expired`    | This code or link has expired.                                   | Start a new sign-in with auth.signin.start.                    |
| `challenge_spent`      | This code or link was already used, or ran out of attempts.      | Start a new sign-in with auth.signin.start.                    |
| `device_code_consumed` | This device code was already redeemed.                           | Start a new device sign-in.                                    |
| `device_code_denied`   | The session that approved this device has since been signed out. | Start a new device sign-in and approve it again.               |
| `invite_expired`       | This invitation has expired.                                     | Ask an owner to invite you again; invitations last seven days. |
| `invite_spent`         | This invitation was already used or withdrawn.                   | Ask for a new invitation.                                      |
| `mfa_token_expired`    | This sign-in took too long.                                      | Start the sign-in again.                                       |
| `mfa_token_spent`      | This sign-in was already completed or ran out of attempts.       | Start the sign-in again.                                       |

## HTTP 413

| Code                            | Message                                                                                     | Fix                                                                                                                                                              |
| ------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attachments_too_large`         | The attachments come to \{…} MB and the limit is \{…} MB for one message. Nothing was sent. | Send fewer or smaller files, or link to them instead. Many mailboxes reject large mail outright, so a message accepted here can still be refused at the far end. |
| `body_too_large`                | The request body is \{declared} bytes, and the limit is \{MAX\_BODY\_BYTES}.                | Send less. An email's HTML is capped at 200 KB and its text at 100 KB by the operation's own schema, so a body this size is not one this API accepts.            |
| `inbound_attachments_too_large` | Attachments total \{attachmentBytes} bytes; the limit is \{MAX\_ATTACHMENT\_BYTES}.         | Messages with more than 10 MB of attachments are not accepted.                                                                                                   |
| `inbound_too_large`             | The message is \{rawSize} bytes; the limit is \{MAX\_INBOUND\_BYTES}.                       | Messages over 25 MB are not accepted.                                                                                                                            |

## HTTP 429

| Code                | Message                                                                                                          | Fix                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `auth_send_limited` | This environment has sent as much sign-in and invitation mail as it is allowed in this window\{…}.               | The limit is \{…} an hour and \{…} a day per environment, and it applies to mail Lath sends on your behalf rather than to email.send. It exists because auth.signin.start is reachable with a publishable key, which is public by design. If this is legitimate traffic, an operator raises LATH\_AUTH\_SENDS\_PER\_HOUR; if it is not, a key has leaked and rotating it is the fix. |
| `rate_limited`      | Too many calls for this key and operation.                                                                       | Retry after \{retryAfterSeconds} seconds.                                                                                                                                                                                                                                                                                                                                            |
| `slow_down`         | Poll at most every \{DEVICE\_POLL\_INTERVAL\_S} seconds.                                                         | Wait \{DEVICE\_POLL\_INTERVAL\_S} seconds between polls.                                                                                                                                                                                                                                                                                                                             |
| `test_mode_limit`   | This test environment has sent \{…} messages in the last 24 hours, and the limit is \{TEST\_MODE\_DAILY\_LIMIT}. | Wait for the window to move, or send from the live environment, where the spending cap applies instead. Test sends are not billed, so this is the only thing standing between a loop and a reputation problem.                                                                                                                                                                       |
| `too_fast`          | That was answered faster than a person reads an email.                                                           | New accounts wait \{…}s between the code being sent and accepted. Start again, or turn it off with auth.settings.set \{"botProtection":\{"minChallengeAgeSeconds":0}}.                                                                                                                                                                                                               |

## HTTP 500

| Code                   | Message                                                                         | Fix                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `data_key_invalid`     | \{name} must be 32 bytes, base64 encoded (got \{key.length} bytes).             | Generate one with: openssl rand -base64 32                                                                |
| `data_key_missing`     | LATH\_DATA\_KEY is not set, so secrets cannot be stored or read.                | Set LATH\_DATA\_KEY to 32 random bytes, base64: openssl rand -base64 32                                   |
| `data_key_unknown`     | A stored secret was sealed with key \{kid}, which this process does not hold.   | Set LATH\_DATA\_KEY\_PREVIOUS to the key that sealed it, then re-seal by re-creating the resource.        |
| `internal`             | Read operation \{def.name} tried to emit \{type}. Reads change nothing.         | Make it a mutation, or remove the emit.                                                                   |
| `output_mismatch`      | \{def.name} returned something its declared output does not describe: \{detail} | This is a defect in Lath rather than in the request. The operation ran; only the response shape is wrong. |
| `secret_unreadable`    | A stored secret is not in the sealed format.                                    | This row predates envelope encryption or was written by hand; re-create the resource.                     |
| `signing_key_unsealed` | Signing key \{kid} is stored unsealed.                                          | Retire it (set retired\_at) so a sealed key is minted on the next sign-in.                                |

## HTTP 502

| Code                   | Message                                                | Fix           |
| ---------------------- | ------------------------------------------------------ | ------------- |
| `oauth_provider_error` | The provider refused the \{what} step (\{res.status}). | It said: \{…} |

## HTTP 503

| Code                       | Message                                                                                                    | Fix                                                                                                                                              |
| -------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `breach_check_unavailable` | The password breach check could not be reached, so the password was not set.                               | Try again in a moment. An operator can turn the check off with auth.settings.set \{"password":\{"breachCheck":false}}, which is not recommended. |
| `dns_unavailable`          | DNS lookup for \{name} failed: \{…}.                                                                       | Try email.domain.verify again in a minute; if it keeps failing the resolver, not your records, is the problem.                                   |
| `payments_unavailable`     | This deployment has no payment processor configured, so no card can be added and nothing is being charged. | An operator sets LATH\_STRIPE\_SECRET\_KEY. Until then usage is measured and reported (billing.usage.get) and never collected.                   |
| `provider_unconfigured`    | Sign in with \{args.provider} is not configured on this deployment.                                        | Set \{names.id} and \{names.secret}, or give this project its own app with auth.oauth.app.set.                                                   |
| `try_again`                | Could not allocate a user code.                                                                            | Call auth.device.start again.                                                                                                                    |
