API
Everything the app reads is public. Anyone can build on the same endpoints.
Base URL and conventions
https://api.unpeg.io/v1 https://api.unpeg.io/openapi.json
- Lamports, lots, slots, ids and ratio parts are decimal integer strings. Parse them as BigInt, never as a float.
- Timestamps are ISO 8601 UTC. Basis points and durations are JSON integers.
- Errors come as
{ code, message, requestId }. No provider details, keys or RPC text ever appear. - Unknown fields, numeric amounts in place of strings and mismatched action fields are rejected.
- 120 requests per minute per IP; 30 per minute for transaction builds. Back off on 429.
- Browsers need their origin listed in the API's CORS configuration; there is no wildcard and no cookie auth.
Read endpoints
| Endpoint | Returns |
|---|---|
GET /v1/config | Cluster, program id, fixed terms, supported actions, oracle trust model and limitation flags. |
GET /v1/markets | Registered markets with the latest current-price observation and the latest qualified settlement interval. |
GET /v1/markets/:id | One market. Ids are jitosol, msol, hsol. |
GET /v1/markets/:id/observations | Discount history, newest first. Paginate with limit, before, from, to. |
GET /v1/markets/:id/references | Net asset value history. |
GET /v1/observations/:id/evidence | The exact stored evidence for one observation and its SHA-256 digest. |
GET /v1/series | Registered series with their decoded on-chain state. Optional marketId filter. |
GET /v1/series/:address | One series. |
GET /v1/series/:address/offers | Open escrow sell offers. Optional side=BREAK|HOLD. |
GET /v1/wallets/:wallet/positions | BREAK and HOLD balances per series. |
GET /v1/wallets/:wallet/offers | Offers made by the wallet, including expired ones awaiting cancellation. |
GET /v1/transactions/:signature | NOT_FOUND, PROCESSED, CONFIRMED, FINALIZED or FAILED. |
GET /v1/events | Server-sent events for observations, references and registrations, with replay by Last-Event-ID. |
GET /health/live | Process is up. |
GET /health/ready | 503 while any market lacks a fresh qualified interval or no program is configured. |
Quotes and previews
| Endpoint | Body | Returns |
|---|---|---|
POST /v1/quotes | { seriesAddress, side, lots } | Cheapest legs from current offers, filled and unfilled lots, total premium. Nothing is reserved. |
POST /v1/payoff-preview | { discountBps, durationSeconds, lots } | Deterministic BREAK and HOLD lamports for a constant discount. Scenario only. |
Transaction builder
POST /v1/transactions/build takes seriesAddress, wallet, action and the fields below, simulates the transaction, and returns it unsigned as base64 together with the blockhash, last valid block height, required signer and program id. Failing simulations return 422 instead of a transaction. The server never signs and has no submit endpoint: verify the payer, program and blockhash in the browser, sign with the wallet, and send through your own RPC connection.
| Action | Extra fields |
|---|---|
| mint_pairs | lots |
| redeem_pairs | lots |
| redeem_break, redeem_hold | lots |
| create_offer | lots, side, pricePerLotLamports, expiresAt, offerId (u64 string, maker-chosen, one use) |
| fill_offer | offerAddress, lots |
| cancel_offer | offerAddress |
| skip_interval | none; uses the next unresolved interval |
| settle | none |
SDK
The backend ships a dependency-free TypeScript client (UnpegClient) and shared types; the app vendors them into its own tree. The OpenAPI document at /openapi.json is generated from the same schemas that validate every request, so any generator will produce a matching client.
curl https://api.unpeg.io/v1/markets
curl https://api.unpeg.io/v1/markets/jitosol/observations?limit=50
curl -X POST https://api.unpeg.io/v1/payoff-preview \
-H 'content-type: application/json' \
-d '{"discountBps":180,"durationSeconds":129600,"lots":"1"}'