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.
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 tipPublic
/api/chain-tipGet scanner chain tipReturns 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
GET/api/constantsGet API constantsPublic
/api/constantsGet API constantsReturns 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
{
"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 roundsPublic
/api/roundsList roundsLists 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
sortqueryrequiredRequired roundNumber order. Keep the same value when following nextCursor. Values: asc, desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor. Range: 0-2147483648.
limitqueryoptionalMaximum 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
{
"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 roundPublic
/api/rounds/{roundNumber}Get roundReturns one public Luckotto round with metadata and draw proof fields.
Parameters
roundNumberpathrequiredRound number. Range: 1-2147483647.
Example request
curl -i -X GET \ 'http://localhost:4000/api/rounds/3'
Success response
200The requested public round.RoundDetail
{
"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 resellersPublic
/api/resellersList verified resellersReturns 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
{
"items": [
{
"jackpotAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"displayName": "Example Reseller",
"website": "https://reseller.example",
"verifiedAt": "2026-07-03T09:00:00.000Z"
}
]
}GET/api/resellers/{resellerJackpotAddress}Get resellerBearer API key
/api/resellers/{resellerJackpotAddress}Get resellerReturns the path reseller's deposit descriptor, jackpot address, creation time, and spendable balance. The Bearer API key must belong to this reseller.
Parameters
resellerJackpotAddresspathrequiredFull 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
{
"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 addressesBearer API key
/api/resellers/{resellerJackpotAddress}/deposit-addressesList deposit addressesLists 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
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
sortqueryrequiredRequired depositAddressIdx order. Keep the same value when following nextCursor. Values: asc, desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor. Range: -1-2147483648.
limitqueryoptionalMaximum 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
{
"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 addressBearer API key
/api/resellers/{resellerJackpotAddress}/deposit-addressesCreate deposit addressCreates 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
resellerJackpotAddresspathrequiredFull 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
{
"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 keyBearer API key
/api/resellers/{resellerJackpotAddress}/deposit-addresses/{id}Get deposit address by idempotency keyReturns 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
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
idpathrequiredDeposit 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
{
"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 eventsBearer API key
/api/resellers/{resellerJackpotAddress}/deposit-eventsList deposit eventsLists 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
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
sortqueryrequiredRequired event id order. Keep the same value when following nextCursor. Values: asc, desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor.
limitqueryoptionalMaximum items to return. Defaults to 100. Range: 1-500.
depositAddressqueryoptionalFilter to one full deposit address.
depositTxidqueryoptionalFilter to one Bitcoin transaction id.
depositVoutqueryoptionalFilter to one Bitcoin transaction output index. Minimum: 0.
typequeryoptionalFilter 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
{
"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 ticketsPublic
/api/ticketsList public ticketsLists 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
sortqueryrequiredRequired roundNumber:entryNumber order. Keep the same value when following nextCursor. Values: asc, desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor.
limitqueryoptionalMaximum items to return. Defaults to 100. Range: 1-500.
roundNumberqueryoptionalFilter to one round number. Minimum: 1.
resellerJackpotAddressqueryoptionalFilter 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
{
"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 ticketsBearer API key
/api/resellers/{resellerJackpotAddress}/ticketsList ticketsLists 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
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
sortqueryrequiredRequired roundNumber:entryNumber order. Keep the same value when following nextCursor. Values: asc, desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor.
limitqueryoptionalMaximum items to return. Defaults to 100. Range: 1-500.
roundNumberqueryoptionalFilter 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
{
"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 keyBearer API key
/api/resellers/{resellerJackpotAddress}/tickets/{id}Get ticket by idempotency keyReturns one private ticket for the path reseller by UUIDv4 idempotency key. Missing tickets and tickets owned by another reseller return 404.
Parameters
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
idpathrequiredTicket 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
{
"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 ticketBearer API key
/api/resellers/{resellerJackpotAddress}/ticketsCreate ticketBuys 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
resellerJackpotAddresspathrequiredFull reseller jackpot address. It must match the Bearer API key.
Request body
{
"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
{
"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
}Error responses
API failures use one JSON envelope and that envelope is validated before the route returns it.
errorrequiredstringHuman-readable error message.
coderequiredstringStable 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_ticketcodestableThe ticket id or public entry already exists.
forbiddencodestableThe authenticated caller cannot access this reseller or resource.
insufficient_balancecodestableThe reseller does not have enough spendable balance.
internal_errorcodestableLuckotto failed unexpectedly while handling the request.
invalid_requestcodestableThe request path, query string, headers, or body are invalid.
invalid_tilescodestableTicket tiles are missing, duplicated, or out of range.
not_foundcodestableThe requested resource does not exist.
rate_limitedcodestableThe caller has sent too many requests; honor Retry-After when present.
round_closedcodestableTicket sales are closed for the current round.
unauthorizedcodestableA required API key or session is missing or invalid.
Schemas
Stable API error envelope.
errorrequiredstringHuman-readable error message.
coderequiredstringStable machine-readable error code.
Private reseller metadata.
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 integerTiles 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.
ticketCreditFactoroptionalnumberFraction 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.
ticketMetadataoptionalobjectPrivate metadata copied onto every ticket this address creates. The scanner adds the funding breakdown under the ticket metadata's reserved autoTicket key.
Whole satoshis encoded as a JSON safe integer.
Non-negative count.
Latest Bitcoin block height processed by Luckotto's scanner.
processedTipHeightrequiredinteger or nullLatest Bitcoin block height fully processed by Luckotto's scanner, or null before the first scan.
processedTipHashrequiredstring or nullHash of the latest processed tip block, or null before the first scan.
processedAtrequiredstring or nullWhen Luckotto recorded this processed tip.
Public runtime constants used by API clients and workers.
bitcoinrequiredobjectBitcoin network and explorer endpoint.
walletsrequiredobjectPublic wallet metadata used for round verification.
feesrequiredobjectCurrent server-side fee settings.
lotteryrequiredobjectLuckotto ticket tile constants.
autoTicketrequiredobjectAuto-ticketing settings applied by the block scanner.
roundsrequiredobjectRuntime round timing constants.
drawrequiredobjectDraw replay constants.
databaserequiredobjectDatabase schema version information.
Request body for creating one reseller deposit address.
idrequiredstringRequired UUIDv4 idempotency key. Retry with the exact same fields to replay the original result.
metadataoptionalobjectPrivate 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.
Authenticated reseller account.
depositDescriptorrequiredstringOriginless output descriptor for deriving this reseller's Taproot receive addresses.
jackpotAddressrequiredstringFixed public Bitcoin payout address for this reseller's jackpot wins.
createdAtrequiredstringTime the reseller account was created.
balanceSatsrequiredintegerSpendable reseller balance available for new tickets.
Public reseller details.
jackpotAddressrequiredstringFixed public Bitcoin payout address for this reseller's jackpot wins.
displayNamerequiredstringPublic reseller display name. Unverified public projections use the full jackpot address.
websiterequiredstring or nullOptional public reseller website. Unverified public projections return null.
verifiedAtrequiredstring or nullWhen this reseller was verified, or null if not verified.
Verified public reseller directory.
itemsrequiredarray of objectVerified resellers, sorted by display name and jackpot address. Unverified resellers are not listed.
A reseller-owned Bitcoin deposit address.
idrequiredstringDeposit address UUID. Reseller-supplied when id was provided.
depositAddressrequiredstringBitcoin deposit address. Send funds to this value.
depositAddressIdxrequiredintegerNon-hardened receive index under the reseller deposit account.
createdAtrequiredstringTime the deposit address was allocated.
metadatarequiredobjectPrivate reseller metadata attached to this address.
statusrequiredstringAddress funding state derived from confirmed and mempool deposits.
depositCountrequiredintegerNumber of deposits observed for this address.
pendingSatsrequiredintegerUncredited sats currently observed for this address.
creditedSatsrequiredintegerSats credited to the reseller balance from this address.
Deposit addresses owned by the authenticated reseller.
itemsrequiredarray of objectItems on this page.
nextCursorrequiredstringCursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.
hasNextrequiredbooleanWhether another page is available right now.
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.
depositIdrequiredstringUUIDv7 id of the observed deposit output.
txidrequiredstringBitcoin transaction id containing the deposit output.
voutrequiredintegerTransaction output index.
amountSatsrequiredintegerGross deposit output amount.
chainStatusrequiredstringLatest chain lifecycle state for this outpoint.
creditStatusrequiredstringLatest balance accounting state. credited means the deposit added spendable reseller balance; uncredited means a previous credit was reversed.
depositFeeSatsrequiredinteger or nullDeposit fee charged at credit time; null before crediting.
creditedSatsrequiredinteger or nullSats credited to the reseller balance; null unless credited.
blockHeightrequiredinteger or nullConfirmation block height; null while unconfirmed.
blockHashrequiredstring or nullConfirmation block hash; null while unconfirmed.
blockTimerequiredstring or nullConfirmation block timestamp; null while unconfirmed.
txIndexrequiredinteger or nullConfirmation transaction index; null while unconfirmed.
confirmationCountrequiredinteger or nullConfirmations at the processed chain tip; null unless confirmed. Compare with requiredConfirmationCount before treating a deposit as final.
requiredConfirmationCountrequiredinteger or nullConfirmations Luckotto requires before crediting deposits.
ticketIdrequiredstring or nullTicket 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 nullTerminal reason the scanner declined to auto-ticket this credited deposit; null otherwise.
updatedAtrequiredstringTime of the latest lifecycle or accounting event.
A reseller-owned Bitcoin deposit address with the current reduced state of every observed deposit outpoint.
idrequiredstringDeposit address UUID. Reseller-supplied when id was provided.
depositAddressrequiredstringBitcoin deposit address. Send funds to this value.
depositAddressIdxrequiredintegerNon-hardened receive index under the reseller deposit account.
createdAtrequiredstringTime the deposit address was allocated.
metadatarequiredobjectPrivate reseller metadata attached to this address.
statusrequiredstringAddress funding state derived from confirmed and mempool deposits.
depositCountrequiredintegerNumber of deposits observed for this address.
pendingSatsrequiredintegerUncredited sats currently observed for this address.
creditedSatsrequiredintegerSats credited to the reseller balance from this address.
depositsrequiredarray of objectCurrent 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.
One immutable deposit lifecycle or reversible deposit-only accounting event.
idrequiredstringUUIDv7 event id. It appears as nextCursor in paginated responses.
typerequiredstringDeposit 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.
eventAtrequiredstringEvent time decoded from the UUIDv7 event id.
depositAddressrequiredstringDeposit address that received this output.
depositAddressIdxrequiredintegerNon-hardened receive index of the deposit address.
metadatarequiredobjectPrivate metadata from the receiving deposit address.
depositTxidrequiredstringBitcoin transaction id containing the deposit output.
depositVoutrequiredintegerTransaction output index.
amountSatsrequiredintegerGross deposit output amount.
blockHeightrequiredinteger or nullBlock height for confirmed and reorged_out lifecycle events; otherwise null.
blockHashrequiredstring or nullBlock hash for confirmed and reorged_out lifecycle events; otherwise null.
blockTimerequiredstring or nullBlock timestamp for confirmed and reorged_out lifecycle events; otherwise null.
txIndexrequiredinteger or nullTransaction index for confirmed and reorged_out lifecycle events; otherwise null.
confirmedBlockHeightrequiredinteger or nullMost recent confirmed block height for this deposit at or before this event.
confirmedBlockHashrequiredstring or nullMost recent confirmed block hash for this deposit at or before this event.
confirmedBlockTimerequiredstring or nullMost recent confirmed block timestamp for this deposit at or before this event.
confirmedTxIndexrequiredinteger or nullMost recent confirmed transaction index for this deposit at or before this event.
balanceDeltaSatsrequiredintegerSigned 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 nullFor ticketed events: the ticket the scanner created from this deposit. Null for every other event type.
ticketSkipReasonrequiredstring or nullFor ticket_skipped events: why the scanner permanently declined to auto-ticket this credited deposit. Null for every other event type.
Append-only deposit events visible to the authenticated reseller.
itemsrequiredarray of objectItems on this page.
nextCursorrequiredstringCursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.
hasNextrequiredbooleanWhether another page is available right now.
Request body for buying one funded Luckotto ticket.
idrequiredstringRequired UUIDv4 idempotency key. Retry with the exact same fields to replay the original result.
metadataoptionalobjectPrivate reseller metadata attached to the ticket.
tilesrequiredarray of integerSix distinct Luckotto tile numbers for this ticket.
amountSatsrequiredintegerTicket draw weight and jackpot contribution.
A funded Luckotto ticket with the public ticket fields plus reseller-private metadata.
idrequiredstringPrivate UUIDv4 idempotency key for this ticket.
entryNumberrequiredintegerPublic entry number within the round.
resellerJackpotAddressrequiredstringFull reseller jackpot address.
roundNumberrequiredintegerRound number this ticket belongs to.
tilesrequiredarray of integerSix distinct Luckotto tile numbers on the ticket.
weightSatsrequiredintegerTicket draw weight and jackpot contribution.
createdAtrequiredstringTime the ticket was created.
metadatarequiredobjectPrivate reseller metadata attached to the ticket.
ticketFeeSatsrequiredintegerFee charged to create this ticket.
Tickets owned by the authenticated reseller.
itemsrequiredarray of objectItems on this page.
nextCursorrequiredstringCursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.
hasNextrequiredbooleanWhether another page is available right now.
A public funded Luckotto ticket.
entryNumberrequiredintegerPublic entry number within the round.
resellerJackpotAddressrequiredstringFull reseller jackpot address.
roundNumberrequiredintegerRound number this ticket belongs to.
tilesrequiredarray of integerSix distinct Luckotto tile numbers on the ticket.
weightSatsrequiredintegerTicket draw weight and jackpot contribution.
Public Luckotto tickets.
itemsrequiredarray of objectItems on this page.
nextCursorrequiredstringCursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.
hasNextrequiredbooleanWhether another page is available right now.
Public Luckotto round metadata and draw proof fields.
roundNumberrequiredintegerRound number.
opensAtrequiredstringWhen this round opened.
closesAtrequiredstringWhen this round closes or closed for ticket sales.
soldTicketCountrequiredintegerNumber of funded tickets in the round.
jackpotSatsrequiredintegerTotal ticket draw weight and jackpot contribution.
roundCsvHmacrequiredstring or nullHMAC-SHA256 of the committed final round CSV keyed by roundSecretHash, or null until the round locks and seals its CSV.
roundSecretHashrequiredstring or nullSHA-256 of the round secret bytes and the roundCsvHmac key; published as soon as the round locks and seals its CSV.
roundSecretrequiredstring or nullDraw-sample HMAC key revealed after the round is resolved; its SHA-256 must equal roundSecretHash.
commitmentTxidrequiredstring or nullBitcoin transaction id committing to roundCsvHmac, or null before broadcast.
commitmentBlockHeightrequiredinteger or nullBlock height containing the commitment transaction, or null before confirmation.
drawBlockHeightrequiredinteger or nullBlock height whose block hash starts the draw checkpoint chain.
drawBlockHashrequiredstring or nullBlock hash used as the public seed for the draw checkpoint chain.
drawTilesrequiredarray of integer or nullDrawn Luckotto tiles in draw order (checkpoint n drew the nth tile), or null before resolution.
statusrequiredstringDerived public round lifecycle state.
winningEntryNumberrequiredinteger or nullWinning public entry number, or null before resolution.
jackpotTxidrequiredstring or nullBitcoin payout transaction id, or null before payout.
resolvedAtrequiredstring or nullWhen the round was resolved, or null before resolution.
Public Luckotto round metadata and draw proof fields.
roundNumberrequiredintegerRound number.
opensAtrequiredstringWhen this round opened.
closesAtrequiredstringWhen this round closes or closed for ticket sales.
soldTicketCountrequiredintegerNumber of funded tickets in the round.
jackpotSatsrequiredintegerTotal ticket draw weight and jackpot contribution.
roundCsvHmacrequiredstring or nullHMAC-SHA256 of the committed final round CSV keyed by roundSecretHash, or null until the round locks and seals its CSV.
roundSecretHashrequiredstring or nullSHA-256 of the round secret bytes and the roundCsvHmac key; published as soon as the round locks and seals its CSV.
roundSecretrequiredstring or nullDraw-sample HMAC key revealed after the round is resolved; its SHA-256 must equal roundSecretHash.
commitmentTxidrequiredstring or nullBitcoin transaction id committing to roundCsvHmac, or null before broadcast.
commitmentBlockHeightrequiredinteger or nullBlock height containing the commitment transaction, or null before confirmation.
drawBlockHeightrequiredinteger or nullBlock height whose block hash starts the draw checkpoint chain.
drawBlockHashrequiredstring or nullBlock hash used as the public seed for the draw checkpoint chain.
drawTilesrequiredarray of integer or nullDrawn Luckotto tiles in draw order (checkpoint n drew the nth tile), or null before resolution.
statusrequiredstringDerived public round lifecycle state.
winningEntryNumberrequiredinteger or nullWinning public entry number, or null before resolution.
jackpotTxidrequiredstring or nullBitcoin payout transaction id, or null before payout.
resolvedAtrequiredstring or nullWhen the round was resolved, or null before resolution.
drawCheckpointsrequiredarray of objectStored public draw checkpoints in index order; unresolved rounds may return a partial list.
Public Luckotto rounds.
itemsrequiredarray of objectItems on this page.
nextCursorrequiredstringCursor to pass as the next request's cursor query parameter. It remains useful when hasNext is false so clients can poll later.
hasNextrequiredbooleanWhether another page is available right now.