Parlays API
The Platform API (Django) indexes the high-market-parlay program from
Helius webhooks and serves the resulting state over REST.
| Environment | Base URL |
|---|---|
| Dev | https://dev-api.gomarket.io |
| Production | https://prod-api.gomarket.io |
Each has its own database.
Routes
Section titled “Routes”| Method | Path | Auth |
|---|---|---|
GET | /api/parlays/user/ | Platform JWT |
GET | /api/parlays/{parlay_id}/ | Platform JWT |
GET | /api/parlays/vault/ | none |
PATCH | /api/parlays/vault/ | superuser |
POST | /api/parlays/vault/sync/ | superuser |
GET | /api/parlays/admin/ | superuser |
POST | /api/parlays/admin/{parlay_id}/void/ | superuser |
POST | /api/parlays/webhooks/helius/ | webhook secret |
GET | /api/markets/parlay-suggestions/ | none |
GET /api/parlays/user/
Section titled “GET /api/parlays/user/”Your parlays, newest first, capped at 50.
curl -s "https://prod-api.gomarket.io/api/parlays/user/?status=active" \ -H "Authorization: Bearer $JWT"Query: status — active / won / lost / claimed / voided /
refunded.
{ "data": [ { "id": "3f2a…", // internal UUID "parlay_id": 1042, // on-chain id "user_wallet": "7xKX…", "stake": 10000000, // micro-USDC "combined_probability": "0.050000000000", "potential_payout": 200000000, "actual_payout": 0, "status": "active", "legs_total": 3, "legs_won": 1, "legs_lost": 0, "legs_resolved": 1, "tx_signature": "5Kx…", "claim_tx_signature": "", "created_at": "2026-09-14T10:22:01Z", "settled_at": null, "claimed_at": null, "legs": [ { "leg_index": 0, "on_chain_market_id": 412, "market_address": "9aBc…", "selected_outcome": "YES", "probability_at_entry": "0.500000", "status": "won", "resolved_at": "2026-09-15T18:00:00Z", "question": "Will the Fed cut rates in December?" } ] } ]}Matching is by the authenticated user or a case-insensitive match on their wallet address, so slips placed before the wallet was linked still appear.
GET /api/parlays/{parlay_id}/
Section titled “GET /api/parlays/{parlay_id}/”One parlay, scoped to the caller.
curl -s https://prod-api.gomarket.io/api/parlays/1042/ \ -H "Authorization: Bearer $JWT"A parlay that isn’t yours returns the same 404 as one that doesn’t exist —
the id space must not be probeable. Don’t treat 404 as “deleted.”
Status values
Section titled “Status values”| Status | On-chain | Set by | Claimable |
|---|---|---|---|
active | Active (0) | create_parlay | — |
won | Won (1) | settle_leg | yes — payout |
lost | Lost (2) | settle_leg | no |
claimed | Claimed (3) | claim_parlay | no |
voided | Voided (4) | settle_leg, when every leg’s market cancelled | yes — stake refund |
refunded | Refunded (5) | void_parlay (admin) | no — already paid |
Leg statuses: pending, won, lost, voided.
| Field | Unit |
|---|---|
stake, potential_payout, actual_payout | micro-USDC (1e6) |
probability_at_entry | decimal 0–1, 6 dp |
combined_probability | decimal, 12 dp |
probability_at_entry is locked at create time and never recomputed.
curl -s https://prod-api.gomarket.io/api/parlays/vault/{ "data": { "vault_address": "…", "balance": 500000000, "total_staked": 120000000, "total_paid_out": 80000000, "total_exposure": 210000000, "max_payout_cap": 500000000, "max_legs": 10, "min_stake": 1000000, "max_stake": 100000000, "is_active": true, "updated_at": "2026-09-17T09:00:00Z" }}Public read. PATCH is superuser-only and accepts vault_address,
max_legs, max_payout_cap, min_stake, max_stake, is_active.
POST /api/parlays/vault/sync/
Section titled “POST /api/parlays/vault/sync/”Superuser. Re-reads the vault token-account balance over Solana RPC
(getTokenAccountBalance) and updates the DB record. Returns 502 on RPC
failure.
The API’s numbers are a mirror maintained from webhooks. On-chain is authoritative — sync before acting on them.
Useful derived value:
headroom = 0.8 × balance − total_exposureWhen it nears zero, large slips start failing with ExposureLimitExceeded
while small ones still succeed. → Vault & Solvency
curl -s "https://prod-api.gomarket.io/api/parlays/admin/?status=active&wallet=7xKX…&page=1" \ -H "Authorization: Bearer $ADMIN_JWT"Paginated at 20 per page, returns { "count": n, "results": [...] }.
curl -s -X POST https://prod-api.gomarket.io/api/parlays/admin/1042/void/ \ -H "Authorization: Bearer $ADMIN_JWT"Only valid on an active parlay; otherwise 400.
Webhook
Section titled “Webhook”POST /api/parlays/webhooks/helius/Authorization: <PARLAY_WEBHOOK_AUTH_SECRET>Compared with hmac.compare_digest. Answers 503 when unconfigured, 403
on mismatch.
Accepts a Helius transaction payload (single or array). Transactions with
meta.err are skipped; the rest have their logMessages decoded into parlay
events and dispatched atomically.
{ "processed": 3, "skipped": 0 }Out-of-order delivery
Section titled “Out-of-order delivery”Solana webhook delivery is not ordered — a LegSettled can arrive before the
ParlayCreated that made the row it references. The indexer raises
DeferredParlayEvent, the webhook answers 503, and Helius retries.
Worth copying if you index this program yourself. The failure mode without it is a silently dropped settlement.
Events mirrored
Section titled “Events mirrored”| Event | Effect |
|---|---|
ParlayCreated | New Parlay row + N ParlayLeg rows |
LegSettled | Leg status + parlay aggregates |
ParlayClaimed | → claimed, records claim_tx_signature |
ParlayVoided | → refunded (admin void_parlay) |
ConfigInitialized / ConfigUpdated | Config mirror |
VaultSeeded / VaultWithdrawn | Vault mirror |
Events are Anchor #[event]s, written to the transaction log as base64
Program data: lines prefixed with the 8-byte event discriminator.
Suggested parlays
Section titled “Suggested parlays”curl -s https://prod-api.gomarket.io/api/markets/parlay-suggestions/Public. Curated combo templates for the webapp’s “Suggested” cards.
Admin CRUD:
| Method | Path |
|---|---|
GET/POST | /api/markets/admin/parlay-templates/ |
GET/PATCH/DELETE | /api/markets/admin/parlay-templates/{pk}/ |
POST | /api/markets/admin/parlay-templates/{pk}/refresh/ |
POST | /api/markets/admin/parlay-templates/refresh-all/ |
Templates are previews. In the webapp, demo legs carry a synthetic negative
marketId and no marketAddress, and are blocked from submission — they
show the shape of a combo without being placeable.
Database model
Section titled “Database model”ParlayLeg is unique on (parlay, on_chain_market_id), which enforces the
no-duplicate-markets rule at the storage layer as well as on-chain.
Indexes: (user_wallet, status), (status, -created_at),
(on_chain_market_id, status) — the last one supports “settle every leg
waiting on this market.”