Validated internal contract

Luckotto API

The routes, validators, request examples, schemas, and this page are generated from the same internal contract definitions.

Production API base URL: https://luckotto.com/api. Local examples use http://localhost:4000.

SourceapiContracts.tsRuntime route validation
ExamplesapiExamples.generated.tsGenerated from contract definitions
AuthenticationPublic + API keyPer-route requirement
Operational contract

Integration rules

Static examples are for shape and naming. Runtime policy comes from live API responses.

Runtime constants

Fetch https://luckotto.com/api/constants before applying confirmation, auto-ticketing, fee, or round timing policy. Checked-in examples may use faster local testnet settings and must not be copied as production policy.

Pagination limits

Documented limit query parameters accept integers from 1 through 500 and default to 100. Values outside that range return the shared JSON error envelope with code: "invalid_request".

Round CSV headers

GET /api/rounds/{roundNumber}/tickets/csv/public is a non-JSON CSV endpoint. A CSV response includes X-Luckotto-CSV-Status: final once the round CSV is sealed; that value means the CSV bytes are final, not that the draw is resolved. Before the CSV seal, the route returns an unavailable page and no CSV status header. After resolution, the same final CSV response also includes X-Luckotto-Round-Secret.

System

Public runtime constants and scanner chain-tip state.

GET/api/chain-tipGet scanner chain tip
Public

Returns the latest Bitcoin block height processed by Luckotto's scanner. External workers can compare this height with deposit confirmed block heights from deposit events and apply their own confirmation threshold.

Example request

curl -i -X GET \
  'http://localhost:4000/api/chain-tip'

Success response

200The latest scanner-processed chain tip, or nulls before the first scan.ChainTip
SchemaChainTip
Checked-in example response
{
  "processedTipHeight": 142586,
  "processedTipHash": "00000000002a5971e6f74a6ba531ed1f14830d8b59a38814e02756f7c6e5f08f",
  "processedAt": "2026-07-02T05:04:01.000Z"
}
GET/api/constantsGet API constants
Public

Returns public runtime constants used by API clients and workers, including current fees, ticket tile counts, and round timing settings.

Integration notes

The checked-in example response is illustrative and may use faster local testnet settings. Integrations must read the live production response instead of copying example confirmation counts or timing policy.

Example request

curl -i -X GET \
  'http://localhost:4000/api/constants'

Success response

200Public runtime constants.Constants
SchemaConstants
Checked-in example response
{
  "bitcoin": {
    "network": "testnet4",
    "blockExplorerUrl": "https://mempool.space/testnet4"
  },
  "wallets": {
    "commitment": {
      "descriptor": "tr([5a3469b6/86h/1h/0h]tpubDDG8vJgmngBej3WYjjomDbJkb5kmpiFbQbGeb5m4pnKrT4pv7U7kzwmMfSCPaiJ8ZuJdxFTgPungFZ9gjkLU98ruqahEYmUu68WPizuo9s1/*)",
      "accountPath": "86'/1'/0'"
    }
  },
  "fees": {
    "depositFeeSats": 58,
    "ticketFeeSats": 10
  },
  "lottery": {
    "tileCount": 36,
    "tilesPerTicket": 6
  },
  "autoTicket": {
    "confirmationBlocks": 3
  },
  "rounds": {
    "drawDelayBlocks": 2,
    "sufficientConfirmationBlocks": 2,
    "defaultDrawDelayBlocks": 2,
    "minJackpotConfirmations": 100
  },
  "draw": {
    "hashesPerCheckpoint": 1000000000
  },
  "database": {
    "currentVersion": 37,
    "liveVersion": 37
  }
}

Rounds

Public round metadata and draw proof fields.

GET/api/roundsList rounds
Public

Lists public Luckotto rounds with metadata and draw proof fields. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now.

Parameters

sortqueryrequired

Required roundNumber order. Keep the same value when following nextCursor. Values: asc, desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor. Range: 0-2147483648.

limitqueryoptional

Maximum items to return. Defaults to 100. Range: 1-500.

Example request

curl -i -X GET \
  'http://localhost:4000/api/rounds?cursor=1&sort=asc'

Success response

200A cursor page of public rounds.RoundList
SchemaRoundList
Checked-in example response
{
  "hasNext": false,
  "items": [
    {
      "roundNumber": 3,
      "opensAt": "2026-07-01T00:00:00.000Z",
      "closesAt": "2026-07-02T00:00:00.000Z",
      "soldTicketCount": 1,
      "jackpotSats": 1000,
      "roundCsvHmac": null,
      "roundSecretHash": null,
      "roundSecret": null,
      "commitmentTxid": null,
      "commitmentBlockHeight": null,
      "drawBlockHeight": null,
      "drawBlockHash": null,
      "drawTiles": null,
      "status": "open",
      "winningEntryNumber": null,
      "jackpotTxid": null,
      "resolvedAt": null
    }
  ],
  "nextCursor": "3"
}
GET/api/rounds/{roundNumber}Get round
Public

Returns one public Luckotto round with metadata and draw proof fields.

Parameters

roundNumberpathrequired

Round number. Range: 1-2147483647.

Example request

curl -i -X GET \
  'http://localhost:4000/api/rounds/3'

Success response

200The requested public round.RoundDetail
Checked-in example response
{
  "roundNumber": 3,
  "opensAt": "2026-07-01T00:00:00.000Z",
  "closesAt": "2026-07-02T00:00:00.000Z",
  "soldTicketCount": 1,
  "jackpotSats": 1000,
  "roundCsvHmac": null,
  "roundSecretHash": null,
  "roundSecret": null,
  "commitmentTxid": null,
  "commitmentBlockHeight": null,
  "drawBlockHeight": null,
  "drawBlockHash": null,
  "drawTiles": null,
  "status": "open",
  "winningEntryNumber": null,
  "jackpotTxid": null,
  "resolvedAt": null,
  "drawCheckpoints": []
}

Resellers

Public verified directory and authenticated reseller account details.

GET/api/resellersList verified resellers
Public

Returns the public verified reseller directory. Unverified resellers are intentionally omitted; query a reseller's verification status by checking whether its jackpotAddress appears with a non-null verifiedAt.

Example request

curl -i -X GET \
  'http://localhost:4000/api/resellers'

Success response

200Verified public resellers.PublicResellerList
Checked-in example response
{
  "items": [
    {
      "jackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
      "displayName": "Example Reseller",
      "website": "https://reseller.example",
      "verifiedAt": "2026-07-03T09:00:00.000Z"
    }
  ]
}
GET/api/resellers/{resellerJackpotAddress}Get reseller
Bearer API key

Returns the path reseller's deposit descriptor, jackpot address, creation time, and spendable balance. The Bearer API key must belong to this reseller.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk'

Success response

200The authenticated reseller.Reseller
SchemaReseller
Checked-in example response
{
  "depositDescriptor": "tr(tpubDDz3UJNwVjfKaxL8TJfGBGaFXxFXN37BnKvqKRmBeMSDyUfgmuF35sPsmhHeiVN6VWyTebqZoELEPwcZRt6UyXmZpTX1rovL1GkkraVNH65/*)",
  "jackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "createdAt": "2026-07-03T08:53:36.000Z",
  "balanceSats": 99942
}

Deposit addresses

Private reseller deposit address allocation and lookup.

GET/api/resellers/{resellerJackpotAddress}/deposit-addressesList deposit addresses
Bearer API key

Lists deposit addresses for the path reseller. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

sortqueryrequired

Required depositAddressIdx order. Keep the same value when following nextCursor. Values: asc, desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor. Range: -1-2147483648.

limitqueryoptional

Maximum items to return. Defaults to 100. Range: 1-500.

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses?cursor=3899999&sort=asc'

Success response

200A page of deposit addresses.DepositAddressList
Checked-in example response
{
  "hasNext": false,
  "items": [
    {
      "id": "018f58d2-2800-4000-8000-000000000456",
      "depositAddress": "tb1p0e79zy0ejktxdd8ethstntwdaup303kjgmxvylge544fngzeqy6sskwy4w",
      "depositAddressIdx": 7,
      "createdAt": "2026-07-03T08:53:36.000Z",
      "metadata": {
        "customerId": "external-user-123"
      },
      "status": "funded",
      "depositCount": 1,
      "pendingSats": 0,
      "creditedSats": 99942
    }
  ],
  "nextCursor": "7"
}
POST/api/resellers/{resellerJackpotAddress}/deposit-addressesCreate deposit address
Bearer API key

Creates a new Bitcoin deposit address for the path reseller. Metadata is private to that reseller and free-form except the reserved autoTicket key, which makes the scanner spend every credited deposit into one ticket automatically.

Integration notes

Address creation is idempotent by request id. If an integration needs lookup by an order or customer key, derive the UUIDv4 id from a private HMAC of that key and replay the same POST body to recover the address. Reusing the same id with different metadata returns an error and does not allocate another address.

const id = uuidV4FromHmac(addressLookupSeed, `order:${orderId}`);
await createDepositAddress({
  id,
  metadata: { orderId }
});

Use a private HMAC seed, not a public hash, so another caller cannot reserve the same id first.

Auto-ticketing: metadata is free-form except the reserved autoTicket key (see the AutoTicketSettings schema). When present, Luckotto's scanner spends every deposit credited to this address into one ticket after the deposit has autoTicket.confirmationBlocks confirmations (published by /api/constants). Address creation rejects invalid autoTicket settings.

{
  "id": "018f58d2-2800-4000-8000-000000000458",
  "metadata": {
    "orderId": "order-789",
    "autoTicket": {
      "tiles": [
        3,
        7,
        12
      ],
      "ticketCreditFactor": 0.9,
      "ticketMetadata": {
        "orderId": "order-789"
      }
    }
  }
}

Each deposit outpoint makes exactly one ticket. tiles are locked in; the remaining tiles (and any excess unique tiles) are chosen pseudorandomly but deterministically per outpoint, so the same deposit always produces the same ticket. The spend budget is floor(creditedSats * ticketCreditFactor); the ticket amount is that budget minus the current ticketFeeSats, and the rest of the credit stays as spendable reseller balance. ticketMetadata is copied into the private ticket metadata, and the scanner records the funding breakdown under the ticket metadata's reserved autoTicket key.

The outcome lands on the deposit event stream and the per-outpoint deposit state: a ticketed event links the deposit to its ticketId (fetch it with GET /api/resellers/{resellerJackpotAddress}/tickets/{id}), while a terminal ticket_skipped event records amount_too_small or invalid_settings. Conversions that cannot run yet — sales window between rounds or insufficient balance — are retried on every following block, so a ticket can land in a later round than the deposit's confirmation.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

Request body

{
  "id": "018f58d2-2800-4000-8000-000000000457",
  "metadata": {
    "customerId": "new-customer-456",
    "source": "checkout"
  }
}

Example request

curl -i -X POST \
  -H 'Authorization: Bearer <reseller-api-key>' \
  -H 'Content-Type: application/json' \
  --data '{
  "id": "018f58d2-2800-4000-8000-000000000457",
  "metadata": {
    "customerId": "new-customer-456",
    "source": "checkout"
  }
}' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses'

Success response

200The created deposit address. Retrying an identical request with the same id returns the original address.DepositAddress
Checked-in example response
{
  "id": "018f58d2-2800-4000-8000-000000000457",
  "depositAddress": "tb1p5cyxnuxmeuwuvkwfem96lxxss9yq2w5g9p8j2x44e2hgm0q6spwq8n3m7r",
  "depositAddressIdx": 8,
  "createdAt": "2026-07-03T08:53:36.000Z",
  "metadata": {
    "customerId": "new-customer-456",
    "source": "checkout"
  },
  "status": "pending",
  "depositCount": 0,
  "pendingSats": 0,
  "creditedSats": 0
}
GET/api/resellers/{resellerJackpotAddress}/deposit-addresses/{id}Get deposit address by idempotency key
Bearer API key

Returns one private deposit address for the path reseller by UUIDv4 idempotency key, including the current reduced state of every deposit outpoint observed on it. Missing addresses and addresses owned by another reseller return 404.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

idpathrequired

Deposit address UUIDv4 idempotency key.

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses/018f58d2-2800-4000-8000-000000000456'

Success response

200The private reseller-owned deposit address with current per-outpoint deposit state.DepositAddressDetail
Checked-in example response
{
  "id": "018f58d2-2800-4000-8000-000000000456",
  "depositAddress": "tb1p0e79zy0ejktxdd8ethstntwdaup303kjgmxvylge544fngzeqy6sskwy4w",
  "depositAddressIdx": 7,
  "createdAt": "2026-07-03T08:53:36.000Z",
  "metadata": {
    "customerId": "external-user-123"
  },
  "status": "funded",
  "depositCount": 1,
  "pendingSats": 0,
  "creditedSats": 99942,
  "deposits": [
    {
      "depositId": "019f2730-3e38-7d2e-9204-f777ab4d0ab7",
      "txid": "5ea52c60b43aabe57c016460a995809d700621c169fcb7d9b6338546b4f46c55",
      "vout": 0,
      "amountSats": 100000,
      "chainStatus": "confirmed",
      "creditStatus": "credited",
      "depositFeeSats": 58,
      "creditedSats": 99942,
      "blockHeight": 142575,
      "blockHash": "00000000002fbae0663ebd7fecbdb19c4162410c7a69c6763f6cea6405d6332b",
      "blockTime": "2026-07-02T04:45:00.000Z",
      "txIndex": 1,
      "confirmationCount": 11,
      "requiredConfirmationCount": 2,
      "ticketId": null,
      "ticketSkipReason": null,
      "updatedAt": "2026-07-02T05:04:01.000Z"
    }
  ]
}

Deposits

Append-only deposit lifecycle and reversible accounting events.

GET/api/resellers/{resellerJackpotAddress}/deposit-eventsList deposit events
Bearer API key

Lists immutable deposit lifecycle and reversible deposit-only accounting events for the path reseller. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now. Use credited events as Luckotto balance-credit events, watch uncredited/reorged_out events as reversals, and apply your own confirmation threshold with /api/chain-tip before auto-ticketing.

Integration notes

Canonical consumption: request sort=asc, persist nextCursor after each processed page, and replay events in response order. For one customer deposit address, add depositAddress=<bitcoin-address>.

Shortcut: when you only need the latest per-outpoint state for one address (a checkout or order-status page), GET /api/resellers/{resellerJackpotAddress}/deposit-addresses/{id} already embeds it as deposits — no event reduction needed. Use the event stream when you need the audit history, cursor replay, or balance deltas.

Spendable reseller balance is a simple accounting reducer. Lifecycle events carry balanceDeltaSats=0; credited events add spendable balance; uncredited events remove a previous credit.

let availableDeltaSats = 0;

for (const event of events) {
  availableDeltaSats += event.balanceDeltaSats;
}

For customer or order status, reduce by deposit outpoint. Keep chain state and credit state separately; this mirrors Luckotto's current deposit view. For credited and uncredited events, the historical deposit fee can be derived as amountSats - abs(balanceDeltaSats).

type DepositState = {
  amountSats: number;
  blockHeight: number | null;
  chainStatus: "mempool" | "confirmed" | "dropped" | "reorged_out";
  creditedSats: number;
  creditStatus: "not_credited" | "credited" | "uncredited";
  depositAddress: string;
  depositFeeSats: number;
  ticketId: string | null;
  ticketSkipReason: string | null;
};

const depositsByOutpoint = new Map<string, DepositState>();

for (const event of events) {
  const outpoint = `${event.depositTxid}:${event.depositVout}`;
  const previous: DepositState = depositsByOutpoint.get(outpoint) ?? {
    amountSats: event.amountSats,
    blockHeight: null,
    chainStatus: "dropped",
    creditedSats: 0,
    creditStatus: "not_credited",
    depositAddress: event.depositAddress,
    depositFeeSats: 0,
    ticketId: null,
    ticketSkipReason: null
  };
  const next: DepositState = {
    ...previous,
    amountSats: event.amountSats,
    depositAddress: event.depositAddress
  };

  if (event.type === "mempool_seen") {
    next.chainStatus = "mempool";
    next.blockHeight = null;
  } else if (event.type === "confirmed") {
    next.chainStatus = "confirmed";
    next.blockHeight = event.blockHeight;
  } else if (event.type === "credited") {
    next.creditStatus = "credited";
    next.creditedSats = event.balanceDeltaSats;
    next.depositFeeSats = event.amountSats - Math.abs(event.balanceDeltaSats);
    next.blockHeight = event.confirmedBlockHeight ?? next.blockHeight;
  } else if (event.type === "uncredited") {
    next.creditStatus = "uncredited";
    next.creditedSats = 0;
    next.depositFeeSats = event.amountSats - Math.abs(event.balanceDeltaSats);
    next.blockHeight = event.confirmedBlockHeight ?? next.blockHeight;
  } else if (event.type === "mempool_dropped") {
    next.chainStatus = "dropped";
    next.blockHeight = null;
  } else if (event.type === "reorged_out") {
    next.chainStatus = "reorged_out";
    next.blockHeight = event.blockHeight;
  } else if (event.type === "ticketed") {
    next.ticketId = event.ticketId;
  } else if (event.type === "ticket_skipped") {
    next.ticketSkipReason = event.ticketSkipReason;
  }

  depositsByOutpoint.set(outpoint, next);
}

function checkoutStatus(deposit: DepositState): string {
  if (deposit.chainStatus === "mempool") return "unconfirmed";
  if (deposit.chainStatus === "dropped") return "dropped";
  if (deposit.chainStatus === "reorged_out") return "reorged_out";
  if (deposit.ticketId) return "ticketed";
  if (deposit.creditStatus === "credited") return "credited";
  return "confirmed";
}

For a checkout UI, show the latest status for each outpoint on the deposit address. Treat credited as spendable by Luckotto. If the vendor wants a deeper safety policy, compare the reduced blockHeight with /api/chain-tip.processedTipHeight before creating the ticket.

For addresses with autoTicket metadata, ticketed events link a deposit to the ticket the scanner bought from it; fetch ticket details with GET /api/resellers/{resellerJackpotAddress}/tickets/{ticketId}. A ticket_skipped event is terminal: the deposit's credit stays as ordinary spendable balance.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

sortqueryrequired

Required event id order. Keep the same value when following nextCursor. Values: asc, desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor.

limitqueryoptional

Maximum items to return. Defaults to 100. Range: 1-500.

depositAddressqueryoptional

Filter to one full deposit address.

depositTxidqueryoptional

Filter to one Bitcoin transaction id.

depositVoutqueryoptional

Filter to one Bitcoin transaction output index. Minimum: 0.

typequeryoptional

Filter to one deposit lifecycle or accounting event type. Values: mempool_seen, mempool_dropped, confirmed, reorged_out, credited, uncredited, ticketed, ticket_skipped

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-events?depositTxid=5ea52c60b43aabe57c016460a995809d700621c169fcb7d9b6338546b4f46c55&sort=asc'

Success response

200A cursor page of deposit events.DepositEventList
Checked-in example response
{
  "hasNext": false,
  "items": [
    {
      "id": "019f2730-3e38-7d2e-9204-f777ab4d0ab7",
      "type": "credited",
      "eventAt": "2026-07-02T05:04:01.000Z",
      "depositAddress": "tb1p0e79zy0ejktxdd8ethstntwdaup303kjgmxvylge544fngzeqy6sskwy4w",
      "depositAddressIdx": 7,
      "metadata": {
        "customerId": "external-user-123"
      },
      "depositTxid": "5ea52c60b43aabe57c016460a995809d700621c169fcb7d9b6338546b4f46c55",
      "depositVout": 0,
      "amountSats": 100000,
      "blockHeight": null,
      "blockHash": null,
      "blockTime": null,
      "txIndex": null,
      "confirmedBlockHeight": 142575,
      "confirmedBlockHash": "00000000002fbae0663ebd7fecbdb19c4162410c7a69c6763f6cea6405d6332b",
      "confirmedBlockTime": "2026-07-02T04:45:00.000Z",
      "confirmedTxIndex": 1,
      "balanceDeltaSats": 99942,
      "ticketId": null,
      "ticketSkipReason": null
    }
  ],
  "nextCursor": "019f2730-3e38-7d2e-9204-f777ab4d0ab7"
}

Tickets

Funded ticket creation, listing, and id lookup.

GET/api/ticketsList public tickets
Public

Lists public tickets across all resellers. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now. Optionally filter by resellerJackpotAddress, roundNumber, or both. Private fields such as metadata, ticket UUID, createdAt, and ticketFeeSats are never returned.

Parameters

sortqueryrequired

Required roundNumber:entryNumber order. Keep the same value when following nextCursor. Values: asc, desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor.

limitqueryoptional

Maximum items to return. Defaults to 100. Range: 1-500.

roundNumberqueryoptional

Filter to one round number. Minimum: 1.

resellerJackpotAddressqueryoptional

Filter to one full reseller jackpot address.

Example request

curl -i -X GET \
  'http://localhost:4000/api/tickets?cursor=3%3A9999&resellerJackpotAddress=tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk&roundNumber=3&sort=asc'

Success response

200A cursor page of public tickets.PublicTicketList
Checked-in example response
{
  "hasNext": false,
  "items": [
    {
      "entryNumber": 1,
      "resellerJackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
      "roundNumber": 3,
      "tiles": [
        1,
        7,
        12,
        18,
        24,
        31
      ],
      "weightSats": 1000
    }
  ],
  "nextCursor": "3:1"
}
GET/api/resellers/{resellerJackpotAddress}/ticketsList tickets
Bearer API key

Lists private tickets for the path reseller. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now. Use roundNumber to restrict reconciliation to one round.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

sortqueryrequired

Required roundNumber:entryNumber order. Keep the same value when following nextCursor. Values: asc, desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor.

limitqueryoptional

Maximum items to return. Defaults to 100. Range: 1-500.

roundNumberqueryoptional

Filter to one round number. Minimum: 1.

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets?cursor=3%3A9999&roundNumber=3&sort=asc'

Success response

200A cursor page of private reseller-owned tickets.TicketList
Checked-in example response
{
  "hasNext": false,
  "items": [
    {
      "id": "018f58d2-2800-4000-8000-000000000123",
      "entryNumber": 1,
      "resellerJackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
      "roundNumber": 3,
      "tiles": [
        1,
        7,
        12,
        18,
        24,
        31
      ],
      "weightSats": 1000,
      "createdAt": "2026-07-03T08:53:36.000Z",
      "metadata": {
        "customerId": "external-user-123",
        "orderId": "order-123"
      },
      "ticketFeeSats": 10
    }
  ],
  "nextCursor": "3:1"
}
GET/api/resellers/{resellerJackpotAddress}/tickets/{id}Get ticket by idempotency key
Bearer API key

Returns one private ticket for the path reseller by UUIDv4 idempotency key. Missing tickets and tickets owned by another reseller return 404.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

idpathrequired

Ticket UUIDv4 idempotency key.

Example request

curl -i -X GET \
  -H 'Authorization: Bearer <reseller-api-key>' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets/018f58d2-2800-4000-8000-000000000123'

Success response

200The private reseller-owned ticket.Ticket
SchemaTicket
Checked-in example response
{
  "id": "018f58d2-2800-4000-8000-000000000123",
  "entryNumber": 1,
  "resellerJackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "roundNumber": 3,
  "tiles": [
    1,
    7,
    12,
    18,
    24,
    31
  ],
  "weightSats": 1000,
  "createdAt": "2026-07-03T08:53:36.000Z",
  "metadata": {
    "customerId": "external-user-123",
    "orderId": "order-123"
  },
  "ticketFeeSats": 10
}
POST/api/resellers/{resellerJackpotAddress}/ticketsCreate ticket
Bearer API key

Buys one funded ticket for the path reseller in the current open round. amountSats becomes the public draw weight returned as weightSats.

Integration notes

Ticket creation is idempotent by request id. If an integration needs lookup by an order or customer ticket key, derive the UUIDv4 id from a private HMAC of that key, then replay the POST body or call this route's GET-by-id endpoint. Reusing the same id with a different reseller, amount, metadata, or tile set returns an error and does not create another ticket or spend balance again.

const id = uuidV4FromHmac(ticketLookupSeed, `ticket:${orderId}`);
await createTicket({
  id,
  metadata: { orderId },
  tiles,
  amountSats
});

Use a private HMAC seed, not a public hash, so another caller cannot reserve the same id first.

Parameters

resellerJackpotAddresspathrequired

Full reseller jackpot address. It must match the Bearer API key.

Request body

CreateTicketRequestapplication/json
{
  "id": "018f58d2-2800-4000-8000-000000000123",
  "metadata": {
    "customerId": "external-user-123",
    "orderId": "order-123"
  },
  "tiles": [
    1,
    7,
    12,
    18,
    24,
    31
  ],
  "amountSats": 1000
}

Example request

curl -i -X POST \
  -H 'Authorization: Bearer <reseller-api-key>' \
  -H 'Content-Type: application/json' \
  --data '{
  "id": "018f58d2-2800-4000-8000-000000000123",
  "metadata": {
    "customerId": "external-user-123",
    "orderId": "order-123"
  },
  "tiles": [
    1,
    7,
    12,
    18,
    24,
    31
  ],
  "amountSats": 1000
}' \
  'http://localhost:4000/api/resellers/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets'

Success response

200The created ticket. Retrying an identical request with the same id returns the original ticket.Ticket
SchemaTicket
Checked-in example response
{
  "id": "018f58d2-2800-4000-8000-000000000123",
  "entryNumber": 1,
  "resellerJackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "roundNumber": 3,
  "tiles": [
    1,
    7,
    12,
    18,
    24,
    31
  ],
  "weightSats": 1000,
  "createdAt": "2026-07-03T08:53:36.000Z",
  "metadata": {
    "customerId": "external-user-123",
    "orderId": "order-123"
  },
  "ticketFeeSats": 10
}
Shared

Error responses

API failures use one JSON envelope and that envelope is validated before the route returns it.

errorrequiredstring

Human-readable error message.

coderequiredstring

Stable machine-readable error code.

Rate limiting

HTTP 429 responses use this envelope with code: "rate_limited". Application throttles include Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset, where reset is a Unix timestamp in seconds. Wait at least Retry-After seconds when it is present; if an upstream throttle omits it, use exponential backoff. Public and reseller-key API routes currently publish no fixed quota.

Unknown API paths

Unknown /api/* paths return HTTP 404 with this JSON envelope and code: "not_found".

Error-code catalog

duplicate_ticketcodestable

The ticket id or public entry already exists.

forbiddencodestable

The authenticated caller cannot access this reseller or resource.

insufficient_balancecodestable

The reseller does not have enough spendable balance.

internal_errorcodestable

Luckotto failed unexpectedly while handling the request.

invalid_requestcodestable

The request path, query string, headers, or body are invalid.

invalid_tilescodestable

Ticket tiles are missing, duplicated, or out of range.

not_foundcodestable

The requested resource does not exist.

rate_limitedcodestable

The caller has sent too many requests; honor Retry-After when present.

round_closedcodestable

Ticket sales are closed for the current round.

unauthorizedcodestable

A required API key or session is missing or invalid.

Schemas

ErrorResponse

Stable API error envelope.

errorrequiredstring

Human-readable error message.

coderequiredstring

Stable machine-readable error code.

Metadata

Private reseller metadata.

AutoTicketSettings

Auto-ticketing settings inside deposit-address metadata. When metadata.autoTicket is present, the scanner spends every credited deposit to the address into one ticket once the deposit is autoTicket.confirmationBlocks deep (see /api/constants). Invalid settings are rejected at address creation.

tilesrequiredarray of integer

Tiles locked into the ticket, from zero up to all tilesPerTicket. Missing tiles are filled (and extra unique tiles trimmed) deterministically per deposit outpoint, so replays always produce the same ticket.

ticketCreditFactoroptionalnumber

Fraction of the credited deposit spent on the ticket, greater than 0 and at most 1. Defaults to 1. The unspent remainder stays as spendable reseller balance.

ticketMetadataoptionalobject

Private metadata copied onto every ticket this address creates. The scanner adds the funding breakdown under the ticket metadata's reserved autoTicket key.

Sats

Whole satoshis encoded as a JSON safe integer.

Count

Non-negative count.

ChainTip

Latest Bitcoin block height processed by Luckotto's scanner.

processedTipHeightrequiredinteger or null

Latest Bitcoin block height fully processed by Luckotto's scanner, or null before the first scan.

processedTipHashrequiredstring or null

Hash of the latest processed tip block, or null before the first scan.

processedAtrequiredstring or null

When Luckotto recorded this processed tip.

Constants

Public runtime constants used by API clients and workers.

bitcoinrequiredobject

Bitcoin network and explorer endpoint.

walletsrequiredobject

Public wallet metadata used for round verification.

feesrequiredobject

Current server-side fee settings.

lotteryrequiredobject

Luckotto ticket tile constants.

autoTicketrequiredobject

Auto-ticketing settings applied by the block scanner.

roundsrequiredobject

Runtime round timing constants.

drawrequiredobject

Draw replay constants.

databaserequiredobject

Database schema version information.

CreateDepositAddressRequest

Request body for creating one reseller deposit address.

idrequiredstring

Required UUIDv4 idempotency key. Retry with the exact same fields to replay the original result.

metadataoptionalobject

Private metadata attached to the new deposit address. Free-form, except the reserved autoTicket key which must match the AutoTicketSettings schema and turns on per-deposit auto-ticketing.

Reseller

Authenticated reseller account.

depositDescriptorrequiredstring

Originless output descriptor for deriving this reseller's Taproot receive addresses.

jackpotAddressrequiredstring

Fixed public Bitcoin payout address for this reseller's jackpot wins.

createdAtrequiredstring

Time the reseller account was created.

balanceSatsrequiredinteger

Spendable reseller balance available for new tickets.

PublicReseller

Public reseller details.

jackpotAddressrequiredstring

Fixed public Bitcoin payout address for this reseller's jackpot wins.

displayNamerequiredstring

Public reseller display name. Unverified public projections use the full jackpot address.

websiterequiredstring or null

Optional public reseller website. Unverified public projections return null.

verifiedAtrequiredstring or null

When this reseller was verified, or null if not verified.

PublicResellerList

Verified public reseller directory.

itemsrequiredarray of object

Verified resellers, sorted by display name and jackpot address. Unverified resellers are not listed.

DepositAddress

A reseller-owned Bitcoin deposit address.

idrequiredstring

Deposit address UUID. Reseller-supplied when id was provided.

depositAddressrequiredstring

Bitcoin deposit address. Send funds to this value.

depositAddressIdxrequiredinteger

Non-hardened receive index under the reseller deposit account.

createdAtrequiredstring

Time the deposit address was allocated.

metadatarequiredobject

Private reseller metadata attached to this address.

statusrequiredstring

Address funding state derived from confirmed and mempool deposits.

depositCountrequiredinteger

Number of deposits observed for this address.

pendingSatsrequiredinteger

Uncredited sats currently observed for this address.

creditedSatsrequiredinteger

Sats credited to the reseller balance from this address.

DepositAddressList

Deposit addresses owned by the authenticated reseller.

itemsrequiredarray of object

Items on this page.

nextCursorrequiredstring

Cursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.

hasNextrequiredboolean

Whether another page is available right now.

AddressDeposit

Current reduced state of one deposit outpoint observed on a deposit address. This is the latest lifecycle and credit state per outpoint; the deposit-events endpoint remains the append-only audit source.

depositIdrequiredstring

UUIDv7 id of the observed deposit output.

txidrequiredstring

Bitcoin transaction id containing the deposit output.

voutrequiredinteger

Transaction output index.

amountSatsrequiredinteger

Gross deposit output amount.

chainStatusrequiredstring

Latest chain lifecycle state for this outpoint.

creditStatusrequiredstring

Latest balance accounting state. credited means the deposit added spendable reseller balance; uncredited means a previous credit was reversed.

depositFeeSatsrequiredinteger or null

Deposit fee charged at credit time; null before crediting.

creditedSatsrequiredinteger or null

Sats credited to the reseller balance; null unless credited.

blockHeightrequiredinteger or null

Confirmation block height; null while unconfirmed.

blockHashrequiredstring or null

Confirmation block hash; null while unconfirmed.

blockTimerequiredstring or null

Confirmation block timestamp; null while unconfirmed.

txIndexrequiredinteger or null

Confirmation transaction index; null while unconfirmed.

confirmationCountrequiredinteger or null

Confirmations at the processed chain tip; null unless confirmed. Compare with requiredConfirmationCount before treating a deposit as final.

requiredConfirmationCountrequiredinteger or null

Confirmations Luckotto requires before crediting deposits.

ticketIdrequiredstring or null

Ticket the scanner created from this deposit under the address autoTicket settings; null when not (yet) auto-ticketed. Look the ticket up with GET /api/resellers/{resellerJackpotAddress}/tickets/{id}.

ticketSkipReasonrequiredstring or null

Terminal reason the scanner declined to auto-ticket this credited deposit; null otherwise.

updatedAtrequiredstring

Time of the latest lifecycle or accounting event.

DepositAddressDetail

A reseller-owned Bitcoin deposit address with the current reduced state of every observed deposit outpoint.

idrequiredstring

Deposit address UUID. Reseller-supplied when id was provided.

depositAddressrequiredstring

Bitcoin deposit address. Send funds to this value.

depositAddressIdxrequiredinteger

Non-hardened receive index under the reseller deposit account.

createdAtrequiredstring

Time the deposit address was allocated.

metadatarequiredobject

Private reseller metadata attached to this address.

statusrequiredstring

Address funding state derived from confirmed and mempool deposits.

depositCountrequiredinteger

Number of deposits observed for this address.

pendingSatsrequiredinteger

Uncredited sats currently observed for this address.

creditedSatsrequiredinteger

Sats credited to the reseller balance from this address.

depositsrequiredarray of object

Current per-outpoint deposit state, most recently updated first (up to 500 outpoints). Poll this instead of reducing the event stream when you only need the latest state for one address.

DepositEvent

One immutable deposit lifecycle or reversible deposit-only accounting event.

idrequiredstring

UUIDv7 event id. It appears as nextCursor in paginated responses.

typerequiredstring

Deposit lifecycle, deposit-only accounting, or auto-ticket outcome event type. credited adds spendable balance and uncredited removes a previous credit. ticketed and ticket_skipped record the terminal auto-ticket outcome for deposits to addresses with autoTicket metadata.

eventAtrequiredstring

Event time decoded from the UUIDv7 event id.

depositAddressrequiredstring

Deposit address that received this output.

depositAddressIdxrequiredinteger

Non-hardened receive index of the deposit address.

metadatarequiredobject

Private metadata from the receiving deposit address.

depositTxidrequiredstring

Bitcoin transaction id containing the deposit output.

depositVoutrequiredinteger

Transaction output index.

amountSatsrequiredinteger

Gross deposit output amount.

blockHeightrequiredinteger or null

Block height for confirmed and reorged_out lifecycle events; otherwise null.

blockHashrequiredstring or null

Block hash for confirmed and reorged_out lifecycle events; otherwise null.

blockTimerequiredstring or null

Block timestamp for confirmed and reorged_out lifecycle events; otherwise null.

txIndexrequiredinteger or null

Transaction index for confirmed and reorged_out lifecycle events; otherwise null.

confirmedBlockHeightrequiredinteger or null

Most recent confirmed block height for this deposit at or before this event.

confirmedBlockHashrequiredstring or null

Most recent confirmed block hash for this deposit at or before this event.

confirmedBlockTimerequiredstring or null

Most recent confirmed block timestamp for this deposit at or before this event.

confirmedTxIndexrequiredinteger or null

Most recent confirmed transaction index for this deposit at or before this event.

balanceDeltaSatsrequiredinteger

Signed reseller balance delta. Lifecycle and auto-ticket events use 0. Credited events add spendable balance; uncredited events remove a previous credit. The balance spent on an auto-ticket is on the linked ticket, not this event.

ticketIdrequiredstring or null

For ticketed events: the ticket the scanner created from this deposit. Null for every other event type.

ticketSkipReasonrequiredstring or null

For ticket_skipped events: why the scanner permanently declined to auto-ticket this credited deposit. Null for every other event type.

DepositEventList

Append-only deposit events visible to the authenticated reseller.

itemsrequiredarray of object

Items on this page.

nextCursorrequiredstring

Cursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.

hasNextrequiredboolean

Whether another page is available right now.

CreateTicketRequest

Request body for buying one funded Luckotto ticket.

idrequiredstring

Required UUIDv4 idempotency key. Retry with the exact same fields to replay the original result.

metadataoptionalobject

Private reseller metadata attached to the ticket.

tilesrequiredarray of integer

Six distinct Luckotto tile numbers for this ticket.

amountSatsrequiredinteger

Ticket draw weight and jackpot contribution.

Ticket

A funded Luckotto ticket with the public ticket fields plus reseller-private metadata.

idrequiredstring

Private UUIDv4 idempotency key for this ticket.

entryNumberrequiredinteger

Public entry number within the round.

resellerJackpotAddressrequiredstring

Full reseller jackpot address.

roundNumberrequiredinteger

Round number this ticket belongs to.

tilesrequiredarray of integer

Six distinct Luckotto tile numbers on the ticket.

weightSatsrequiredinteger

Ticket draw weight and jackpot contribution.

createdAtrequiredstring

Time the ticket was created.

metadatarequiredobject

Private reseller metadata attached to the ticket.

ticketFeeSatsrequiredinteger

Fee charged to create this ticket.

TicketList

Tickets owned by the authenticated reseller.

itemsrequiredarray of object

Items on this page.

nextCursorrequiredstring

Cursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.

hasNextrequiredboolean

Whether another page is available right now.

PublicTicket

A public funded Luckotto ticket.

entryNumberrequiredinteger

Public entry number within the round.

resellerJackpotAddressrequiredstring

Full reseller jackpot address.

roundNumberrequiredinteger

Round number this ticket belongs to.

tilesrequiredarray of integer

Six distinct Luckotto tile numbers on the ticket.

weightSatsrequiredinteger

Ticket draw weight and jackpot contribution.

PublicTicketList

Public Luckotto tickets.

itemsrequiredarray of object

Items on this page.

nextCursorrequiredstring

Cursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.

hasNextrequiredboolean

Whether another page is available right now.

Round

Public Luckotto round metadata and draw proof fields.

roundNumberrequiredinteger

Round number.

opensAtrequiredstring

When this round opened.

closesAtrequiredstring

When this round closes or closed for ticket sales.

soldTicketCountrequiredinteger

Number of funded tickets in the round.

jackpotSatsrequiredinteger

Total ticket draw weight and jackpot contribution.

roundCsvHmacrequiredstring or null

HMAC-SHA256 of the committed final round CSV keyed by roundSecretHash, or null until the round locks and seals its CSV.

roundSecretHashrequiredstring or null

SHA-256 of the round secret bytes and the roundCsvHmac key; published as soon as the round locks and seals its CSV.

roundSecretrequiredstring or null

Draw-sample HMAC key revealed after the round is resolved; its SHA-256 must equal roundSecretHash.

commitmentTxidrequiredstring or null

Bitcoin transaction id committing to roundCsvHmac, or null before broadcast.

commitmentBlockHeightrequiredinteger or null

Block height containing the commitment transaction, or null before confirmation.

drawBlockHeightrequiredinteger or null

Block height whose block hash starts the draw checkpoint chain.

drawBlockHashrequiredstring or null

Block hash used as the public seed for the draw checkpoint chain.

drawTilesrequiredarray of integer or null

Drawn Luckotto tiles in draw order (checkpoint n drew the nth tile), or null before resolution.

statusrequiredstring

Derived public round lifecycle state.

winningEntryNumberrequiredinteger or null

Winning public entry number, or null before resolution.

jackpotTxidrequiredstring or null

Bitcoin payout transaction id, or null before payout.

resolvedAtrequiredstring or null

When the round was resolved, or null before resolution.

RoundDetail

Public Luckotto round metadata and draw proof fields.

roundNumberrequiredinteger

Round number.

opensAtrequiredstring

When this round opened.

closesAtrequiredstring

When this round closes or closed for ticket sales.

soldTicketCountrequiredinteger

Number of funded tickets in the round.

jackpotSatsrequiredinteger

Total ticket draw weight and jackpot contribution.

roundCsvHmacrequiredstring or null

HMAC-SHA256 of the committed final round CSV keyed by roundSecretHash, or null until the round locks and seals its CSV.

roundSecretHashrequiredstring or null

SHA-256 of the round secret bytes and the roundCsvHmac key; published as soon as the round locks and seals its CSV.

roundSecretrequiredstring or null

Draw-sample HMAC key revealed after the round is resolved; its SHA-256 must equal roundSecretHash.

commitmentTxidrequiredstring or null

Bitcoin transaction id committing to roundCsvHmac, or null before broadcast.

commitmentBlockHeightrequiredinteger or null

Block height containing the commitment transaction, or null before confirmation.

drawBlockHeightrequiredinteger or null

Block height whose block hash starts the draw checkpoint chain.

drawBlockHashrequiredstring or null

Block hash used as the public seed for the draw checkpoint chain.

drawTilesrequiredarray of integer or null

Drawn Luckotto tiles in draw order (checkpoint n drew the nth tile), or null before resolution.

statusrequiredstring

Derived public round lifecycle state.

winningEntryNumberrequiredinteger or null

Winning public entry number, or null before resolution.

jackpotTxidrequiredstring or null

Bitcoin payout transaction id, or null before payout.

resolvedAtrequiredstring or null

When the round was resolved, or null before resolution.

drawCheckpointsrequiredarray of object

Stored public draw checkpoints in index order; unresolved rounds may return a partial list.

RoundList

Public Luckotto rounds.

itemsrequiredarray of object

Items on this page.

nextCursorrequiredstring

Cursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.

hasNextrequiredboolean

Whether another page is available right now.