Skip to content

API Reference

Base URLs:

  • Production: https://api.ultimalotto.com
  • Sandbox: https://sandbox.ultimalotto.com

Rate Limits

All endpoints are rate-limited:

TierLimitIdentifier
Public (no auth)60 requests/minuteIP address
Authenticated (API key or JWT)120 requests/minuteAPI key

Response headers on every request:

  • X-RateLimit-Limit — max requests per window
  • X-RateLimit-Remaining — requests remaining
  • X-RateLimit-Reset — seconds until window resets

Exceeding the limit returns HTTP 429 with { "error": "Rate limit exceeded" }.


Public API (No Auth)

GET /api/public/rounds

List rounds with pagination.

Query: limit (default 50), offset (default 0)

Response:

json
{
  "rounds": [
    {
      "id": 42,
      "state": "CLOSED",
      "ticketPriceUsdc": 3000000,
      "totalTickets": 1500,
      "operatorCount": 3,
      "escrowedOperatorCount": 3,
      "excludedOperatorCount": 0,
      "fullManifestHash": "abc123...",
      "effectiveManifestHash": "def456...",
      "drawSeed": "a1b2c3...",
      "winnerIndex": 42,
      "winningOperatorId": "op_xxx",
      "createdAt": "2025-01-15T12:00:00.000Z",
      "scheduledDrawTime": "2025-01-15T14:00:00.000Z"
    }
  ],
  "count": 1
}

GET /api/public/rounds/:id

Get a single round by ID.

Response: Full round object including operatorIds, escrowedOperatorIds, excludedOperatorIds, previousRoundHash, chainedAnchorHash.

GET /api/public/rounds/:id/events

Get event log for a round.

Response:

json
{
  "events": [
    {
      "type": "ROUND_OPENED",
      "roundId": 42,
      "timestamp": "2025-01-15T12:00:00.000Z",
      "data": {}
    }
  ],
  "count": 1
}

GET /api/public/operators

List all operators.

Response:

json
{
  "operators": [
    {
      "operatorId": "op_xxx",
      "name": "Acme Lottery",
      "jurisdiction": "Malta",
      "active": true,
      "reputationScore": 100,
      "totalRoundsParticipated": 10,
      "totalRoundsWon": 1,
      "registeredAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "count": 1
}

GET /api/public/operators/:id

Get a single operator by ID.

GET /api/public/stats

Protocol statistics.

Response:

json
{
  "totalRounds": 100,
  "closedRounds": 95,
  "failedRounds": 2,
  "activeRounds": 3,
  "totalOperators": 15,
  "activeOperators": 12,
  "totalTicketsSold": 150000,
  "protocolLaunchDate": "2025-01-01T00:00:00.000Z"
}

POST /api/public/apply

Submit operator application.

Body:

json
{
  "companyName": "Acme Lottery",
  "jurisdiction": "Malta",
  "gamingLicenseNumber": "MGA/123",
  "contactEmail": "ops@acme.com",
  "contactName": "Jane Doe",
  "ethereumAddress": "0x...",
  "website": "https://acme.com",
  "description": "Optional description"
}

Response:

json
{
  "success": true,
  "applicationId": "app_xxx",
  "applicationToken": "ult_xxx",
  "status": "pending",
  "message": "Application submitted. Save your application token to check status.",
  "warning": "Save this application token — it cannot be retrieved again."
}

GET /api/public/application-status/:id

Check application status. Requires X-Application-Token header.

Response (approved):

json
{
  "applicationId": "app_xxx",
  "companyName": "Acme Lottery",
  "status": "approved",
  "operatorId": "op_xxx",
  "onboarding": {
    "step1": "Post your operator bond on-chain...",
    "step2": "Install the Operator SDK...",
    "sdkDocs": "https://docs.ultimalotto.com/operator-sdk"
  }
}

Operator API (Auth: X-Ultima-Key or Bearer JWT)

POST /api/operator/manifest

Submit a manifest for a round.

Body:

json
{
  "roundId": 42,
  "manifestJson": "{\"entries\":[...]}",
  "manifestHash": "sha256_hex_string",
  "ticketCount": 500
}

Response:

json
{
  "success": true,
  "roundId": 42,
  "state": "OPEN",
  "sealedCount": 1,
  "totalInvited": 3
}

POST /api/operator/heartbeat

Record liveness heartbeat.

Response:

json
{
  "success": true,
  "operatorId": "op_xxx",
  "timestamp": "2025-01-15T12:00:00.000Z"
}

PUT /api/operator/webhook

Register or update webhook URL.

Body:

json
{
  "url": "https://your-domain.com/webhook"
}

Response:

json
{
  "success": true,
  "operatorId": "op_xxx",
  "webhookUrl": "https://your-domain.com/webhook"
}

GET /api/operator/rounds

Get rounds the operator is invited to.

Response:

json
{
  "rounds": [...],
  "count": 5
}

GET /api/operator/me

Get operator profile.

Response: Operator object (excluding apiKeyHash).

POST /api/operator/test-webhook

Send a test webhook (sandbox). Body: { "eventType": "round.opened" }.

POST /api/operator/test-webhook/all

Send all webhook event types (sandbox).

GET /api/operator/webhook-logs

View recent webhook delivery logs (sandbox).


Auth API

POST /api/auth/operator/login

Exchange API key for JWT pair.

Body:

json
{
  "apiKey": "ulk_your_api_key"
}

Response:

json
{
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "expiresIn": 900
}

POST /api/auth/refresh

Refresh access token. Body: { "refreshToken": "..." }. Old refresh token is revoked.

POST /api/auth/logout

Revoke refresh token. Body: { "refreshToken": "..." }.


Error Codes

See Error Reference for HTTP status codes and error response format.