Appearance
API Reference
Version: V2 — Number-based tickets, jackpot rollovers, sub-prizes, 6 winning numbers per draw
Base URLs:
- Production:
https://api.ultimalotto.com- Sandbox:
https://api.ultimalotto.com(withSANDBOX_MODE=true)Authentication: See Authentication Guide
Public API (No Auth Required)
These endpoints are fully public — no API key or JWT needed. Used by player apps, verification tools, and marketing sites.
GET /api/public/stats
Protocol-wide statistics including jackpot state and odds.
Response:
json
{
"totalRounds": 127,
"settledRounds": 115,
"failedRounds": 2,
"activeRounds": 10,
"totalOperators": 5,
"activeOperators": 3,
"totalTicketsSold": 4218750,
"jackpotPool": "847200000000000",
"drawsSinceLastWin": 42,
"currentOddsRange": 10000000,
"cumulativeTickets": "4218750",
"protocolLaunchDate": "2026-01-01T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
jackpotPool | string | Current jackpot in micro-USDC (divide by 1,000,000 for USD) |
currentOddsRange | number | Jackpot number range (1 to this value) |
cumulativeTickets | string | Total tickets settled across all draws (for odds ladder) |
GET /api/public/jackpot
Current jackpot state with next draw info. Used by jackpot counter components.
Response:
json
{
"jackpot": "847200000000000",
"drawsSinceLastWin": 42,
"lastWinRoundId": 73,
"cumulativeTickets": "4218750",
"currentOddsRange": 10000000,
"nextDrawTime": "2026-02-17T02:00:00.000Z",
"nextDrawRoundId": 128,
"ticketPrice": 3000000,
"updatedAt": "2026-02-16T02:15:00.000Z"
}| Field | Type | Description |
|---|---|---|
jackpot | string | Jackpot pool in micro-USDC |
ticketPrice | number | Protocol ticket price in micro-USDC (3000000 = $3.00) |
nextDrawTime | string | null | ISO timestamp of next scheduled draw |
GET /api/public/jackpot-counter
Lightweight counter data (pre-formatted for display). Cacheable.
Response:
json
{
"jackpot": "847200000.00",
"currency": "USD",
"drawsSinceLastWin": 42,
"nextDrawTime": "2026-02-17T02:00:00.000Z",
"ticketPrice": "3.00",
"currentOdds": "1 in 10,000,000"
}GET /api/public/rounds
List rounds with pagination.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Max rounds to return |
offset | number | 0 | Skip first N rounds |
Response:
json
{
"rounds": [
{
"id": 127,
"state": "SETTLED",
"ticketPriceUsdc": 3000000,
"oddsRange": 10000000,
"totalTickets": 312450,
"operatorCount": 3,
"escrowedOperatorCount": 3,
"fullManifestHash": "a1b2c3d4e5f6...",
"effectiveManifestHash": "f6e5d4c3b2a1...",
"drawSeed": "7890abcdef12...",
"jackpotWon": false,
"jackpotWinnerCount": 0,
"createdAt": "2026-02-15T21:00:00.000Z",
"scheduledDrawTime": "2026-02-16T02:00:00.000Z"
}
],
"count": 1
}GET /api/public/rounds/:id
Full details for a single round, including winning numbers and settlement data.
Response:
json
{
"id": 127,
"state": "SETTLED",
"ticketPriceUsdc": 3000000,
"oddsRange": 10000000,
"totalTickets": 312450,
"operatorIds": ["op_abc", "op_def", "op_ghi"],
"escrowedOperatorIds": ["op_abc", "op_def", "op_ghi"],
"excludedOperatorIds": [],
"fullManifestHash": "a1b2c3d4e5f6...",
"effectiveManifestHash": "f6e5d4c3b2a1...",
"drawSeed": "7890abcdef12...",
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"jackpotWon": false,
"jackpotWinnerCount": 0,
"jackpotAmount": "847200000000000",
"subPrizeTotalUsdc": "1050000000000",
"totalDeposited": "937350000000",
"previousRoundHash": "abcdef123456...",
"chainedAnchorHash": "654321fedcba...",
"settlementHash": "112233445566...",
"btcSettlementTxHash": "aabbccddee...",
"createdAt": "2026-02-15T21:00:00.000Z",
"scheduledDrawTime": "2026-02-16T02:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
jackpotNumber | number | null | Winning jackpot number (null if not drawn yet) |
bonus1–bonus5 | number | null | Winning bonus numbers per tier |
jackpotWon | boolean | Whether any ticket matched the jackpot number |
settlementHash | string | null | SHA-256 of the settlement proof |
GET /api/public/rounds/:id/events
Event log for a round (lifecycle progression).
Response:
json
{
"events": [
{
"type": "ROUND_OPENED",
"roundId": 127,
"timestamp": "2026-02-15T21:00:00.000Z",
"data": {}
},
{
"type": "MANIFEST_SUBMITTED",
"roundId": 127,
"timestamp": "2026-02-16T02:01:00.000Z",
"data": { "operatorId": "op_abc", "ticketCount": 150000 }
},
{
"type": "ROUND_SEALED",
"roundId": 127,
"timestamp": "2026-02-16T02:05:00.000Z",
"data": { "sealedCount": 3 }
},
{
"type": "ROUND_DRAWN",
"roundId": 127,
"timestamp": "2026-02-16T02:30:00.000Z",
"data": {
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42
}
},
{
"type": "ROUND_SETTLED",
"roundId": 127,
"timestamp": "2026-02-16T02:45:00.000Z",
"data": { "settlementHash": "112233445566..." }
}
],
"count": 5
}GET /api/public/draw/:id
Draw result by draw ID or round ID. Includes all 6 winning numbers.
Response:
json
{
"drawId": "draw_127",
"roundId": 127,
"drawSeed": "7890abcdef12...",
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"jackpotWon": false,
"jackpotWinnerCount": 0,
"jackpotAmount": "847200000000000",
"createdAt": "2026-02-16T02:30:00.000Z"
}GET /api/public/draw/:id/winners
Winners by prize tier for a draw.
Response:
json
{
"drawId": "draw_127",
"roundId": 127,
"jackpotWon": false,
"jackpotWinnerCount": 0,
"jackpotAmount": "847200000000000",
"subPrizeTiers": [
{
"tier": 1,
"winners": [
{
"operatorId": "op_abc",
"ticketId": "UL-2026-127-0048829",
"prizeAmount": "25000000000",
"claimed": false
}
],
"count": 1
},
{ "tier": 2, "winners": [], "count": 0 },
{
"tier": 3,
"winners": [
{
"operatorId": "op_def",
"ticketId": "UL-2026-127-0102445",
"prizeAmount": "100000000",
"claimed": false
}
],
"count": 1
},
{ "tier": 4, "winners": [ "..." ], "count": 12 },
{ "tier": 5, "winners": [ "..." ], "count": 3124 }
],
"totalSubPrizeWinners": 3138
}| Tier | Prize | Odds |
|---|---|---|
| 1 (Bonus 1) | $25,000 | 1 in 1,000,000 |
| 2 (Bonus 2) | $1,000 | 1 in 100,000 |
| 3 (Bonus 3) | $100 | 1 in 10,000 |
| 4 (Bonus 4) | $10 | 1 in 1,000 |
| 5 (Bonus 5) | $5 | 1 in 100 |
GET /api/public/odds
Current odds ladder and next milestone.
Response:
json
{
"currentOddsRange": 10000000,
"currentOdds": "1 in 10,000,000",
"cumulativeTickets": "4218750",
"currentMilestone": {
"cumulativeTickets": "0",
"oddsRange": 10000000
},
"nextMilestone": {
"cumulativeTickets": "100000000",
"oddsRange": 100000000,
"ticketsRemaining": "95781250"
}
}Odds Ladder (pre-coded into protocol):
| Cumulative Tickets | Odds Range |
|---|---|
| Launch | 1 in 10,000,000 |
| 100,000,000 | 1 in 100,000,000 |
| 1,000,000,000 | 1 in 300,000,000 |
| 10,000,000,000 | 1 in 1,000,000,000 |
| 100,000,000,000 | 1 in 3,000,000,000 |
GET /api/public/history
Past draw results with winning numbers.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Max draws to return |
offset | number | 0 | Skip first N draws |
Response:
json
{
"draws": [
{
"drawId": "draw_127",
"roundId": 127,
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"jackpotWon": false,
"jackpotWinnerCount": 0,
"jackpotAmount": "847200000000000",
"createdAt": "2026-02-16T02:30:00.000Z"
}
],
"count": 1,
"limit": 20,
"offset": 0
}GET /api/public/sub-prizes/:drawId
Sub-prize breakdown by tier for a draw.
Response:
json
{
"drawId": "draw_127",
"tiers": [
{
"tier": 1,
"winnerCount": 1,
"totalPayout": "25000000000",
"winners": [
{
"operatorId": "op_abc",
"ticketId": "UL-2026-127-0048829",
"prizeAmount": "25000000000",
"claimed": false,
"claimDeadline": "2026-08-15T02:30:00.000Z"
}
]
},
{
"tier": 2,
"winnerCount": 3,
"totalPayout": "3000000000",
"winners": [ "..." ]
},
{ "tier": 3, "winnerCount": 31, "totalPayout": "3100000000", "winners": [ "..." ] },
{ "tier": 4, "winnerCount": 312, "totalPayout": "3120000000", "winners": [ "..." ] },
{ "tier": 5, "winnerCount": 3124, "totalPayout": "15620000000", "winners": [ "..." ] }
],
"totalWinners": 3471
}GET /api/public/operators
List all registered operators.
Response:
json
{
"operators": [
{
"operatorId": "op_abc",
"name": "Acme Lottery",
"jurisdiction": "Malta",
"active": true,
"reputationScore": 100,
"totalRoundsParticipated": 115,
"totalRoundsWon": 1,
"registeredAt": "2026-01-01T00:00:00.000Z"
}
],
"count": 1
}GET /api/public/operators/:id
Single operator details with full stats.
Response:
json
{
"operatorId": "op_abc",
"name": "Acme Lottery",
"jurisdiction": "Malta",
"active": true,
"reputationScore": 100,
"totalRoundsParticipated": 115,
"totalRoundsEscrowed": 115,
"totalRoundsExcluded": 0,
"totalRoundsWon": 1,
"totalPayoutsOnTime": 115,
"registeredAt": "2026-01-01T00:00:00.000Z"
}POST /api/public/apply
Submit an operator application.
Request:
json
{
"companyName": "Acme Lottery",
"jurisdiction": "Malta",
"gamingLicenseNumber": "MGA/B2B/123/2025",
"contactEmail": "ops@acme.com",
"contactName": "Jane Doe",
"ethereumAddress": "0x1234567890abcdef1234567890abcdef12345678",
"website": "https://acme.com",
"description": "Licensed online gambling operator in Malta"
}| Field | Required | Validation |
|---|---|---|
companyName | Yes | Non-empty string |
jurisdiction | Yes | Non-empty string |
gamingLicenseNumber | Yes | Non-empty string |
contactEmail | Yes | Valid email format |
contactName | Yes | Non-empty string |
ethereumAddress | Yes | 0x + 40 hex characters |
website | No | URL |
description | No | String |
Response (201):
json
{
"success": true,
"applicationId": "app_m1abc123_a1b2c3d4",
"applicationToken": "ult_abcdef1234567890abcdef1234567890",
"status": "pending",
"message": "Application submitted. Save your application token to check status.",
"warning": "Save this application token — it cannot be retrieved again."
}WARNING
Save the applicationToken immediately. It cannot be retrieved again and is required to check application status.
Error (409):
json
{
"error": "A pending application already exists for this email",
"applicationId": "app_existing_id"
}GET /api/public/application-status/:id
Check application status. Requires X-Application-Token header.
Headers:
X-Application-Token: ult_abcdef1234567890abcdef1234567890Response (approved):
json
{
"applicationId": "app_m1abc123_a1b2c3d4",
"companyName": "Acme Lottery",
"status": "approved",
"submittedAt": "2026-02-01T12:00:00.000Z",
"reviewedAt": "2026-02-03T15:30:00.000Z",
"reviewNote": "Approved — valid MGA license verified",
"operatorId": "op_xyz",
"onboarding": {
"step1": "Post your operator bond on-chain using UltimaRegistry contract",
"step2": "Install the Operator SDK: npm install @ultimalotto/operator-sdk",
"step3": "Configure your SDK with the API key provided during approval",
"step4": "Connect to the sandbox environment to test your integration",
"step5": "Once sandbox tests pass, go live!"
}
}Response (pending):
json
{
"applicationId": "app_m1abc123_a1b2c3d4",
"companyName": "Acme Lottery",
"status": "pending",
"submittedAt": "2026-02-01T12:00:00.000Z",
"reviewedAt": null,
"reviewNote": null
}Operator API (API Key Auth)
Authenticated endpoints for registered operators. Requires X-Ultima-Key header or Authorization: Bearer <JWT>.
X-Ultima-Key: ulk_your_api_key_herePOST /api/operator/tickets
Submit tickets with 6 numbers for the current round.
Request:
json
{
"roundId": 128,
"tickets": [
{
"ticketId": "UL-2026-128-0000001",
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"isQuickPick": false
},
{
"ticketId": "UL-2026-128-0000002",
"jackpotNumber": 7891234,
"bonus1": 555123,
"bonus2": 44001,
"bonus3": 9999,
"bonus4": 100,
"bonus5": 73,
"isQuickPick": true
}
]
}| Field | Type | Validation |
|---|---|---|
ticketId | string | Unique ticket identifier |
jackpotNumber | number | 1 to oddsRange (currently 10,000,000) |
bonus1 | number | 1 to 1,000,000 |
bonus2 | number | 1 to 100,000 |
bonus3 | number | 1 to 10,000 |
bonus4 | number | 1 to 1,000 |
bonus5 | number | 1 to 100 |
isQuickPick | boolean | Whether numbers were computer-generated |
Response:
json
{
"success": true,
"roundId": 128,
"operatorId": "op_abc",
"ticketsAccepted": 2,
"state": "OPEN"
}POST /api/operator/manifest
Submit a ticket manifest hash for round sealing.
Request:
json
{
"roundId": 128,
"manifestJson": "{\"entries\":[{\"ticketId\":\"UL-2026-128-0000001\",\"hash\":\"abc...\"}]}",
"manifestHash": "sha256_of_manifest_json_hex_string",
"ticketCount": 150000
}Response:
json
{
"success": true,
"roundId": 128,
"state": "OPEN",
"sealedCount": 1,
"totalInvited": 3
}POST /api/operator/check-tickets
Check your operator's tickets against a draw result. Call after the round reaches DRAWN state.
Request:
json
{
"roundId": 127
}Response:
json
{
"roundId": 127,
"operatorId": "op_abc",
"jackpotWinners": [],
"jackpotWinnerCount": 0,
"bonusTiers": [
{ "tier": 1, "winnerCount": 0, "winners": [] },
{ "tier": 2, "winnerCount": 0, "winners": [] },
{
"tier": 3,
"winnerCount": 1,
"winners": [{ "ticketId": "UL-2026-127-0048829" }]
},
{
"tier": 4,
"winnerCount": 5,
"winners": [
{ "ticketId": "UL-2026-127-0012001" },
{ "ticketId": "UL-2026-127-0034567" },
{ "ticketId": "UL-2026-127-0067890" },
{ "ticketId": "UL-2026-127-0099123" },
{ "ticketId": "UL-2026-127-0123456" }
]
},
{ "tier": 5, "winnerCount": 1504, "winners": [ "..." ] }
],
"totalWinners": 1510
}POST /api/operator/heartbeat
Record a liveness heartbeat. Used for reputation scoring.
Response:
json
{
"success": true,
"operatorId": "op_abc",
"timestamp": "2026-02-16T12:00:00.000Z"
}PUT /api/operator/webhook
Register or update your webhook URL for draw event notifications.
Request:
json
{
"url": "https://your-domain.com/api/webhooks/coordinator"
}Response:
json
{
"success": true,
"operatorId": "op_abc",
"webhookUrl": "https://your-domain.com/api/webhooks/coordinator"
}GET /api/operator/rounds
Get rounds your operator is invited to participate in.
Response:
json
{
"rounds": [
{
"id": 128,
"state": "OPEN",
"ticketPriceUsdc": 3000000,
"oddsRange": 10000000,
"totalTickets": 0,
"operatorIds": ["op_abc", "op_def", "op_ghi"],
"scheduledDrawTime": "2026-02-17T02:00:00.000Z"
}
],
"count": 1
}GET /api/operator/me
Get your operator profile.
Response:
json
{
"operatorId": "op_abc",
"name": "Acme Lottery",
"jurisdiction": "Malta",
"address": "0x1234567890abcdef1234567890abcdef12345678",
"webhookUrl": "https://your-domain.com/api/webhooks/coordinator",
"lastHeartbeat": "2026-02-16T12:00:00.000Z",
"active": true,
"reputationScore": 100,
"totalRoundsParticipated": 115,
"totalRoundsEscrowed": 115,
"totalRoundsExcluded": 0,
"totalRoundsWon": 1,
"totalPayoutsOnTime": 115,
"totalPayoutsLate": 0,
"registeredAt": "2026-01-01T00:00:00.000Z",
"onChainId": "0xabcdef...",
"onChainBondAmount": "10000000000",
"onChainBondStatus": "active",
"onChainLastAttestation": "2026-02-15T00:00:00.000Z"
}Auth API
POST /api/auth/operator/login
Exchange API key for JWT access + refresh token pair.
Request:
json
{
"apiKey": "ulk_your_api_key_here"
}Response:
json
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 900
}| Field | Description |
|---|---|
accessToken | Short-lived JWT (15 minutes). Use as Authorization: Bearer <token>. |
refreshToken | Long-lived token (7 days). Use to get a new access token. |
POST /api/auth/refresh
Refresh an expired access token. The old refresh token is revoked.
Request:
json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Response:
json
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 900
}POST /api/auth/logout
Revoke a refresh token.
Request:
json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Response:
json
{
"success": true
}Operator Player API (30+ Endpoints)
These endpoints are served by the operator's backend (apps/operator-api/), not the coordinator. Each operator hosts this on their own infrastructure.
Base URL: Operator-defined (e.g., https://demo-api.ultimalotto.com)
Auth Endpoints (No JWT)
POST /api/auth/register
json
// Request
{
"email": "player@example.com",
"password": "SecurePass123!",
"displayName": "Lucky Player"
}
// Response
{
"player": {
"id": 1,
"email": "player@example.com",
"displayName": "Lucky Player",
"kycLevel": "none",
"createdAt": "2026-02-16T12:00:00.000Z"
},
"tokens": {
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"expiresIn": 900
}
}POST /api/auth/login
json
// Request
{ "email": "player@example.com", "password": "SecurePass123!" }
// Response
{
"player": { "id": 1, "email": "player@example.com", "displayName": "Lucky Player" },
"tokens": { "accessToken": "eyJ...", "refreshToken": "eyJ...", "expiresIn": 900 }
}POST /api/auth/refresh
json
// Request
{ "refreshToken": "eyJ..." }
// Response
{ "accessToken": "eyJ...", "refreshToken": "eyJ...", "expiresIn": 900 }GET /api/auth/me
Returns current player profile. Requires Authorization: Bearer <token>.
json
{
"id": 1,
"email": "player@example.com",
"displayName": "Lucky Player",
"kycLevel": "basic",
"createdAt": "2026-02-16T12:00:00.000Z"
}Player Endpoints (JWT Required)
All require Authorization: Bearer <accessToken>.
GET /api/player/balance
json
{ "balance": "97000000", "currency": "USDC" }POST /api/player/tickets/purchase
json
// Request
{
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"isQuickPick": false
}
// Response
{
"ticket": {
"id": 1,
"ticketId": "UL-2026-128-0000001",
"roundId": 128,
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"status": "active",
"purchasedAt": "2026-02-16T14:00:00.000Z"
},
"balance": "94000000"
}GET /api/player/tickets
json
{
"tickets": [
{
"id": 1,
"ticketId": "UL-2026-128-0000001",
"roundId": 128,
"jackpotNumber": 4729156,
"bonus1": 381047,
"bonus2": 62914,
"bonus3": 4207,
"bonus4": 831,
"bonus5": 42,
"status": "active",
"winnings": null,
"purchasedAt": "2026-02-16T14:00:00.000Z"
}
],
"count": 1
}Query: ?status=active|won|lost&roundId=128&limit=50&offset=0
GET /api/player/prizes
json
{
"prizes": [
{
"id": 1,
"ticketId": "UL-2026-127-0048829",
"tier": 3,
"amount": "100000000",
"status": "unclaimed",
"claimedAt": null
}
],
"count": 1
}POST /api/player/prizes/:id/claim
json
{ "success": true, "prize": { "id": 1, "status": "claimed", "claimedAt": "2026-02-16T15:00:00.000Z" }, "balance": "194000000" }GET /api/player/transactions
json
{
"transactions": [
{ "id": 1, "type": "deposit", "amount": "100000000", "status": "completed", "createdAt": "2026-02-16T12:00:00.000Z" },
{ "id": 2, "type": "purchase", "amount": "-3000000", "status": "completed", "reference": "UL-2026-128-0000001", "createdAt": "2026-02-16T14:00:00.000Z" },
{ "id": 3, "type": "prize", "amount": "100000000", "status": "completed", "reference": "Bonus 3 — Round 127", "createdAt": "2026-02-16T15:00:00.000Z" }
],
"count": 3
}Query: ?type=purchase|deposit|withdrawal|prize&limit=50&offset=0
GET /api/player/limits
json
{
"dailyLimit": "50000000",
"weeklyLimit": "200000000",
"monthlyLimit": null,
"selfExclusionUntil": null
}POST /api/player/limits
json
// Request
{ "dailyLimit": "50000000", "weeklyLimit": "200000000" }
// Response
{ "success": true, "limits": { "dailyLimit": "50000000", "weeklyLimit": "200000000" } }POST /api/player/self-exclusion
json
// Request
{ "durationDays": 30 }
// Response
{ "success": true, "selfExclusionUntil": "2026-03-18T15:00:00.000Z" }Webhook Events
The coordinator sends webhook events to operators. See the Webhooks Guide for setup and HMAC verification.
| Event | Trigger | Key Data |
|---|---|---|
round.opened | New round created | roundId, scheduledDrawTime |
round.sealed | Ticket sales closed | roundId, sealedCount |
round.anchored | Manifest anchored on-chain | roundId, anchorHash |
round.drawn | Winning numbers computed | roundId, all 6 winning numbers |
round.settled | Settlement complete | roundId, settlementHash |
Health Check
GET /health
json
{ "status": "ok", "version": "2.0", "timestamp": "2026-02-16T12:00:00.000Z" }Error Codes
See Error Reference for HTTP status codes and error response format.
Rate Limits
See Rate Limits for detailed rate limiting information per tier.