Trade
Public market data: list tradeable properties, fetch property detail (order book snapshot, recent trades, session volume, market hours), and paginated candle history. No authentication required.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/trade | List tradeable properties |
GET | /api/trade/:tokenName | Property market detail |
GET | /api/trade/:tokenName/candles | Paginated OHLCV candle history |
For longer-form property disclosure (header, overview, documents), see Info. For live order book and trade streams, see WebSocket.
List tradeable properties
GET /api/trade
Returns every tradeable property with market data and a trailing-24h hourly sparkline (candlesticks), plus the active payment token address. For full chart history at other resolutions, use Candle history below. The list sparkline is not a substitute.
Request
import axios from 'axios';
const response = await axios.get('https://api.loafmarkets.com/api/trade');
console.log(response.data);Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| None | - | No | No parameters for this endpoint. |
Response
interface TradeResponse {
properties: TradePropertyItem[];
paymentTokenAddress: string;
}
interface TradePropertyItem {
propertyId: number;
tokenName: string;
assetName: string;
ticker: string;
contractAddress: string | null;
streetAddress: string;
country: string;
latitude: number;
longitude: number;
propertyType: string;
status: 'PENDING' | 'LIVE' | 'DELISTED';
totalTokens: number;
headerImageUrl: string;
videoUrl: string;
marketPrice: number;
dailyReferencePrice: number | null;
volume24h: number; // session-to-date settled notional
rentalYieldPercentage: number | null;
candlesticks: Array<{
time: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
}>;
}Example response:
{
"properties": [
{
"propertyId": 42,
"tokenName": "opera",
"assetName": "Sunset Villa",
"ticker": "OPR",
"contractAddress": "0xabc…",
"streetAddress": "12 Ocean Ave",
"country": "AU",
"latitude": -33.86,
"longitude": 151.21,
"propertyType": "APARTMENT",
"status": "LIVE",
"totalTokens": 10000,
"headerImageUrl": "https://cdn.example.com/header.jpg",
"videoUrl": "",
"marketPrice": 167.49,
"dailyReferencePrice": 165.0,
"volume24h": 1204.5,
"rentalYieldPercentage": 5.2,
"candlesticks": []
}
],
"paymentTokenAddress": "0xdef…"
}Errors
| Status | Code | Description |
|---|---|---|
500 | internal_error | Unexpected server error. |
Public endpoint; no API key required. volume24h is session-to-date settled notional (resets at market open), not a strict trailing 24h window.
Get your starting point here, then switch to the live feed. Call this endpoint once to load current mark price and session volume, then subscribe to WebSocket markprice:{tokenName} and volume:{tokenName} for updates — those live feeds only send changes, not the full picture.
Property market detail
GET /api/trade/:tokenName
Full market detail for one property: nested property metadata, selector list, current order book snapshot, recent trades, session volume, payment token, and market-hours metadata. Candle history is not included; fetch it from Candle history.
Request
import axios from 'axios';
const tokenName = 'opera';
const response = await axios.get(
`https://api.loafmarkets.com/api/trade/${tokenName}`,
);
console.log(response.data);Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenName | string | Yes | (path) Lowercase property tokenName (letters a-z only, max 20). Not the ticker. |
Response
interface PropertyResponse {
propertyList: Array<{
assetName: string;
tokenName: string;
ticker: string;
streetAddress: string;
suburb: string;
status: string;
}>;
property: {
propertyId: number;
tokenName: string;
assetName: string;
ticker: string;
contractAddress: string | null;
totalTokens: number;
tokensIssued: number;
rentalYieldPercentage: number | null;
streetAddress: string;
videoUrl: string;
status: 'PENDING' | 'LIVE' | 'DELISTED';
isHalted: boolean;
};
paymentTokenAddress: string;
volume24h: number;
orderBook: {
bids: Array<{ price: number; quantity: number; orderId?: number }>;
asks: Array<{ price: number; quantity: number; orderId?: number }>;
} | null;
recentTrades: Array<{
tradeId: number;
propertyId: number;
aggressorSide: 'BUY' | 'SELL';
price: number;
quantity: number;
timestamp: number;
}>;
dailyReferencePrice: number | null;
marketOpenTime: string;
marketCloseTime: string;
marketTimezone: string;
marketClosedDays: string[];
maxSlippageBps: number;
}Example response:
{
"propertyList": [
{
"assetName": "Sunset Villa",
"tokenName": "opera",
"ticker": "OPR",
"streetAddress": "12 Ocean Ave",
"suburb": "Bondi",
"status": "LIVE"
}
],
"property": {
"propertyId": 42,
"tokenName": "opera",
"assetName": "Sunset Villa",
"ticker": "OPR",
"contractAddress": "0xabc…",
"totalTokens": 10000,
"tokensIssued": 8000,
"rentalYieldPercentage": 5.2,
"streetAddress": "12 Ocean Ave",
"videoUrl": "",
"status": "LIVE",
"isHalted": false
},
"paymentTokenAddress": "0xdef…",
"volume24h": 1204.5,
"orderBook": {
"bids": [{ "price": 167.0, "quantity": 10 }],
"asks": [{ "price": 168.0, "quantity": 5 }]
},
"recentTrades": [],
"dailyReferencePrice": 165.0,
"marketOpenTime": "09:00",
"marketCloseTime": "17:00",
"marketTimezone": "Australia/Sydney",
"marketClosedDays": ["Saturday", "Sunday"],
"maxSlippageBps": 300
}Errors
| Status | Code | Description |
|---|---|---|
400 | validation_error | Invalid tokenName. |
404 | not_found | Property not found. |
500 | internal_error | Unexpected server error. |
Public endpoint; no API key required. orderBook may be null when the book is empty.
Don’t poll this in a loop. Repeated automated requests get blocked by our bot protection, and non-browser clients can’t recover. For live updates, use the WebSocket instead → orderbook:{tokenName} and trades:{tokenName}.
Candle history
GET /api/trade/:tokenName/candles
Paginated OHLCV candle history for a property. Candles are aggregated server-side to the requested resolution.
Request
import axios from 'axios';
const tokenName = 'opera';
const response = await axios.get(
`https://api.loafmarkets.com/api/trade/${tokenName}/candles`,
{ params: { resolution: '1h', countBack: 200 } },
);
console.log(response.data.candles.at(-1)); // latest candle
// Page older: pass previous response.oldestTs as `to` while hasMore is trueParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenName | string | Yes | (path) Lowercase property tokenName (letters a-z only, max 20). Not the ticker. |
resolution | string | Yes | (query) Bucket size: 1m, 5m, 15m, 1h, 4h, 1d, or 1w. |
to | number | No | (query) Return only candles strictly older than this unix-seconds timestamp. Omit for the most recent candles. |
countBack | number | No | (query) How many candles to return (server default 1000). |
Response
interface CandleHistoryResponse {
resolution: string;
candles: Array<{
time: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
}>; // oldest → newest
oldestTs: number | null;
hasMore: boolean;
}Example response:
{
"resolution": "1h",
"candles": [
{
"time": 1718000000,
"open": 165.0,
"high": 168.5,
"low": 164.2,
"close": 167.49,
"volume": 42.5
}
],
"oldestTs": 1718000000,
"hasMore": true
}Errors
| Status | Code | Description |
|---|---|---|
400 | validation_error | Invalid tokenName or resolution. |
404 | not_found | Property not found. |
500 | internal_error | Unexpected server error. |
Public endpoint; no API key required. To page backwards, pass the previous response’s oldestTs as to while hasMore is true.
Get your starting point here, then switch to the live feed. Call this endpoint once (or page it) to load chart history, then subscribe to WebSocket chart:{tokenName} for updates — the live feed only adds new candles, not the full history.