# Getting started for app builders

> Read bonded services, claims, events and live streams from the public LOPW HTTP API: JSON, cursor pagination, ETags, problem+json errors and Server-Sent Events, with open CORS.

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

Everything LOPW does on-chain is public and indexed. The read-only API serves it as JSON for third-party apps, mobile clients and agents: no wallet, no node, no key.

## Conventions

- **Versioned paths with the network in the path:** `/v1/{network}/...`, where `network` is `testnet` or `mainnet`.
- **JSON only**, `snake_case` fields matching the contract types; amounts as decimal strings (i128 does not fit a JSON number); timestamps as Unix seconds; 32-byte values as lowercase hex.
- **Errors** are RFC 9457 `application/problem+json` with a stable machine-readable `code`.
- **Cursor pagination** (`cursor`, `limit` 1 to 200, default 50), never offset pages, so clients can resume.
- **Open CORS** on read endpoints; no cookies; no auth for public reads.
- **Cache-friendly:** `ETag` and `304 Not Modified`; rate limits advertised with `RateLimit-*` headers and `429` plus `Retry-After`.
- **Real time:** `GET /v1/{network}/stream` is a Server-Sent Events feed of contract events.
- **Self-describing:** the OpenAPI 3.1 document is served at `/v1/openapi.json`; a copy lives at [/docs/api/openapi.json](https://lopw.com/docs/api/openapi.json).

## First calls

**HTTP**

```http
GET /v1/testnet/stats
// { "services": 2, "active_services": 2, "total_stake": "4000000000", "claims_paid": 9, "claims_rejected": 1, "total_paid": "320000000", "indexed_ledger": 5021999 }

GET /v1/testnet/services?state=Active&limit=50
// { "data": [ { "service_id": "…64 hex…", "seller": "G…", "state": "Active", "stake": "2000000000", "min_stake": "1000000000",
//              "pending_withdrawal": "0", "terms_hash": "…", "key_version": 1, "claims": { "paid": 3, "rejected": 0, "total_paid": "150000000" } } ],
//   "next_cursor": null }

GET /v1/testnet/services/{service_id}/claims?outcome=paid
// { "data": [ { "id": "…", "service_id": "…", "predicate_id": 6, "outcome": "paid", "reason": null, "buyer": "G…", "amount": "50000000",
//              "payment_reference": "…", "transaction": "…", "ledger": 5021500, "occurred_at": 1790991420 } ], "next_cursor": null }
```

**JavaScript**

```ts
const API = "https://api.lopw.com";
const res = await fetch(`${API}/v1/testnet/services?state=Active`);
const { data, next_cursor } = await res.json();
for (const s of data) {
  const stakeUsdc = Number(BigInt(s.stake)) / 1e7;           // decimal string -> number for display only
  console.log(s.service_id.slice(0, 8), s.state, stakeUsdc, "USDC", s.claims.paid, "claims paid");
}
```

Field values shown here are illustrative shapes, not live numbers.

## Live events

```ts
const es = new EventSource("https://api.lopw.com/v1/testnet/stream");
es.addEventListener("claim_paid", (e) => console.log("claim paid", JSON.parse(e.data)));
es.addEventListener("state_changed", (e) => console.log("state changed", JSON.parse(e.data)));
// Each message has id (event ID), event (kind) and data (an Event). Resume with the Last-Event-ID header or ?after=<id>.
```

Event kinds mirror the contract events: `service_registered`, `deposited`, `slashed`, `state_changed`, `claim_paid`, `claim_rejected`, and the admin events (`claims_change_proposed`, `validator_change_proposed`, `claims_contract_set`, `validator_set`). The full list is in the [registry](https://lopw.com/docs/protocol/bond-registry.html#events) and [claims](https://lopw.com/docs/protocol/claims.html#events) references.

## Reading the chain directly

The API is a convenience; the chain is the source of truth. Any app can read the same data with the protocol package’s binding, which simulates read-only contract calls from a throwaway account and never signs anything:

```ts
import { StellarBinding, getNetwork } from "@lopw/protocol";
const binding = new StellarBinding(getNetwork("testnet"));
const service = await binding.getService(serviceId);                   // { seller, state, stake, min_stake, terms_hash, key_version, max_claim_window, pending_withdrawal }
const terms = await binding.getTerms(serviceId, service.terms_hash);   // { predicates, claim_window, max_data_age, allowed_status, max_response_bytes, oracle }
const key = await binding.getKey(serviceId, service.key_version);      // { public_key, registered_at, retired_at }
```

Next: the complete [HTTP API reference](https://lopw.com/docs/protocol/api.html), or [read one service’s bond](https://lopw.com/docs/guides/read-bond.html) from its own discovery endpoint.
