# HTTP API

> The read-only LOPW HTTP API: every public endpoint, parameters, response shapes, pagination, caching, errors and the Server-Sent Events stream, with the OpenAPI 3.1 document.

*LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/api.html · This Markdown twin: https://lopw.com/docs/protocol/api.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt

Read-only records of bonded services, warranty claims and protocol events, built for third-party apps, mobile clients and autonomous agents. The chain is the source of truth; the API is an indexed, cacheable view of it.

OpenAPI 3.1 document: [/docs/api/openapi.json](https://lopw.com/docs/api/openapi.json) (also served by the API itself at `/v1/openapi.json`). Base URL: `https://api.lopw.com`.

## Endpoints

| Method and path | Returns | Parameters |
| --- | --- | --- |
| `GET /healthz` | `{ status, indexed_ledger, indexed_at }`; 503 with a problem when unhealthy | — |
| `GET /v1/openapi.json` | This document | — |
| `GET /v1/{network}/stats` | `Stats`: network-wide totals | `network` |
| `GET /v1/{network}/services` | `{ data: Service[], next_cursor }` | `state`, `cursor`, `limit` |
| `GET /v1/{network}/services/{id}` | `Service` | `id`: 64 hex |
| `GET /v1/{network}/services/{id}/claims` | `{ data: Claim[], next_cursor }`, newest first | `outcome`, `cursor`, `limit` |
| `GET /v1/{network}/services/{id}/events` | `{ data: Event[], next_cursor }`, newest first | `cursor`, `limit` |
| `GET /v1/{network}/claims` | `{ data: Claim[], next_cursor }`, newest first | `outcome`, `cursor`, `limit` |
| `GET /v1/{network}/stream` | `text/event-stream` of contract events | `after` (or the `Last-Event-ID` header) |
| `GET /v1/{network}/scenarios` | `Scenario[]` with plain-language notes and recent runs | `runs` 0 to 100, default 5 |
| `GET /v1/rates` | Exchange-rate snapshot with exact cross rates | `base`, `symbols`, `date` |

`network` is `testnet` or `mainnet`. `limit` is 1 to 200, default 50. Operator-only endpoints under `/v1/admin/` are not public.

## Schemas

| Schema | Fields |
| --- | --- |
| `Service` | `service_id`, `seller`, `state`, `stake`, `min_stake`, `pending_withdrawal`, `terms_hash`, `key_version`, `claims { paid, rejected, total_paid }`, `registered_at`, `updated_at` |
| `Claim` | `id`, `service_id`, `predicate_id` 1 to 6, `outcome` `paid` \| `rejected`, `reason` `not_proven` \| `not_verifiable` \| null, `buyer`, `amount`, `payment_reference`, `transaction`, `ledger`, `occurred_at` |
| `Event` | `id`, `kind` (for example `service_registered`, `deposited`, `slashed`, `state_changed`, `claim_paid`, `claim_rejected`), `service_id`, `contract_id`, `transaction`, `ledger`, `topics`, `data`, `occurred_at` |
| `Stats` | `services`, `active_services`, `total_stake`, `claims_paid`, `claims_rejected`, `total_paid`, `indexed_ledger` |
| `Scenario` | `id`, `title`, `summary`, `why`, `steps[]`, `expected`, `service`, `totals { runs, passed }`, `runs[]` with `outcome` `pass` \| `fail`, `observed`, `payment_tx`, `claim_tx`, `payout`, `stake_before`, `stake_after` |
| `Problem` | `type`, `title`, `status`, `code` (stable, mirrors contract error names where applicable), `detail` |

Amounts are decimal strings in the stake asset’s smallest unit (USDC: 7 decimals); 32-byte values are lowercase hex; times are Unix seconds; `state` is one of `Pending`, `Active`, `Suspended`, `Unbonding`, `Closed`.

## Pagination, caching, limits, errors

- **Pagination:** pass the previous page’s `next_cursor` as `cursor`; `null` means the end.
- **Caching:** reads carry `ETag`; send `If-None-Match` to receive `304 Not Modified`.
- **Rate limits:** advertised with `RateLimit-*` headers; exceeding them returns `429` with `Retry-After`.
- **Errors:** `application/problem+json` with `status` 400, 404 or 429 and a machine-readable `code`.
- **CORS:** open on read endpoints; no cookies.

## Live stream

```http
GET /v1/testnet/stream
Accept: text/event-stream

id: 00000000000000123
event: claim_paid
data: { "id": "…", "kind": "claim_paid", "service_id": "…", "contract_id": "C…", "transaction": "…", "ledger": 5021500, "topics": [ … ], "data": { … }, "occurred_at": 1790991420 }
```

Each message has `id` (event ID), `event` (the kind) and `data` (an `Event`). Resume after a disconnect with the `Last-Event-ID` header or `?after=`. Monitoring is built on this stream plus direct chain reads.
