---
title: "Errors and how to act on them"
summary: "LatticeKit returns machine-readable problem documents rather than prose, and many failures carry a remediation naming what to do next — the contract a program or an agent should code against."
---

# Errors and how to act on them

A growing share of callers are programs. A failure that only a human can interpret is a
failure that stops an automation, so LatticeKit returns errors in a fixed, machine-
readable shape and, where it can, says what to do next.

## The shape

Failures are returned as problem documents — a standard structure with a `type`, a short
`title`, a `status`, and a `detail` explaining this particular occurrence. Content type
is `application/problem+json`.

```json
{
  "type": "https://foundation.dev/problems/booking-slot-unavailable",
  "title": "The slot is no longer available",
  "status": 409,
  "detail": "Another booking took the last place at 09:00 while this request was in flight."
}
```

Error responses are never hand-built prose and never plain strings. If you are parsing
an error message to work out what happened, parse `type` instead — it is stable, and the
human-readable fields are not.

## Remediations

Some failures carry a **remediation**: a structured hint naming the concrete next step.
Rather than "invalid request", a remediation says which capability to call, which field
to change, or which document explains the rule that was violated.

This exists because the alternative — an agent retrying the same call with slightly
different guesses — is both expensive and unlikely to converge. A remediation turns a
dead end into a next action.

Where a remediation points at documentation, it names a document in this set.

## Status codes

The usual meanings apply, with a few worth calling out:

| Status | What it means here |
|---|---|
| `401` | The credential is missing, malformed or expired. Indistinguishable from a revoked one, deliberately. |
| `403` | Authenticated, but this credential is not permitted this action or this scope. |
| `404` | Either it does not exist, or it exists outside your scope. These are not distinguished, because distinguishing them leaks. |
| `409` | A conflict with current state — something changed underneath you. Usually worth re-reading and retrying once. |
| `422` | The request was understood and refused. The `detail` says why; a retry without changing something will fail identically. |
| `429` | Rate limit reached. Back off and retry after the `Retry-After` header. |

The `type` is an identifier to match on, not a page to fetch. A connected agent's daily
budget is different from a rate limit: when it is spent, the tool call comes back as a
result marked as an error whose text says the allowance is used up for the day —
repeating the call will not get past it, and neither will backing off.

## Idempotency

Write requests accept an idempotency key, and for some callers one is required. This is
not decoration: a program that times out re-sends without ever learning whether the first
attempt landed, and no operator is watching to notice the second one. Send a stable key
per logical operation, and a retry is safe.

A repeated key returns the original outcome rather than performing the action twice.

## Retrying

Retry `409` once after re-reading state, and `429` after backing off. A spent daily
budget is not retryable until the next day. Do not retry `422`
unchanged — it is a considered refusal, not a transient fault. Do not retry a write
without an idempotency key.
