# Common rules

> Rules shared by every LMSP endpoint, covering version headers and extensions, idempotency keys, the problem+json error format with its error codes, and the health check.

Source: https://madeforagent.com/lmsp/1.0/common-rules/
Language: en
Specification: LMSP 1.0 (draft)
Updated: 2026-11-08

## 9.1 Versions and extensions {#versioning}

Each LMSP request **MAY** carry these headers:

```http
LMSP-Version: 1.0
LMSP-Client: example-client/1.2.0
```

`LMSP-Version` is the protocol version the client targets. `LMSP-Client` identifies the client software for diagnostics, as `name/version`. A service **MUST NOT** change protocol behavior based on it.

Rules:

- The `v1` in the path denotes the major version.
- A request that names an unknown major version is answered with `426 Upgrade Required` and the error code `unsupported_protocol_version`.
- New optional fields **MUST NOT** break 1.x compatibility.
- Services and clients **MUST** ignore fields they do not know.
- Private extensions go in an `extensions` object and are named in reverse-domain notation:

```json
{
  "extensions": {
    "com.example.cache": {
      "enabled": true
    }
  }
}
```

## 9.2 Idempotency {#idempotency}

Requests that create something, such as creating a response, creating a task or loading a model, accept an idempotency key:

```http
Idempotency-Key: <client-generated-unique-value>
```

The service stores the key together with a digest of the request body and the result:

- The same key with the same body returns the original result.
- The same key with a different body is answered with `409 Conflict` and `idempotency_conflict`.

A key is scoped to the client and the endpoint. It **SHOULD** be a unique value of at most 255 characters, such as a ULID or a UUID. A service **SHOULD** keep keys for at least 24 hours.

A service **MAY** ignore the key on streaming requests. A client that needs safe retries uses a [task](https://madeforagent.com/lmsp/1.0/tasks.md).

## 9.3 Errors {#errors}

Every error on the LMSP interface uses `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)):

```json
{
  "type": "urn:lmsp:error:model-not-found",
  "title": "Model not found",
  "status": 404,
  "code": "model_not_found",
  "detail": "No local model with id 'qwen3:14b-q4_k_m'",
  "request_id": "req_01J8M9S6Y4W8C3P5R1V7Z2A0DH",
  "retryable": false
}
```

| Field | Required | Meaning |
| --- | --- | --- |
| `type` | Yes | URI that identifies the kind of error. LMSP uses `urn:lmsp:error:` followed by the code with hyphens instead of underscores. |
| `title` | Yes | Short, human-readable summary. |
| `status` | Yes | The HTTP status code. |
| `code` | Yes | Machine-readable error code, from the list below. |
| `detail` | No | Human-readable explanation of this occurrence. It **MUST NOT** contain secrets, prompts or generated content. |
| `request_id` | No | Identifier of the request, for correlation with service logs. |
| `retryable` | Yes | Whether repeating the request later can succeed. |

Common error codes:

| Code | HTTP status | Retryable | Meaning |
| --- | --- | --- | --- |
| `invalid_request` | 400 | No | The request is malformed or has invalid parameters. |
| `unauthorized` | 401 | No | The token is missing, invalid, expired or revoked, or a pairing code is wrong. |
| `forbidden` | 403 | No | The token lacks the required scope, or the `Origin` or `Host` check failed. |
| `model_not_found` | 404 | No | No local model has this id. |
| `model_not_ready` | 409 | Yes | The model is not loaded and is not loaded on demand. |
| `model_load_failed` | 500 | Yes | Loading the model failed. |
| `context_length_exceeded` | 400 | No | The input does not fit in the context window of the model. |
| `unsupported_capability` | 400 or 501 | No | The model or the service does not support what the request needs. An unimplemented optional endpoint answers 501. |
| `unsupported_protocol_version` | 426 | No | The service does not support the requested major version. |
| `response_not_found` | 404 | No | No stored response has this id. |
| `task_not_found` | 404 | No | No task has this id. |
| `task_not_cancelable` | 409 | No | The task has already succeeded or failed. |
| `idempotency_conflict` | 409 | No | The idempotency key was already used with a different request body. |
| `event_history_expired` | 410 | No | The requested events are older than the retention period. |
| `rate_limited` | 429 | Yes | Too many requests. |
| `resource_exhausted` | 503 | Yes | The device is out of memory or another resource. |
| `backend_unavailable` | 503 | Yes | The model runtime cannot be reached. |
| `server_restarted` | 503 | Yes | The service restarted while the task was unfinished. |
| `internal_error` | 500 | Varies | An unexpected failure inside the service. |

The `retryable` field is authoritative. The column above shows the usual value. A service **MAY** define further codes. A client **MUST** handle a code it does not know by its HTTP status and its `retryable` field.

## 9.4 Health check {#health}

```http
GET /lmsp/v1/health
```

```json
{
  "status": "ready",
  "service_id": "svc_01J8M4Y8K6M5E6K6Z7Z0Y8W3M2",
  "uptime_seconds": 86420,
  "loaded_models": 1,
  "active_tasks": 2,
  "storage": "ok"
}
```

| Field | Meaning |
| --- | --- |
| `status` | `starting`, `ready`, `degraded` or `stopping`. |
| `service_id` | The service id from the service description. |
| `uptime_seconds` | Seconds since the service started. |
| `loaded_models` | Number of models in the `ready` state. |
| `active_tasks` | Number of tasks that are `queued`, `loading`, `running` or `cancelling`. |
| `storage` | `ok` or `error`. |

The status is `200 OK` when `status` is `ready` or `degraded`, and `503 Service Unavailable` otherwise.

The health endpoint returns only operational status. It **MUST NOT** return model paths, prompts, access tokens or other sensitive information.
