Competition
Competition overview, your queue standing, and (for winners) prize payout nomination. Round lifecycle is admin-operated; these routes are for integrators and the product UI.
For the product rules (League of Loaf), see Trading competition. For the ranked board, see Leaderboard.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/competition | Public competition overview |
GET | /api/competition/queue-position | Your queue / final placement |
GET | /api/competition/queue-position/card | Queue-position share card (JPEG) |
GET | /api/competition/payout-details | Winner payout status |
POST | /api/competition/payout-details | Nominate prize destination (one-shot) |
Competition overview
GET /api/competition
Public, unauthenticated. Returns every round summary, a featured round (ACTIVE → PREPARED → latest), base maker/taker fees, and the current queue size. When no rounds exist, rounds is empty and featuredRound is null.
Cached briefly for edge clients (Cache-Control: public, max-age=0, s-maxage=5).
Request
import axios from 'axios';
const response = await axios.get('https://api.loafmarkets.com/api/competition');
console.log(response.data);Response
Type: CompetitionInfoResponse
interface CompetitionInfoResponse {
rounds: CompetitionRoundSummary[];
featuredRound: CompetitionFeaturedRound | null;
makerFeeBps: number;
takerFeeBps: number;
queueCount: number;
}
interface CompetitionRoundSummary {
roundNumber: number;
name: string;
startsAt: number | null; // Unix seconds
endsAt: number | null;
status: CompetitionRoundStatus;
totalPrizePool: number; // whole USDC
participantBatchSize: number;
}
type CompetitionRoundStatus =
| 'DRAFT'
| 'PENDING'
| 'PREPARED'
| 'ACTIVE'
| 'ENDING'
| 'ENDED';
interface CompetitionFeaturedRound {
roundNumber: number;
name: string;
rules: string;
startsAt: number | null;
endsAt: number | null;
startingBalanceUsdl: number;
status: CompetitionRoundStatus;
newAssetProperty: TradePropertyItem | null;
prizePool: PrizePoolEntry[];
volumeMultiplierTiers: VolumeMultiplierTier[];
bottomCullPercent: number;
}
interface PrizePoolEntry {
place: number; // 1 = first
amount: number; // whole USDC (display)
}
interface VolumeMultiplierTier {
minVolume: number;
multiplier: number;
}Errors
| Status | Code | Description |
|---|---|---|
500 | internal_error | Unexpected server error. |
Queue position
GET /api/competition/queue-position
Authenticated. Returns your standing in the admission queue or (during the break after a round) your final placement from the last ended round. The two are mutually exclusive: show finalPlacement when set, otherwise position.
The queue exists independently of a live round. When no round config is available, priorityBoostPlaces and maxBoostsPerUser fall back to server defaults.
Request
import axios from 'axios';
const response = await axios.get(
'https://api.loafmarkets.com/api/competition/queue-position',
{ headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` } },
);
console.log(response.data);Response
Type: QueuePositionResponse
interface QueuePositionResponse {
position: number | null;
queueCount: number;
finalPlacement: number | null;
referralCount: number;
priorityBoostPlaces: number; // N in the triangular boost formula
maxBoostsPerUser: number; // Z — cap on referrals that count toward boost
}Example response (queued):
{
"position": 128,
"queueCount": 2400,
"finalPlacement": null,
"referralCount": 2,
"priorityBoostPlaces": 100,
"maxBoostsPerUser": 5
}Errors
| Status | Code | Description |
|---|---|---|
401 | unauthorized | Missing or invalid authentication. |
500 | internal_error | Unexpected server error. |
Requires API key authentication. Sensitive endpoints are also rate limited per account.
Queue-position card
GET /api/competition/queue-position/card
Authenticated. Returns a JPEG share image for your current queue position.
Response headers include Content-Type: image/jpeg and Cache-Control: private, no-store (including on auth failure).
Errors
| Status | Code | Description |
|---|---|---|
401 | unauthorized | Missing or invalid authentication. |
404 | not_found | You are not currently in the competition queue. |
500 | internal_error | Unexpected server error. |
Payout details
Get payout status
GET /api/competition/payout-details
Authenticated. Read-only “have I claimed yet?” for the latest ended round. Non-winners and “no ended round” return 200 with eligible: false (not an error).
Nominate payout destination
POST /api/competition/payout-details
Authenticated. Prize winners nominate exactly one of walletAddress or email. Accepted once per round. A second submission returns 409.
Request body (POST)
Type: CompetitionPayoutDetailsRequestBody
interface CompetitionPayoutDetailsRequestBody {
walletAddress?: string;
email?: string;
}Provide exactly one of the two fields.
Request (GET)
import axios from 'axios';
const response = await axios.get(
'https://api.loafmarkets.com/api/competition/payout-details',
{ headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` } },
);
console.log(response.data);Request (POST)
import axios from 'axios';
import type { CompetitionPayoutDetailsRequestBody } from '@loafmarkets/shared-types';
const body: CompetitionPayoutDetailsRequestBody = {
walletAddress: '0xabc…',
};
const response = await axios.post(
'https://api.loafmarkets.com/api/competition/payout-details',
body,
{ headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` } },
);
console.log(response.data);Response
Type: CompetitionPayoutDetailsResponse
interface CompetitionPayoutDetailsResponse {
eligible: boolean;
roundNumber: number | null;
place: number | null;
submission: CompetitionPayoutSubmission | null;
}
interface CompetitionPayoutSubmission {
payoutType: 'WALLET' | 'EMAIL';
walletAddress: string | null;
email: string | null;
submittedAt: number; // Unix seconds
}submission non-null means already claimed (further POST returns 409). null means still claimable when eligible is true.
Errors
| Status | Code | Description |
|---|---|---|
400 | validation_error | Body failed schema validation (e.g. both or neither destination). |
401 | unauthorized | Missing or invalid authentication. |
403 | — | Caller is not a prize winner for the latest ended round (POST). |
409 | — | No ended round yet, or payout already submitted (POST). |
503 | — | Round ended but frozen final board is missing — retry later. |
500 | internal_error | Unexpected server error. |