Luckotto API
Sell funded Luckotto tickets from your own product: fund a partner balance with Bitcoin, create tickets over JSON, and follow every round through to a publicly verifiable draw.
The examples and the OpenAPI document are generated from the same contracts the live API validates every request and response against.
https://luckotto.com/apiBitcoin mainnet, real fundshttp://localhost:4000/apiDevelopment and testingFill in your integration
Every example below uses these values and the local base URL http://localhost:4000/api. The address and environment are saved in this browser; the API key stays in memory and leaves this page only inside requests you copy or send yourself.
From API key to a sold ticket
Five requests take a new partner from nothing to a funded ticket a customer can verify.
Create an API key and check the connection
GET/api/partners/{partnerPayoutAddress}Create the key in the partner dashboard under Settings, store it server-side, and confirm it works. The response includes balanceSats, the available balance every purchase debits.
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk'
200 Example response
{
"payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"desiredPrizeSats": 100000000,
"createdAt": "2026-07-03T08:53:36.000Z",
"balanceSats": 99942
}Create a deposit address
POST/api/partners/{partnerPayoutAddress}/deposit-addressesGenerate one UUIDv4 as the address id and keep it: it is the idempotency key, so a retried request returns the same address instead of allocating a second one. Metadata is private and free-form.
curl -i -X POST \
-H 'Authorization: Bearer <partner-api-key>' \
-H 'Content-Type: application/json' \
--data '{
"id": "018f58d2-2800-4000-8000-000000000457",
"metadata": {
"customerId": "new-customer-456",
"source": "checkout"
},
"autoTicket": {
"expression": "net_amount * 0.75",
"metadata": {
"source": "automatic-deposit"
}
}
}' \
'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses'200 Example response
{
"id": "018f58d2-2800-4000-8000-000000000457",
"depositAddress": "tb1ptsps7ch37fe5mjs0mm2kl6043uvgsfvkgwk09742xuhxhvh2kxaqp5ggpz",
"bip32Path": "/459777854/157137149/1752886951/2034487076/377163367",
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"customerId": "new-customer-456",
"source": "checkout"
},
"autoTicket": {
"expression": "net_amount * 0.75",
"metadata": {
"source": "automatic-deposit"
}
},
"status": "pending",
"depositCount": 0,
"pendingSats": 0,
"creditedSats": 0
}Fund the address and wait for the credit
GET/api/partners/{partnerPayoutAddress}/deposit-addresses/{id}Send Bitcoin to depositAddress from any wallet on the configured network. Poll this lookup until status is funded: the deposit confirms, the deposit fee is taken, and creditedSats moves into the partner balance from step 1. The required confirmation count comes from /api/constants.
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses/018f58d2-2800-4000-8000-000000000456'
200 Example response
{
"id": "018f58d2-2800-4000-8000-000000000456",
"depositAddress": "tb1pafjzr7fs3tyal6l3l6ad5784fzmafvshrg7q4u535fjewhl9utvsn65th3",
"bip32Path": "/130014968/456224106/1083178501/182488015/337931103",
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"customerId": "external-user-123"
},
"autoTicket": {
"expression": "net_amount * 0.5",
"metadata": {
"source": "automatic-deposit"
}
},
"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": 3,
"autoTicketResult": {
"status": "created",
"allocatedSats": 49971,
"balanceRemainderSats": 49971,
"ticketId": "018f58d2-2800-4000-8000-000000000123"
},
"updatedAt": "2026-07-02T05:04:01.000Z"
}
]
}Buy a ticket
POST/api/partners/{partnerPayoutAddress}/ticketsGenerate one UUIDv4 per sale and reuse it on retries — the same id with the same contribution and metadata replays the original ticket with the same 200 response and never debits twice. The debit is exactly contributedSats plus the current ticket fee. Round rollover is atomic: the request always resolves against a round accepting ticket sales, and a purchase may perform the rollover itself. Send expectedTicketFeeSats and expectedRoundNumber when the purchase must use the fee and accepting round you inspected; a mismatch rejects a new purchase without a debit. A retry of an existing ticket returns the original receipt regardless of those expected values.
curl -i -X POST \
-H 'Authorization: Bearer <partner-api-key>' \
-H 'Content-Type: application/json' \
--data '{
"id": "018f58d2-2800-4000-8000-000000000123",
"metadata": {
"displayReference": "ticket-123"
},
"contributedSats": 1000,
"expectedRoundNumber": 3,
"expectedTicketFeeSats": 100
}' \
'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets'200 Example response
{
"id": "018f58d2-2800-4000-8000-000000000123",
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000,
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"displayReference": "ticket-123"
},
"ticketFeeSats": 100,
"autoTicketFunding": null
}Hand the receipt to your customer
GET/api/tickets/{id}The ticket id is public. Link your customer to /tickets/{id} on the Luckotto site, or read the same data from the public API without any key.
curl -i \ 'http://localhost:4000/api/tickets/018f58d2-2800-4000-8000-000000000123'
200 Example response
{
"id": "018f58d2-2800-4000-8000-000000000123",
"metadata": {
"displayReference": "ticket-123"
},
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000
}Partner API keys
Bearer header
Operations marked Bearer API key require Authorization: Bearer <partner-api-key> on every request. Public operations take no credentials at all.
Where keys come from
Keys are created and revoked in the partner dashboard settings, not through this API. A partner can hold several active keys at once, so rotation is: create a new key, move traffic, revoke the old one.
One partner per key
Every partner route names the partner in its path. The partnerPayoutAddress path segment must be the partner the key belongs to: a missing or unknown key returns 401 unauthorized, while a valid key used on another partner's path returns 403 forbidden.
Handling
Call the partner API from your servers only. A key in browser JavaScript, a mobile app, or a URL is public; treat it as leaked and revoke it.
How every operation behaves
JSON only
Requests with bodies must send Content-Type: application/json. Responses are JSON with Cache-Control: no-store, and CORS is open, so public data can be read directly from a browser.
Amounts
Every amount is whole satoshis encoded as a JSON safe integer. There are no decimal BTC strings anywhere in the API.
Idempotent creation
Both POST operations take a client-generated UUIDv4 id. Deposit-address retries require the original fields. Ticket retries require the original partner, contribution, and metadata; round and fee expectations apply only to new tickets. A valid replay returns the original resource with the same 200 status, while changing an immutable field fails with duplicate_ticket. Generate the id before the first attempt and persist it with your order.
Runtime constants
Fees, confirmation thresholds, and round timing are live settings. Read them from /api/constants in the environment you target; the checked-in examples may show faster local testnet values and must not be copied as production policy.
Compatibility
New response fields may be added over time; ignore fields you do not recognize. Error codes, enum values, and existing field meanings are stable.
Auto-ticket expressions
An auto-ticket deposit address stores one immutable formula that turns each confirmed deposit into a ticket budget. The result includes the ticket fee and is floored and clamped to the available net deposit.
Open the expression reference and calculatorPagination and polling
List operations share one cursor scheme, and it doubles as the change feed: there are no webhooks, so integrations poll with a stored cursor.
Reading a list
Pass sort=asc or sort=desc — it is required — and optionally limit (1 through 500, default 100). Take nextCursor from the response and send it back as cursor with the same sort until hasNext is false.
curl -i 'http://localhost:4000/api/rounds?sort=asc&limit=100'
# → { "items": [...], "nextCursor": "3", "hasNext": false }
curl -i 'http://localhost:4000/api/rounds?sort=asc&limit=100&cursor=3'Tail polling
nextCursor stays meaningful when hasNext is false. Persist it after processing a page, then repeat the same request later: anything new appears after the stored cursor exactly once. This is the supported way to watch for new rounds, tickets, and deposit events.
Reconciling deposits
For accounting, follow /api/partners/{partnerPayoutAddress}/deposit-events with sort=asc and a stored cursor. Treat credited as adding balance and uncredited as reversing that credit, and apply your own confirmation threshold by comparing confirmedBlockHeight against /api/chain-tip before treating a payment as final.
Round lifecycle
status on every round response tells you which draw proof fields are populated and what your integration should do. Rounds close weekly; a round with no funded tickets never closes — closesAt simply moves forward.
Populated: closesAt, ticketFundedPotSats; Luckotto-provided prizeBankrollSats may still be null while the previous round settles, and its final value freezes at close
Sell tickets. Every ticket lands in the currently open round.
Populated: roundManifestHash, roundSecretHash, vdfIterations, then commitmentTxid, commitmentBlockHeight, drawBlockHeight, drawBlockHash
Sales have already moved atomically to the successor round. Anyone can compute and submit the one-shot VDF for this round's draw block.
Populated: delayedDrawSeed, vdfSolutionAcceptedAt, roundSecretRevealAt
A canonical VDF solution is accepted. Wait out the fixed reveal delay.
Populated: roundSecret, outcome (won or noWinner), winningPartnerPayoutAddress, payoutTxid, resolvedAt; the round detail adds winningPrizeShortfallSats and prizeSats
Read the result. Every input needed to replay the draw is now public.
Round manifest and headers
GET /rounds/{roundNumber}/manifest.json serves the committed round manifest — the file whose tagged SHA-256 is the round commitment. Once the round seals, responses carry X-Luckotto-Round-Manifest-Status: final (the bytes are final, not the draw) and X-Luckotto-Round-Manifest-SHA256. After resolution the same response adds X-Luckotto-Round-Secret. Before the seal, the route returns an unavailable page and no status header.
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 \ 'http://localhost:4000/api/chain-tip'
http://localhost:4000/api/chain-tipResponse
{
"processedTipHeight": 142586,
"processedTipHash": "00000000002a5971e6f74a6ba531ed1f14830d8b59a38814e02756f7c6e5f08f",
"processedAt": "2026-07-02T05:04:01.000Z"
}Errors
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/constantsGet API constantsPublic
/api/constantsGet API constantsReturns public project and runtime constants used by API clients and workers, including the deposit account xpub, fixed prize shortfall cap, draw-allocation scale, current fees, 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 \ 'http://localhost:4000/api/constants'
http://localhost:4000/api/constantsResponse
{
"bitcoin": {
"network": "testnet4"
},
"wallets": {
"deposit": {
"xpub": "tpubDDWQBDzrBZFbAHVbgXywi8tB8QLzvDmRYC9vd24rsnDZ3EwdWdgKd8AsMHRAQNTmmyqRkvNGgy8HTdsTr8T7LnVbMzasQNoa511EbDWeBNk"
},
"commitment": {
"xpub": "tpubDDG8vJgmngBej3WYjjomDbJkb5kmpiFbQbGeb5m4pnKrT4pv7U7kzwmMfSCPaiJ8ZuJdxFTgPungFZ9gjkLU98ruqahEYmUu68WPizuo9s1"
}
},
"fees": {
"depositFeeSats": 58,
"ticketFeeSats": 100
},
"lottery": {
"drawAllocationScale": 4294967296,
"maxPrizeShortfallFactor": 0.5
},
"rounds": {
"drawDelayBlocks": 3,
"sufficientConfirmationBlocks": 1,
"autoTicketConfirmationBlocks": 3,
"minPayoutConfirmations": 100
},
"draw": {
"minimumIterations": 1,
"construction": "class-group-repeated-squaring",
"proof": "nested-wesolowski",
"discriminantBits": 1024,
"revealDelaySeconds": 1800,
"startingElement": "08",
"seedEncoding": "LUCKOTTO-VDF-SEED-1",
"proofEncoding": "LUCKOTTO-CHIAVDF-NWESO-1",
"outputExtractor": "tagged-SHA-256",
"sampleExpansion": "SHA-256"
}
}Errors
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Rounds
Public round metadata, participation breakdowns, 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. One of: 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 \ 'http://localhost:4000/api/rounds?cursor=1&sort=asc'
http://localhost:4000/api/rounds?cursor=1&sort=ascResponse
{
"hasNext": false,
"items": [
{
"roundNumber": 3,
"opensAt": "2026-07-01T00:00:00.000Z",
"closesAt": "2026-07-02T00:00:00.000Z",
"soldTicketCount": 1,
"ticketFundedPotSats": 1000,
"prizeBankrollSats": 100000000,
"roundManifestHash": null,
"roundSecretHash": null,
"roundSecret": null,
"commitmentTxid": null,
"commitmentBlockHeight": null,
"drawBlockHeight": null,
"drawBlockHash": null,
"vdfIterations": null,
"delayedDrawSeed": null,
"drawExtensionSeed": null,
"vdfSolutionAcceptedAt": null,
"roundSecretRevealAt": null,
"status": "open",
"winningPartnerPayoutAddress": null,
"outcome": null,
"payoutTxid": null,
"resolvedAt": null
}
],
"nextCursor": "3"
}Errors
invalid_request400sort is missing or invalid, cursor is malformed, limit is outside 1-500, or an unsupported query parameter is present.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
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 \ 'http://localhost:4000/api/rounds/3'
http://localhost:4000/api/rounds/3Response
{
"roundNumber": 3,
"opensAt": "2026-07-01T00:00:00.000Z",
"closesAt": "2026-07-02T00:00:00.000Z",
"soldTicketCount": 1,
"ticketFundedPotSats": 1000,
"prizeBankrollSats": 100000000,
"roundManifestHash": null,
"roundSecretHash": null,
"roundSecret": null,
"commitmentTxid": null,
"commitmentBlockHeight": null,
"drawBlockHeight": null,
"drawBlockHash": null,
"vdfIterations": null,
"delayedDrawSeed": null,
"drawExtensionSeed": null,
"vdfSolutionAcceptedAt": null,
"roundSecretRevealAt": null,
"status": "open",
"winningPartnerPayoutAddress": null,
"outcome": null,
"payoutTxid": null,
"resolvedAt": null,
"winningPrizeShortfallSats": null,
"prizeSats": null
}Errors
invalid_request400roundNumber is not a positive integer.
not_found404The round does not exist.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/rounds/{roundNumber}/partnersList round partnersPublic
/api/rounds/{roundNumber}/partnersList round partnersLists every partner that bought funded tickets in the round, with its aggregate contribution, ticket count, desired prize, and potential prize.
Parameters
roundNumberpathrequiredRound number. Range: 1-2147483647.
Example request
curl -i \ 'http://localhost:4000/api/rounds/3/partners'
http://localhost:4000/api/rounds/3/partnersResponse
{
"items": [
{
"roundNumber": 3,
"payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"displayName": "Example Partner",
"soldTicketCount": 1,
"ticketFundedPotSats": 1000,
"desiredPrizeSats": 1000,
"prizeIfWonSats": 1000
}
]
}Errors
invalid_request400roundNumber is not a positive integer.
not_found404The round does not exist.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/rounds/{roundNumber}/partners/{partnerPayoutAddress}Get round partnerPublic
/api/rounds/{roundNumber}/partners/{partnerPayoutAddress}Get round partnerReturns one participating partner's aggregate contribution, ticket count, desired prize, and potential prize in the round.
Parameters
roundNumberpathrequiredRound number. Range: 1-2147483647.
partnerPayoutAddresspathrequiredFull participating-partner Luckotto payout address. 14-128 characters.
Example request
curl -i \ 'http://localhost:4000/api/rounds/3/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk'
http://localhost:4000/api/rounds/3/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tkResponse
{
"roundNumber": 3,
"payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"displayName": "Example Partner",
"soldTicketCount": 1,
"ticketFundedPotSats": 1000,
"desiredPrizeSats": 1000,
"prizeIfWonSats": 1000
}Errors
invalid_request400roundNumber is not a positive integer.
not_found404The round does not exist or the partner did not participate in it.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/rounds/{roundNumber}/vdfGet the canonical VDF solutionPublic
/api/rounds/{roundNumber}/vdfGet the canonical VDF solutionReturns the round manifest hash, current draw block, and verified one-shot VDF solution. Superseded solutions remain in the internal append-only database history but are not publicly exposed.
Parameters
roundNumberpathrequiredRound number. Range: 1-2147483647.
Example request
curl -i \ 'http://localhost:4000/api/rounds/3/vdf'
http://localhost:4000/api/rounds/3/vdfResponse
{
"roundNumber": 3,
"canonicalDrawBlockHash": null,
"roundManifestHash": null,
"vdfIterations": null,
"solutions": []
}Errors
invalid_request400roundNumber is not a positive integer.
not_found404The round does not exist.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
POST/api/rounds/{roundNumber}/vdfSubmit a VDF solutionPublic
/api/rounds/{roundNumber}/vdfSubmit a VDF solutionPublicly races one strictly verified solution for the round manifest hash, draw block, and manifest iteration count. Core verifies the LUCKOTTO-1 Chia proof, hashes the canonical output, and records its own acceptance time and solve duration. Public proof verification is serialized across the service, so concurrent attempts receive HTTP 429 instead of waiting; a valid partner Bearer key can use the trusted overflow rate bucket but is not required. Core's trusted evaluator uses the same verifier and append-only persistence rules directly without depending on this HTTP route.
Parameters
roundNumberpathrequiredRound number. Range: 1-2147483647.
Request body
drawBlockHashrequiredstringNo field description. 64 lowercase hex characters.
outputrequiredstringNo field description. 200 lowercase hex characters.
witnessTyperequiredintegerNo field description. Range: 0-64.
proofrequiredstringNo field description. Lowercase hex.
Example request
curl -i -X POST \
-H 'Content-Type: application/json' \
--data '{
"drawBlockHash": "0000000000000000000000000000000000000000000000000000000000000000",
"output": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"witnessType": 0,
"proof": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}' \
'http://localhost:4000/api/rounds/3/vdf'Response
{
"status": "accepted",
"solution": {
"drawBlockHash": "0000000000000000000000000000000000000000000000000000000000000000",
"output": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"outputHash": "0000000000000000000000000000000000000000000000000000000000000000",
"witnessType": 0,
"proof": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"acceptedAt": "2026-07-15T12:00:00.000Z",
"solveDurationMs": 3600000,
"canonical": true
},
"roundStatus": "revealPending",
"roundSecretRevealAt": "2026-07-15T12:30:00.000Z"
}Errors
invalid_request400The body is not a valid solution object or drawBlockHash is malformed.
not_found404The round does not exist.
solution_not_ready409The round has not sealed a manifest and iteration count yet.
stale_draw_block409drawBlockHash is no longer the round's canonical draw block.
solution_conflict409A different valid output is already stored for this draw block.
invalid_request413The request body exceeds 24 KiB.
invalid_solution422The proof fails strict LUCKOTTO-1 verification.
rate_limited429The per-round verification rate bucket is exhausted or another public proof is already being verified.
internal_error503The VDF verifier is temporarily unavailable.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Partners
Public partner directory and authenticated partner account details.
GET/api/partnersList directory partnersPublic
/api/partnersList directory partnersReturns the public partner directory: partners presented by display name. Partners outside the directory still exist and are presented by payout address on round and ticket surfaces.
Example request
curl -i \ 'http://localhost:4000/api/partners'
http://localhost:4000/api/partnersResponse
{
"items": [
{
"payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"desiredPrizeSats": 100000000,
"displayName": "Example Partner",
"website": "https://partner.example"
}
]
}Errors
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/partners/{partnerPayoutAddress}Get partnerBearer API key
/api/partners/{partnerPayoutAddress}Get partnerReturns the path partner's Luckotto payout address, creation time, and available balance. The public deposit xpub is available from /api/constants. The Bearer API key must belong to this partner.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
Example request
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk'
Response
{
"payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"desiredPrizeSats": 100000000,
"createdAt": "2026-07-03T08:53:36.000Z",
"balanceSats": 99942
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Deposit addresses
Private partner deposit address allocation and lookup.
GET/api/partners/{partnerPayoutAddress}/deposit-addressesList deposit addressesBearer API key
/api/partners/{partnerPayoutAddress}/deposit-addressesList deposit addressesLists deposit addresses for the path partner. Set sort to asc or desc, then pass nextCursor as cursor with the same sort. hasNext tells whether another page exists now.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
sortqueryrequiredRequired allocation order. Keep the same value when following nextCursor. One of: 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 \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses?cursor=3899999&sort=asc'
Response
{
"hasNext": false,
"items": [
{
"id": "018f58d2-2800-4000-8000-000000000456",
"depositAddress": "tb1pafjzr7fs3tyal6l3l6ad5784fzmafvshrg7q4u535fjewhl9utvsn65th3",
"bip32Path": "/130014968/456224106/1083178501/182488015/337931103",
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"customerId": "external-user-123"
},
"autoTicket": null,
"status": "funded",
"depositCount": 1,
"pendingSats": 0,
"creditedSats": 99942
}
],
"nextCursor": "7"
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
invalid_request400sort is missing or invalid, cursor is malformed, limit is outside 1-500, or an unsupported query parameter is present.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
POST/api/partners/{partnerPayoutAddress}/deposit-addressesCreate deposit addressBearer API key
/api/partners/{partnerPayoutAddress}/deposit-addressesCreate deposit addressCreates a new Bitcoin deposit address for the path partner. Private address metadata is free-form; optional autoTicket metadata becomes public on generated tickets.
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 private metadata or autoTicket configuration returns an error and does not allocate another address.
const id = uuidV4FromHmac(addressLookupSeed, `order:${orderId}`);
await createDepositAddress({
id,
metadata: { orderId },
autoTicket: {
expression: "net_amount * 0.75",
metadata: { publicOrderReference }
}
});Use a private HMAC seed, not a public hash, so another caller cannot reserve the same id first.
Luckotto derives the address with Hash Tweak V1 from the canonical payoutAddress/id payload and the public wallets.deposit.xpub constant. Keep the UUID and payload private: anyone who learns them can reproduce the address relationship. The returned bip32Path is xpub-relative and independently verifiable at /docs/hash-tweak.
autoTicket.metadata is public ticket metadata and is copied exactly to every generated ticket. Auto-ticket funding txids remain visible only through authenticated partner deposit and ticket responses.
Build and test expressions in the browser at /docs/auto-ticket-expressions. The expression result is the total ticket budget, including the ticket fee.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
Request body
idrequiredstringRequired deposit-address UUIDv4 idempotency key. Retry with the exact same fields to replay the original result. Canonical lowercase UUID.
autoTicketoptionalobject or nullOptional auto-ticket configuration. Omit it or send null for an ordinary deposit address. Its metadata is public ticket metadata, unlike the address metadata field.
Example request
curl -i -X POST \
-H 'Authorization: Bearer <partner-api-key>' \
-H 'Content-Type: application/json' \
--data '{
"id": "018f58d2-2800-4000-8000-000000000457",
"metadata": {
"customerId": "new-customer-456",
"source": "checkout"
},
"autoTicket": {
"expression": "net_amount * 0.75",
"metadata": {
"source": "automatic-deposit"
}
}
}' \
'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses'Response
{
"id": "018f58d2-2800-4000-8000-000000000457",
"depositAddress": "tb1ptsps7ch37fe5mjs0mm2kl6043uvgsfvkgwk09742xuhxhvh2kxaqp5ggpz",
"bip32Path": "/459777854/157137149/1752886951/2034487076/377163367",
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"customerId": "new-customer-456",
"source": "checkout"
},
"autoTicket": {
"expression": "net_amount * 0.75",
"metadata": {
"source": "automatic-deposit"
}
},
"status": "pending",
"depositCount": 0,
"pendingSats": 0,
"creditedSats": 0
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
invalid_request400id is missing or not a UUIDv4, the body is not valid JSON, or autoTicket.expression is not a valid expression.
duplicate_ticket400id already exists with different metadata or autoTicket configuration. Retrying with the exact original fields returns the original address.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/partners/{partnerPayoutAddress}/deposit-addresses/{id}Get deposit address by idempotency keyBearer API key
/api/partners/{partnerPayoutAddress}/deposit-addresses/{id}Get deposit address by idempotency keyReturns one private deposit address for the path partner by UUIDv4 idempotency key, including the current reduced state of every deposit outpoint observed on it. Missing addresses and addresses owned by another partner return 404.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
idpathrequiredDeposit address UUIDv4 idempotency key. Canonical lowercase UUID.
Example request
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-addresses/018f58d2-2800-4000-8000-000000000456'
Response
{
"id": "018f58d2-2800-4000-8000-000000000456",
"depositAddress": "tb1pafjzr7fs3tyal6l3l6ad5784fzmafvshrg7q4u535fjewhl9utvsn65th3",
"bip32Path": "/130014968/456224106/1083178501/182488015/337931103",
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"customerId": "external-user-123"
},
"autoTicket": {
"expression": "net_amount * 0.5",
"metadata": {
"source": "automatic-deposit"
}
},
"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": 3,
"autoTicketResult": {
"status": "created",
"allocatedSats": 49971,
"balanceRemainderSats": 49971,
"ticketId": "018f58d2-2800-4000-8000-000000000123"
},
"updatedAt": "2026-07-02T05:04:01.000Z"
}
]
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
not_found404No deposit address with this id belongs to the partner.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Deposits
Append-only deposit lifecycle and reversible accounting events.
GET/api/partners/{partnerPayoutAddress}/deposit-eventsList deposit eventsBearer API key
/api/partners/{partnerPayoutAddress}/deposit-eventsList deposit eventsLists immutable deposit lifecycle and reversible deposit-only accounting events for the path partner. 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 treating a payment as final.
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/partners/{partnerPayoutAddress}/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.
Available partner balance is a simple accounting reducer. Lifecycle events carry balanceDeltaSats=0; credited events add available 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;
};
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
};
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;
}
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.creditStatus === "credited") return "credited";
return "confirmed";
}For a checkout UI, show the latest status for each outpoint on the deposit address. Treat credited as available to Luckotto. If the vendor wants a deeper safety policy, compare the reduced blockHeight with /api/chain-tip.processedTipHeight before creating the ticket.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
sortqueryrequiredRequired event id order. Keep the same value when following nextCursor. One of: asc desc
cursorqueryoptionalExclusive page cursor from the previous response's nextCursor. Canonical lowercase UUID.
limitqueryoptionalMaximum items to return. Defaults to 100. Range: 1-500.
depositAddressqueryoptionalFilter to one full deposit address. 14-128 characters.
depositTxidqueryoptionalFilter to one Bitcoin transaction id. 64 lowercase hex characters.
depositVoutqueryoptionalFilter to one Bitcoin transaction output index. Minimum: 0.
typequeryoptionalFilter to one deposit lifecycle or accounting event type. One of: mempool_seen mempool_dropped confirmed reorged_out credited uncredited
Example request
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/deposit-events?depositTxid=5ea52c60b43aabe57c016460a995809d700621c169fcb7d9b6338546b4f46c55&sort=asc'
Response
{
"hasNext": false,
"items": [
{
"id": "019f2730-3e38-7d2e-9204-f777ab4d0ab7",
"type": "credited",
"eventAt": "2026-07-02T05:04:01.000Z",
"depositAddress": "tb1pafjzr7fs3tyal6l3l6ad5784fzmafvshrg7q4u535fjewhl9utvsn65th3",
"bip32Path": "/130014968/456224106/1083178501/182488015/337931103",
"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
}
],
"nextCursor": "019f2730-3e38-7d2e-9204-f777ab4d0ab7"
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
invalid_request400sort is missing or invalid, or a cursor, limit, or filter parameter is malformed.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Tickets
Funded ticket creation, listing, and id lookup.
GET/api/ticketsList public ticketsPublic
/api/ticketsList public ticketsLists public tickets across all partners. Ticket UUID and metadata are public; createdAt and ticketFeeSats remain partner-only. Set sort to asc or desc, then pass nextCursor as cursor with the same sort.
Parameters
sortqueryrequiredRequired direction for roundNumber:ticketId ordering. Keep the same value when following nextCursor. One of: 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.
partnerPayoutAddressqueryoptionalFilter to one full partner Luckotto payout address. 14-128 characters.
Example request
curl -i \ 'http://localhost:4000/api/tickets?cursor=3%3A018f58d2-2800-4000-8000-000000000123&partnerPayoutAddress=tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk&roundNumber=3&sort=asc'
http://localhost:4000/api/tickets?cursor=3%3A018f58d2-2800-4000-8000-000000000123&partnerPayoutAddress=tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk&roundNumber=3&sort=ascResponse
{
"hasNext": false,
"items": [
{
"id": "018f58d2-2800-4000-8000-000000000123",
"metadata": {
"displayReference": "ticket-123"
},
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000
}
],
"nextCursor": "3:018f58d2-2800-4000-8000-000000000123"
}Errors
invalid_request400sort is missing or invalid, cursor is malformed, limit is outside 1-500, or an unsupported query parameter is present.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/tickets/{id}Get public ticket by IDPublic
/api/tickets/{id}Get public ticket by IDReturns one public ticket by its canonical UUIDv4 identity without requiring a partner API key. Missing tickets return 404. createdAt and ticketFeeSats remain partner-only.
Parameters
idpathrequiredPublic canonical ticket UUIDv4. Canonical lowercase UUID.
Example request
curl -i \ 'http://localhost:4000/api/tickets/018f58d2-2800-4000-8000-000000000123'
http://localhost:4000/api/tickets/018f58d2-2800-4000-8000-000000000123Response
{
"id": "018f58d2-2800-4000-8000-000000000123",
"metadata": {
"displayReference": "ticket-123"
},
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000
}Errors
not_found404No ticket with this id exists.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/partners/{partnerPayoutAddress}/ticketsList ticketsBearer API key
/api/partners/{partnerPayoutAddress}/ticketsList ticketsLists tickets owned by the path partner. Ticket UUIDs and metadata are public; this authenticated view additionally includes createdAt and ticketFeeSats. Set sort to asc or desc, then pass nextCursor as cursor with the same sort.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
sortqueryrequiredRequired direction for roundNumber:ticketId ordering. Keep the same value when following nextCursor. One of: 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 \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets?cursor=3%3A018f58d2-2800-4000-8000-000000000123&roundNumber=3&sort=asc'
Response
{
"hasNext": false,
"items": [
{
"id": "018f58d2-2800-4000-8000-000000000123",
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000,
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"displayReference": "ticket-123"
},
"ticketFeeSats": 100,
"autoTicketFunding": null
}
],
"nextCursor": "3:018f58d2-2800-4000-8000-000000000123"
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
invalid_request400sort is missing or invalid, cursor is malformed, limit is outside 1-500, or an unsupported query parameter is present.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
GET/api/partners/{partnerPayoutAddress}/tickets/{id}Get ticket by IDBearer API key
/api/partners/{partnerPayoutAddress}/tickets/{id}Get ticket by IDReturns one ticket for the path partner by its public UUIDv4 identity. This authenticated response includes createdAt and ticketFeeSats.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
idpathrequiredPublic canonical ticket UUIDv4. Canonical lowercase UUID.
Example request
curl -i \ -H 'Authorization: Bearer <partner-api-key>' \ 'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets/018f58d2-2800-4000-8000-000000000123'
Response
{
"id": "018f58d2-2800-4000-8000-000000000123",
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000,
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"displayReference": "ticket-123"
},
"ticketFeeSats": 100,
"autoTicketFunding": null
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
not_found404No ticket with this id belongs to the partner.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
POST/api/partners/{partnerPayoutAddress}/ticketsCreate ticketBearer API key
/api/partners/{partnerPayoutAddress}/ticketsCreate ticketBuys one funded receipt for the path partner. The debit is exactly contributedSats + ticketFeeSats; optional expectedRoundNumber and expectedTicketFeeSats preconditions prevent creating a new ticket in an unexpected round or at an unexpected fee. Core aggregates all of the partner's round contributions into one draw interval governed by the desired prize snapshotted when the round locks.
Integration notes
The request id is both the public canonical ticket identity and the idempotency key. Use a random UUIDv4 or derive one from a private HMAC when deterministic lookup is required; never embed plaintext customer, order, or private information in it. Reusing the same id with a different partner, contribution, or metadata returns an error and does not debit balance again.
const [constants, rounds] = await Promise.all([
getConstants(),
listRounds({ sort: "desc", limit: 1 })
]);
const round = rounds.items[0];
const id = uuidV4FromHmac(ticketLookupSeed, `ticket:${orderId}`);
await createTicket({
id,
metadata: { displayReference: publicReference },
contributedSats,
expectedRoundNumber: round.roundNumber,
expectedTicketFeeSats: constants.fees.ticketFeeSats
});Use a private HMAC seed, not a public hash, so another caller cannot reserve the same id first.
Parameters
partnerPayoutAddresspathrequiredFull partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.
Request body
idrequiredstringRequired public canonical UUIDv4 ticket ID. Retry with the same contribution and metadata to replay the original result. Canonical lowercase UUID.
metadataoptionalPublicTicketMetadataPublic display metadata attached to the ticket. When the round seals, its canonical JSON encoding is committed through the partner manifest hash carried by the round manifest, but it does not affect the Core draw. Encrypt any private or sensitive values before storage.
contributedSatsrequiredintegerFixed ticket contribution A. Exactly this amount plus the ticket fee is debited at purchase and added to the partner's aggregate round contribution. Minimum: 1.
expectedRoundNumberoptionalintegerOptional new-purchase precondition. When supplied, a new ticket is created only in this round. An idempotent replay returns the stored ticket regardless of this value. Range: 1-2147483647.
expectedTicketFeeSatsoptionalSatsOptional new-purchase precondition. When supplied, a new ticket is created only when this is the current ticket fee. An idempotent replay returns the stored ticket regardless of this value. The caller asserts the fee but does not set it. Minimum: 0.
Example request
curl -i -X POST \
-H 'Authorization: Bearer <partner-api-key>' \
-H 'Content-Type: application/json' \
--data '{
"id": "018f58d2-2800-4000-8000-000000000123",
"metadata": {
"displayReference": "ticket-123"
},
"contributedSats": 1000,
"expectedRoundNumber": 3,
"expectedTicketFeeSats": 100
}' \
'http://localhost:4000/api/partners/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk/tickets'Response
{
"id": "018f58d2-2800-4000-8000-000000000123",
"partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
"roundNumber": 3,
"contributedSats": 1000,
"createdAt": "2026-07-03T08:53:36.000Z",
"metadata": {
"displayReference": "ticket-123"
},
"ticketFeeSats": 100,
"autoTicketFunding": null
}Errors
unauthorized401The Authorization header is missing or the Bearer API key is invalid.
forbidden403The API key is valid but belongs to a different partner than partnerPayoutAddress.
invalid_request400id is missing or not a UUIDv4, contributedSats is not a positive safe integer, an expected purchase value is outside its documented range, or the body is not valid JSON.
duplicate_ticket400id already exists with a different partner, contribution, or metadata. Retrying with the same immutable fields returns the original ticket.
insufficient_balance400Available balance is less than contributedSats plus the current ticketFeeSats.
unexpected_round409A new ticket's expectedRoundNumber is supplied and does not match the accepting round. Idempotent replays ignore this precondition.
unexpected_ticket_fee409A new ticket's expectedTicketFeeSats is supplied and does not match the live fee. Idempotent replays ignore this precondition.
Every operation can also return 429 rate_limited and 500 internal_error in the shared error envelope.
Error responses
Every failure, on every operation, is this JSON envelope — and the server validates the envelope before returning it. Each operation above lists its specific codes.
errorrequiredstringHuman-readable error message.
coderequiredstringStable machine-readable error code. One of: duplicate_ticket forbidden insufficient_balance internal_error invalid_solution invalid_request not_found rate_limited solution_conflict solution_not_ready round_closed stale_draw_block unexpected_round unexpected_ticket_fee unauthorized
Error-code catalog
duplicate_ticket400The idempotency id is already taken by a request with different immutable fields. Retrying with the original immutable fields returns the original resource instead.
forbidden403The authenticated caller cannot access this partner or resource.
insufficient_balance400The partner does not have enough available balance.
internal_error500503Luckotto failed unexpectedly while handling the request.
invalid_solution422The submitted VDF solution proof failed strict LUCKOTTO-1 verification.
invalid_request400405413415The request path, query string, headers, or body are invalid.
not_found404The requested resource does not exist.
rate_limited429The caller has sent too many requests; honor Retry-After when present.
solution_conflict409A different valid VDF output is already stored for this draw block.
solution_not_ready409The round has not sealed a manifest and iteration count yet, so it cannot accept a VDF solution.
round_closed400A non-ticket round-scoped operation cannot apply after sales moved to the successor. Ticket creation rolls over atomically and does not return this code.
stale_draw_block409The submitted draw block is no longer the round's canonical draw block.
unexpected_round409The expectedRoundNumber precondition does not match the accepting round for a new ticket. No ticket was created and no balance was debited.
unexpected_ticket_fee409The expectedTicketFeeSats precondition does not match the live ticket fee for a new ticket. No ticket was created and no balance was debited.
unauthorized401A required API key or session is missing or invalid.
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. General public and partner-key routes publish no fixed quota; VDF submission is the exception, with a per-round attempt bucket and a single public proof verifier. Retry when that verifier is occupied.
Unknown API paths
Unknown /api/* paths return HTTP 404 with this JSON envelope and code: "not_found".
Schemas
Named object shapes used across operations. Field types link to their own definitions.
ErrorResponseStable API error envelope.+
errorrequiredstringHuman-readable error message.
coderequiredstringStable machine-readable error code. One of: duplicate_ticket forbidden insufficient_balance internal_error invalid_solution invalid_request not_found rate_limited solution_conflict solution_not_ready round_closed stale_draw_block unexpected_round unexpected_ticket_fee unauthorized
PrivateMetadataPrivate partner metadata.+
PublicTicketMetadataPublic ticket display metadata. When the round seals, its canonical JSON encoding is committed through the partner manifest hash carried by the round manifest, but it does not affect the Core draw. Encrypt any private or sensitive values before storing them.+
SatsWhole satoshis encoded as a JSON safe integer.+
Type: integer Minimum: 0.
CountNon-negative count.+
Type: integer Minimum: 0.
ChainTipLatest Bitcoin block height processed by Luckotto's scanner.+
processedTipHeightrequiredCount or nullLatest Bitcoin block height fully processed by Luckotto's scanner, or null before the first scan. Minimum: 0.
processedTipHashrequiredstring or nullHash of the latest processed tip block, or null before the first scan. 64 lowercase hex characters.
processedAtrequiredstring or nullWhen Luckotto recorded this processed tip. UTC ISO 8601 date-time.
ConstantsPublic project and runtime constants used by API clients and workers.+
bitcoinrequiredobjectBitcoin network.
networkrequiredstringConfigured Bitcoin network.
walletsrequiredobjectPublic wallet metadata used for deposit and round verification.
depositrequiredobjectPublic account xpub used for partner deposit addresses.
xpubrequiredstringRaw depth-3 BIP86 account extended public key.
commitmentrequiredobjectPublic account xpub used for round commitments.
xpubrequiredstringRaw depth-3 BIP86 commitment account extended public key.
feesrequiredobjectCurrent server-side fee settings.
lotteryrequiredobjectLuckotto draw-allocation constants.
drawAllocationScalerequiredintegerCompiled LUCKOTTO-1 application-layer draw-allocation scale, fixed at 2^32. Always 4294967296.
maxPrizeShortfallFactorrequirednumberProject-wide prize shortfall cap factor. Every prize shortfall is capped at floor(prizeBankrollSats * maxPrizeShortfallFactor). This is a compiled protocol constant. Always 0.5.
roundsrequiredobjectRuntime round timing constants.
drawDelayBlocksrequiredintegerFixed LUCKOTTO-1 protocol delay: exactly 3 blocks after commitment confirmation. Always 3.
sufficientConfirmationBlocksrequiredCountConfirmations Luckotto requires before crediting ordinary deposits. Minimum: 0.
autoTicketConfirmationBlocksrequiredCountConfirmations required before an auto-ticket deposit irrevocably creates its ticket and credits its balance remainder. Minimum: 0.
minPayoutConfirmationsrequiredCountInternal unattended-settlement threshold; it does not change which Bitcoin chain is authoritative. Minimum: 0.
drawrequiredobjectThe fixed VDF construction and minimum iteration count.
minimumIterationsrequiredintegerNo field description. Always 1.
constructionrequiredstringNo field description. One of: class-group-repeated-squaring
proofrequiredstringNo field description. One of: nested-wesolowski
discriminantBitsrequiredintegerNo field description. Always 1024.
revealDelaySecondsrequiredintegerNo field description. Always 1800.
startingElementrequiredstringNo field description. One of: 08
seedEncodingrequiredstringNo field description. One of: LUCKOTTO-VDF-SEED-1
proofEncodingrequiredstringNo field description. One of: LUCKOTTO-CHIAVDF-NWESO-1
outputExtractorrequiredstringNo field description. One of: tagged-SHA-256
sampleExpansionrequiredstringNo field description. One of: SHA-256
CreateDepositAddressRequestRequest body for creating one partner deposit address.+
idrequiredstringRequired deposit-address UUIDv4 idempotency key. Retry with the exact same fields to replay the original result. Canonical lowercase UUID.
autoTicketoptionalobject or nullOptional auto-ticket configuration. Omit it or send null for an ordinary deposit address. Its metadata is public ticket metadata, unlike the address metadata field.
PartnerAuthenticated partner account.+
payoutAddressrequiredstringFixed public Bitcoin payout address for this partner's prize wins. 14-128 characters.
desiredPrizeSatsrequiredSatsLive desired prize configured by this partner. Core synchronizes it to every participation whose ticket sales remain open; the atomic ticket-sales close freezes the round value. Minimum: 0.
createdAtrequiredstringTime the partner account was created. UTC ISO 8601 date-time.
balanceSatsrequiredintegerAvailable partner balance for new tickets. It may be negative after an auto-ticket funding deposit is reorged out.
PublicPartnerPublic partner details.+
payoutAddressrequiredstringFixed public Bitcoin payout address for this partner's prize wins. 14-128 characters.
desiredPrizeSatsrequiredSatsDesired prize for this representation. Partner resources show the live value; round and ticket resources show the effective participation value, which becomes immutable when the round seals. Minimum: 0.
displayNamerequiredstring or nullPublic partner display name, or null when the partner is presented by its Luckotto payout address alone.
websiterequiredstring or nullPublic partner website, or null when the partner has none or is presented by address alone.
PublicPartnerListPublic partner directory.+
itemsrequiredarray of PublicPartnerDirectory partners, sorted by display name and Luckotto payout address.
DepositAddressA partner-owned Bitcoin deposit address.+
idrequiredstringDeposit address UUID. Partner-supplied when id was provided. Canonical lowercase UUID.
depositAddressrequiredstringBitcoin deposit address. Send funds to this value. 14-128 characters.
bip32PathrequiredstringFive-segment xpub-relative BIP32 path produced by Hash Tweak V1.
createdAtrequiredstringTime the deposit address was allocated. UTC ISO 8601 date-time.
autoTicketrequiredobject or nullAuto-ticket configuration, or null for an ordinary deposit address.
statusrequiredstringAddress funding state derived from confirmed and mempool deposits. One of: pending unconfirmed dropped funded
DepositAddressListDeposit addresses owned by the authenticated partner.+
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.
AddressDepositCurrent 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. Canonical lowercase UUID.
txidrequiredstringBitcoin transaction id containing the deposit output. 64 lowercase hex characters.
voutrequiredintegerTransaction output index. Minimum: 0.
chainStatusrequiredstringLatest chain lifecycle state for this outpoint. One of: mempool confirmed dropped reorged_out
creditStatusrequiredstringLatest balance accounting state. credited means the deposit added available partner balance; uncredited means a previous credit was reversed. One of: not_credited credited uncredited
depositFeeSatsrequiredSats or nullDeposit fee charged at credit time; null before crediting. Minimum: 0.
creditedSatsrequiredSats or nullSats credited to the partner balance; null unless credited. Minimum: 0.
blockHashrequiredstring or nullConfirmation block hash; null while unconfirmed. 64 lowercase hex characters.
blockTimerequiredstring or nullConfirmation block timestamp; null while unconfirmed. UTC ISO 8601 date-time.
txIndexrequiredinteger or nullConfirmation transaction index; null while unconfirmed. Minimum: 0.
confirmationCountrequiredCount or nullConfirmations at the processed chain tip; null unless confirmed. Compare with requiredConfirmationCount before treating a deposit as final. Minimum: 0.
requiredConfirmationCountrequiredCount or nullConfirmations required for this address type before processing the deposit. Minimum: 0.
autoTicketResultrequiredobject or nullAuto-ticket result, or null for an ordinary deposit address.
updatedAtrequiredstringTime of the latest lifecycle or accounting event. UTC ISO 8601 date-time.
DepositAddressDetailA partner-owned Bitcoin deposit address with the current reduced state of every observed deposit outpoint.+
idrequiredstringDeposit address UUID. Partner-supplied when id was provided. Canonical lowercase UUID.
depositAddressrequiredstringBitcoin deposit address. Send funds to this value. 14-128 characters.
bip32PathrequiredstringFive-segment xpub-relative BIP32 path produced by Hash Tweak V1.
createdAtrequiredstringTime the deposit address was allocated. UTC ISO 8601 date-time.
autoTicketrequiredobject or nullAuto-ticket configuration, or null for an ordinary deposit address.
statusrequiredstringAddress funding state derived from confirmed and mempool deposits. One of: pending unconfirmed dropped funded
depositsrequiredarray of AddressDepositCurrent 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.
DepositEventOne immutable deposit lifecycle or reversible deposit-only accounting event.+
idrequiredstringUUIDv7 event id. It appears as nextCursor in paginated responses. Canonical lowercase UUID.
typerequiredstringDeposit lifecycle or balance accounting event type. credited adds zero or more available sats and uncredited reverses that credit. One of: mempool_seen mempool_dropped confirmed reorged_out credited uncredited
eventAtrequiredstringEvent time decoded from the UUIDv7 event id. UTC ISO 8601 date-time.
depositAddressrequiredstringDeposit address that received this output. 14-128 characters.
bip32PathrequiredstringXpub-relative BIP32 path of the receiving deposit address.
depositTxidrequiredstringBitcoin transaction id containing the deposit output. 64 lowercase hex characters.
depositVoutrequiredintegerTransaction output index. Minimum: 0.
blockHeightrequiredCount or nullBlock height for confirmed and reorged_out lifecycle events; otherwise null. Minimum: 0.
blockHashrequiredstring or nullBlock hash for confirmed and reorged_out lifecycle events; otherwise null. 64 lowercase hex characters.
blockTimerequiredstring or nullBlock timestamp for confirmed and reorged_out lifecycle events; otherwise null. UTC ISO 8601 date-time.
txIndexrequiredinteger or nullTransaction index for confirmed and reorged_out lifecycle events; otherwise null. Minimum: 0.
confirmedBlockHeightrequiredCount or nullMost recent confirmed block height for this deposit at or before this event. Minimum: 0.
confirmedBlockHashrequiredstring or nullMost recent confirmed block hash for this deposit at or before this event. 64 lowercase hex characters.
confirmedBlockTimerequiredstring or nullMost recent confirmed block timestamp for this deposit at or before this event. UTC ISO 8601 date-time.
confirmedTxIndexrequiredinteger or nullMost recent confirmed transaction index for this deposit at or before this event. Minimum: 0.
balanceDeltaSatsrequiredintegerSigned partner balance delta. Lifecycle events use 0. Credited events add available balance; uncredited events remove a previous credit.
DepositEventListAppend-only deposit events visible to the authenticated partner.+
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.
CreateTicketRequestRequest body for buying one funded Luckotto ticket.+
idrequiredstringRequired public canonical UUIDv4 ticket ID. Retry with the same contribution and metadata to replay the original result. Canonical lowercase UUID.
metadataoptionalPublicTicketMetadataPublic display metadata attached to the ticket. When the round seals, its canonical JSON encoding is committed through the partner manifest hash carried by the round manifest, but it does not affect the Core draw. Encrypt any private or sensitive values before storage.
contributedSatsrequiredintegerFixed ticket contribution A. Exactly this amount plus the ticket fee is debited at purchase and added to the partner's aggregate round contribution. Minimum: 1.
expectedRoundNumberoptionalintegerOptional new-purchase precondition. When supplied, a new ticket is created only in this round. An idempotent replay returns the stored ticket regardless of this value. Range: 1-2147483647.
expectedTicketFeeSatsoptionalSatsOptional new-purchase precondition. When supplied, a new ticket is created only when this is the current ticket fee. An idempotent replay returns the stored ticket regardless of this value. The caller asserts the fee but does not set it. Minimum: 0.
TicketA funded Luckotto ticket. Its UUID and metadata are public; createdAt and ticketFeeSats are partner-only operational fields.+
idrequiredstringPublic canonical UUIDv4 ticket identity, committed through the partner manifest whose hash the round manifest carries. It is a funded receipt and idempotency key; Core selects a partner, not an individual ticket. Canonical lowercase UUID.
partnerPayoutAddressrequiredstringFull partner Luckotto payout address. 14-128 characters.
roundNumberrequiredintegerRound number this ticket belongs to. Minimum: 1.
createdAtrequiredstringTime the ticket was created. UTC ISO 8601 date-time.
metadatarequiredPublicTicketMetadataPublic ticket display metadata. Its canonical JSON encoding is included in the partner manifest when the round seals. Encrypt private or sensitive values before storage.
autoTicketFundingrequiredobject or nullPartner-private funding outpoint for an automatic ticket, or null for a manually purchased ticket.
TicketListTickets owned by the authenticated partner.+
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.
PublicTicketA public funded Luckotto ticket.+
idrequiredstringPublic canonical UUIDv4 ticket identity. Canonical lowercase UUID.
metadatarequiredPublicTicketMetadataPublic ticket display metadata. Its canonical JSON encoding is included in the partner manifest when the round seals. Encrypt private or sensitive values before storage.
partnerPayoutAddressrequiredstringFull partner Luckotto payout address. 14-128 characters.
roundNumberrequiredintegerRound number this ticket belongs to. Minimum: 1.
PublicTicketListPublic Luckotto tickets.+
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.
RoundPublic Luckotto round metadata and draw proof fields.+
roundNumberrequiredintegerRound number. Range: 1-2147483647.
opensAtrequiredstringWhen this round opened. UTC ISO 8601 date-time.
closesAtrequiredstringScheduled ticket-sales transition target. A due round remains accepting until sales move atomically to its successor. UTC ISO 8601 date-time.
ticketFundedPotSatsrequiredSatsticket-funded pot P=sum(contributedSats). It is available even while Q is unknown and is both the base prize and draw denominator. Minimum: 0.
prizeBankrollSatsrequiredSats or nullPrize bankroll Q: the fully partner-owned bankroll backing final prize shortfalls. Null while prize bankroll pending. Q may rise through investment while the round is open and becomes immutable when the round closes; its final value prices shortfalls and draw allocations and is committed inside the manifest. Minimum: 0.
roundManifestHashrequiredstring or nullTagged SHA-256 of the committed round manifest's exact bytes (the on-chain OP_RETURN payload), or null until the round locks and seals. The manifest carries the protocol name, round number, final prize bankroll, round secret hash, VDF iteration count, and the participating-partner table with each partner's desired prize, aggregate contribution, and partner-manifest hash. 64 lowercase hex characters.
roundSecretHashrequiredstring or nullTagged SHA-256 of the round secret bytes, embedded in the committed round manifest; published as soon as the round locks and seals. 64 lowercase hex characters.
roundSecretrequiredstring or nullPublished 32-byte draw-secret input, or null while it remains private; its tagged SHA-256 must equal roundSecretHash. 64 lowercase hex characters.
commitmentTxidrequiredstring or nullBitcoin transaction id committing to roundManifestHash, or null before broadcast. 64 lowercase hex characters.
commitmentBlockHeightrequiredCount or nullBlock height containing the commitment transaction, or null before confirmation. Minimum: 0.
drawBlockHeightrequiredCount or nullBlock height whose hash derives the fixed VDF discriminant. Minimum: 0.
drawBlockHashrequiredstring or nullBlock hash used to derive the public Chia VDF discriminant. 64 lowercase hex characters.
vdfIterationsrequiredinteger or nullOne-shot VDF iteration count committed in the round manifest, or null before sealing. Minimum: 1.
delayedDrawSeedrequiredstring or nullTagged SHA-256 of the canonical VDF output, or null before a solution is accepted. 64 lowercase hex characters.
drawExtensionSeedrequiredstring or nullPublic downstream extension seed derived from the accepted draw attempt counter plus one. It never affects Core winner selection and is null until resolution. 64 lowercase hex characters.
vdfSolutionAcceptedAtrequiredstring or nullServer acceptance time of the canonical VDF solution. UTC ISO 8601 date-time.
roundSecretRevealAtrequiredstring or nullCanonical VDF acceptance plus the fixed 30-minute reveal delay. UTC ISO 8601 date-time.
statusrequiredstringDerived public round lifecycle state. One of: open locked revealPending resolved
winningPartnerPayoutAddressrequiredstring or nullWinning partner's full Luckotto payout address when the outcome is won, or null. 14-128 characters.
outcomerequiredstring or nullResolution result: won or noWinner. Null until the round secret is revealed and the one-shot interval is selected. One of: won noWinner
payoutTxidrequiredstring or nullBitcoin payout transaction id, or null before payout. 1-500 characters.
resolvedAtrequiredstring or nullWhen the round was resolved, or null before resolution. UTC ISO 8601 date-time.
RoundDetailPublic Luckotto round metadata and draw proof fields.+
roundNumberrequiredintegerRound number. Range: 1-2147483647.
opensAtrequiredstringWhen this round opened. UTC ISO 8601 date-time.
closesAtrequiredstringScheduled ticket-sales transition target. A due round remains accepting until sales move atomically to its successor. UTC ISO 8601 date-time.
ticketFundedPotSatsrequiredSatsticket-funded pot P=sum(contributedSats). It is available even while Q is unknown and is both the base prize and draw denominator. Minimum: 0.
prizeBankrollSatsrequiredSats or nullPrize bankroll Q: the fully partner-owned bankroll backing final prize shortfalls. Null while prize bankroll pending. Q may rise through investment while the round is open and becomes immutable when the round closes; its final value prices shortfalls and draw allocations and is committed inside the manifest. Minimum: 0.
roundManifestHashrequiredstring or nullTagged SHA-256 of the committed round manifest's exact bytes (the on-chain OP_RETURN payload), or null until the round locks and seals. The manifest carries the protocol name, round number, final prize bankroll, round secret hash, VDF iteration count, and the participating-partner table with each partner's desired prize, aggregate contribution, and partner-manifest hash. 64 lowercase hex characters.
roundSecretHashrequiredstring or nullTagged SHA-256 of the round secret bytes, embedded in the committed round manifest; published as soon as the round locks and seals. 64 lowercase hex characters.
roundSecretrequiredstring or nullPublished 32-byte draw-secret input, or null while it remains private; its tagged SHA-256 must equal roundSecretHash. 64 lowercase hex characters.
commitmentTxidrequiredstring or nullBitcoin transaction id committing to roundManifestHash, or null before broadcast. 64 lowercase hex characters.
commitmentBlockHeightrequiredCount or nullBlock height containing the commitment transaction, or null before confirmation. Minimum: 0.
drawBlockHeightrequiredCount or nullBlock height whose hash derives the fixed VDF discriminant. Minimum: 0.
drawBlockHashrequiredstring or nullBlock hash used to derive the public Chia VDF discriminant. 64 lowercase hex characters.
vdfIterationsrequiredinteger or nullOne-shot VDF iteration count committed in the round manifest, or null before sealing. Minimum: 1.
delayedDrawSeedrequiredstring or nullTagged SHA-256 of the canonical VDF output, or null before a solution is accepted. 64 lowercase hex characters.
drawExtensionSeedrequiredstring or nullPublic downstream extension seed derived from the accepted draw attempt counter plus one. It never affects Core winner selection and is null until resolution. 64 lowercase hex characters.
vdfSolutionAcceptedAtrequiredstring or nullServer acceptance time of the canonical VDF solution. UTC ISO 8601 date-time.
roundSecretRevealAtrequiredstring or nullCanonical VDF acceptance plus the fixed 30-minute reveal delay. UTC ISO 8601 date-time.
statusrequiredstringDerived public round lifecycle state. One of: open locked revealPending resolved
winningPartnerPayoutAddressrequiredstring or nullWinning partner's full Luckotto payout address when the outcome is won, or null. 14-128 characters.
outcomerequiredstring or nullResolution result: won or noWinner. Null until the round secret is revealed and the one-shot interval is selected. One of: won noWinner
payoutTxidrequiredstring or nullBitcoin payout transaction id, or null before payout. 1-500 characters.
resolvedAtrequiredstring or nullWhen the round was resolved, or null before resolution. UTC ISO 8601 date-time.
winningPrizeShortfallSatsrequiredSats or nullThe winning partner's prize shortfall when the outcome is won, or null. Minimum: 0.
prizeSatsrequiredSats or nullThe Core prize owed to the winning partner: ticketFundedPotSats + winningPrizeShortfallSats. Null for unresolved and noWinner rounds. Minimum: 0.
RoundListPublic Luckotto rounds.+
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.
RoundPartnerOne partner's aggregate participation in a Luckotto round.+
roundNumberrequiredintegerRound number. Range: 1-2147483647.
payoutAddressrequiredstringParticipating partner's full Luckotto payout address. 14-128 characters.
displayNamerequiredstring or nullPublic partner display name, or null when the partner is presented by payout address alone.
soldTicketCountrequiredCountNumber of funded tickets bought by this partner in the round. Minimum: 0.
ticketFundedPotSatsrequiredSatsThis partner's aggregate contribution to the round ticket-funded pot. Minimum: 0.
desiredPrizeSatsrequiredSatsThe partner's live desired prize while ticket sales are open or its immutable snapshot after the atomic ticket-sales close. Minimum: 0.
prizeIfWonSatsrequiredSats or nullThe Core prize payable if this partner wins, or null while the prize bankroll is pending. Minimum: 0.
RoundPartnerListAll partners participating in one Luckotto round.+
itemsrequiredarray of RoundPartnerParticipants ordered by aggregate contribution descending, then payout address ascending.
VdfSolutionThe verified solution for the round's current draw block.+
outputrequiredstringCanonical 100-byte Chia BQFC output. 200 lowercase hex characters.
outputHashrequiredstringTagged SHA-256 of the canonical VDF output bytes. 64 lowercase hex characters.
witnessTyperequiredintegerNested Wesolowski witness depth. Range: 0-64.
proofrequiredstringLUCKOTTO-CHIAVDF-NWESO-1 proof bytes as lowercase hex. Lowercase hex.
acceptedAtrequiredstringDatabase-generated time the first valid solution was accepted. UTC ISO 8601 date-time.
solveDurationMsrequiredintegerElapsed server time from observing the draw block to accepting the solution. Minimum: 1.
drawBlockHashrequiredstringDraw block hash for this solution. 64 lowercase hex characters.
canonicalrequiredbooleanWhether this submission matches the round current draw block.
RoundVdfSolutionThe canonical VDF solution for a round, when available.+
roundNumberrequiredintegerNo field description. Range: 1-2147483647.
canonicalDrawBlockHashrequiredstring or nullNo field description. 64 lowercase hex characters.
roundManifestHashrequiredstring or nullThe tagged manifest hash bound into the VDF seed. 64 lowercase hex characters.
vdfIterationsrequiredinteger or nullNo field description. Minimum: 1.
SubmitVdfSolutionRequestOne VDF solution proof.+
drawBlockHashrequiredstringNo field description. 64 lowercase hex characters.
outputrequiredstringNo field description. 200 lowercase hex characters.
witnessTyperequiredintegerNo field description. Range: 0-64.
proofrequiredstringNo field description. Lowercase hex.
VdfSubmissionResultAccepted or already-stored canonical VDF solution.+
statusrequiredstringNo field description. One of: accepted alreadyStored
roundStatusrequiredstringNo field description. One of: locked revealPending resolved
roundSecretRevealAtrequiredstring or nullNo field description. UTC ISO 8601 date-time.