API reference

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.

Productionhttps://luckotto.com/apiBitcoin mainnet, real funds
Localhttp://localhost:4000/apiDevelopment and testing
AuthenticationPublic + Bearer API keyRequirement shown per operation
FormatJSON in, JSON outErrors use one shared envelope
Examples on this page

Fill 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.

Quickstart

From API key to a sold ticket

Five requests take a new partner from nothing to a funded ticket a customer can verify.

1

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
}

Generate 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
}

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"
    }
  ]
}

Generate 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
}
5

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
}
Authentication

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.

Conventions

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.

Deposit-funded tickets

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 calculator
Guide

Pagination 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.

Guide

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.

open

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.

locked

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.

revealPending

Populated: delayedDrawSeed, vdfSolutionAcceptedAt, roundSecretRevealAt

A canonical VDF solution is accepted. Wait out the fixed reveal delay.

resolved

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 tip
Public

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

Example request

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

Response

200The latest scanner-processed chain tip, or nulls before the first scan.ChainTip
{
  "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 constants
Public

Returns 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/constants

Response

200Public project and runtime constants.Constants
{
  "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 rounds
Public

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

Parameters

sortqueryrequired

Required roundNumber order. Keep the same value when following nextCursor. One of: asc desc

cursorqueryoptional

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

limitqueryoptional

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

Example request

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

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,
      "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_request400

sort 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 round
Public

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

Parameters

roundNumberpathrequired

Round number. Range: 1-2147483647.

Example request

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

Response

200The requested public round.RoundDetail
{
  "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_request400

roundNumber is not a positive integer.

not_found404

The 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 partners
Public

Lists every partner that bought funded tickets in the round, with its aggregate contribution, ticket count, desired prize, and potential prize.

Parameters

roundNumberpathrequired

Round number. Range: 1-2147483647.

Example request

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

Response

200All participating partners in the requested round.RoundPartnerList
{
  "items": [
    {
      "roundNumber": 3,
      "payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
      "displayName": "Example Partner",
      "soldTicketCount": 1,
      "ticketFundedPotSats": 1000,
      "desiredPrizeSats": 1000,
      "prizeIfWonSats": 1000
    }
  ]
}

Errors

invalid_request400

roundNumber is not a positive integer.

not_found404

The 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 partner
Public

Returns one participating partner's aggregate contribution, ticket count, desired prize, and potential prize in the round.

Parameters

roundNumberpathrequired

Round number. Range: 1-2147483647.

partnerPayoutAddresspathrequired

Full 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/tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk

Response

200The requested partner's round participation.RoundPartner
{
  "roundNumber": 3,
  "payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "displayName": "Example Partner",
  "soldTicketCount": 1,
  "ticketFundedPotSats": 1000,
  "desiredPrizeSats": 1000,
  "prizeIfWonSats": 1000
}

Errors

invalid_request400

roundNumber is not a positive integer.

not_found404

The 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 solution
Public

Returns 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

roundNumberpathrequired

Round number. Range: 1-2147483647.

Example request

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

Response

200The round's current canonical VDF solution, if available.RoundVdfSolution
{
  "roundNumber": 3,
  "canonicalDrawBlockHash": null,
  "roundManifestHash": null,
  "vdfIterations": null,
  "solutions": []
}

Errors

invalid_request400

roundNumber is not a positive integer.

not_found404

The 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 solution
Public

Publicly 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

roundNumberpathrequired

Round number. Range: 1-2147483647.

Request body

SubmitVdfSolutionRequest application/json, required

drawBlockHashrequiredstring

No field description. 64 lowercase hex characters.

outputrequiredstring

No field description. 200 lowercase hex characters.

witnessTyperequiredinteger

No field description. Range: 0-64.

proofrequiredstring

No 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

201200The accepted canonical solution and reveal state. A newly accepted solution returns 201; resubmitting the identical output returns 200 with status alreadyStored.VdfSubmissionResult
{
  "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_request400

The body is not a valid solution object or drawBlockHash is malformed.

not_found404

The round does not exist.

solution_not_ready409

The round has not sealed a manifest and iteration count yet.

stale_draw_block409

drawBlockHash is no longer the round's canonical draw block.

solution_conflict409

A different valid output is already stored for this draw block.

invalid_request413

The request body exceeds 24 KiB.

invalid_solution422

The proof fails strict LUCKOTTO-1 verification.

rate_limited429

The per-round verification rate bucket is exhausted or another public proof is already being verified.

internal_error503

The 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 partners
Public

Returns 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/partners

Response

200Public directory partners.PublicPartnerList
{
  "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 partner
Bearer API key

Returns 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

partnerPayoutAddresspathrequired

Full 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

200The authenticated partner.Partner
{
  "payoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "desiredPrizeSats": 100000000,
  "createdAt": "2026-07-03T08:53:36.000Z",
  "balanceSats": 99942
}

Errors

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The 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 addresses
Bearer API key

Lists 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

sortqueryrequired

Required allocation order. Keep the same value when following nextCursor. One of: asc desc

cursorqueryoptional

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

limitqueryoptional

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

Example request

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

Response

200A page of deposit addresses.DepositAddressList
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

invalid_request400

sort 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 address
Bearer API key

Creates 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

Request body

CreateDepositAddressRequest application/json, required

idrequiredstring

Required deposit-address UUIDv4 idempotency key. Retry with the exact same fields to replay the original result. Canonical lowercase UUID.

metadataoptionalPrivateMetadata

Private free-form metadata attached to the new deposit address.

autoTicketoptionalobject or null

Optional 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

200The created deposit address. Retrying an identical request with the same id returns the original address.DepositAddress
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

invalid_request400

id is missing or not a UUIDv4, the body is not valid JSON, or autoTicket.expression is not a valid expression.

duplicate_ticket400

id 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 key
Bearer API key

Returns 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

idpathrequired

Deposit 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

200The private partner-owned deposit address with current per-outpoint deposit state.DepositAddressDetail
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

not_found404

No 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 events
Bearer API key

Lists 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

sortqueryrequired

Required event id order. Keep the same value when following nextCursor. One of: asc desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor. Canonical lowercase UUID.

limitqueryoptional

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

depositAddressqueryoptional

Filter to one full deposit address. 14-128 characters.

depositTxidqueryoptional

Filter to one Bitcoin transaction id. 64 lowercase hex characters.

depositVoutqueryoptional

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

typequeryoptional

Filter 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

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": "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

invalid_request400

sort 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 tickets
Public

Lists 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

sortqueryrequired

Required direction for roundNumber:ticketId ordering. Keep the same value when following nextCursor. One of: asc desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor.

limitqueryoptional

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

roundNumberqueryoptional

Filter to one round number. Minimum: 1.

partnerPayoutAddressqueryoptional

Filter 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=asc

Response

200A cursor page of public tickets.PublicTicketList
{
  "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_request400

sort 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 ID
Public

Returns 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

idpathrequired

Public 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-000000000123

Response

200The public funded ticket.PublicTicket
{
  "id": "018f58d2-2800-4000-8000-000000000123",
  "metadata": {
    "displayReference": "ticket-123"
  },
  "partnerPayoutAddress": "tb1pkpswdsh4gskkf397hrnnzy0gvxftkh2phzed8fcczufhawxfurts9782tk",
  "roundNumber": 3,
  "contributedSats": 1000
}

Errors

not_found404

No 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 tickets
Bearer API key

Lists 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

sortqueryrequired

Required direction for roundNumber:ticketId ordering. Keep the same value when following nextCursor. One of: asc desc

cursorqueryoptional

Exclusive page cursor from the previous response's nextCursor.

limitqueryoptional

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

roundNumberqueryoptional

Filter to one round number. Minimum: 1.

Example request

curl -i \
  -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

200A cursor page of partner-owned tickets.TicketList
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

invalid_request400

sort 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 ID
Bearer API key

Returns one ticket for the path partner by its public UUIDv4 identity. This authenticated response includes createdAt and ticketFeeSats.

Parameters

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

idpathrequired

Public 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

200The partner-owned ticket.Ticket
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

not_found404

No 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 ticket
Bearer API key

Buys 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

partnerPayoutAddresspathrequired

Full partner Luckotto payout address. It must match the Bearer API key. 14-128 characters.

Request body

CreateTicketRequest application/json, required

idrequiredstring

Required public canonical UUIDv4 ticket ID. Retry with the same contribution and metadata to replay the original result. Canonical lowercase UUID.

metadataoptionalPublicTicketMetadata

Public 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.

contributedSatsrequiredinteger

Fixed 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.

expectedRoundNumberoptionalinteger

Optional 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.

expectedTicketFeeSatsoptionalSats

Optional 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

200The created ticket. Retrying the same immutable ticket fields with the same id returns the original ticket with the same 200 status regardless of supplied round or fee expectations.Ticket
{
  "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

unauthorized401

The Authorization header is missing or the Bearer API key is invalid.

forbidden403

The API key is valid but belongs to a different partner than partnerPayoutAddress.

invalid_request400

id 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_ticket400

id already exists with a different partner, contribution, or metadata. Retrying with the same immutable fields returns the original ticket.

insufficient_balance400

Available balance is less than contributedSats plus the current ticketFeeSats.

unexpected_round409

A new ticket's expectedRoundNumber is supplied and does not match the accepting round. Idempotent replays ignore this precondition.

unexpected_ticket_fee409

A 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.

Shared

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.

errorrequiredstring

Human-readable error message.

coderequiredstring

Stable 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_ticket400

The idempotency id is already taken by a request with different immutable fields. Retrying with the original immutable fields returns the original resource instead.

forbidden403

The authenticated caller cannot access this partner or resource.

insufficient_balance400

The partner does not have enough available balance.

internal_error500503

Luckotto failed unexpectedly while handling the request.

invalid_solution422

The submitted VDF solution proof failed strict LUCKOTTO-1 verification.

invalid_request400405413415

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

not_found404

The requested resource does not exist.

rate_limited429

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

solution_conflict409

A different valid VDF output is already stored for this draw block.

solution_not_ready409

The round has not sealed a manifest and iteration count yet, so it cannot accept a VDF solution.

round_closed400

A 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_block409

The submitted draw block is no longer the round's canonical draw block.

unexpected_round409

The expectedRoundNumber precondition does not match the accepting round for a new ticket. No ticket was created and no balance was debited.

unexpected_ticket_fee409

The expectedTicketFeeSats precondition does not match the live ticket fee for a new ticket. No ticket was created and no balance was debited.

unauthorized401

A 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".

Reference

Schemas

Named object shapes used across operations. Field types link to their own definitions.

ErrorResponseStable API error envelope.
errorrequiredstring

Human-readable error message.

coderequiredstring

Stable 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 null

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

processedTipHashrequiredstring or null

Hash of the latest processed tip block, or null before the first scan. 64 lowercase hex characters.

processedAtrequiredstring or null

When Luckotto recorded this processed tip. UTC ISO 8601 date-time.

ConstantsPublic project and runtime constants used by API clients and workers.
bitcoinrequiredobject

Bitcoin network.

networkrequiredstring

Configured Bitcoin network.

walletsrequiredobject

Public wallet metadata used for deposit and round verification.

depositrequiredobject

Public account xpub used for partner deposit addresses.

xpubrequiredstring

Raw depth-3 BIP86 account extended public key.

commitmentrequiredobject

Public account xpub used for round commitments.

xpubrequiredstring

Raw depth-3 BIP86 commitment account extended public key.

feesrequiredobject

Current server-side fee settings.

depositFeeSatsrequiredSats

Fee charged when a confirmed deposit credits balance. Minimum: 0.

ticketFeeSatsrequiredSats

Fee charged when creating a ticket. Minimum: 0.

lotteryrequiredobject

Luckotto draw-allocation constants.

drawAllocationScalerequiredinteger

Compiled LUCKOTTO-1 application-layer draw-allocation scale, fixed at 2^32. Always 4294967296.

maxPrizeShortfallFactorrequirednumber

Project-wide prize shortfall cap factor. Every prize shortfall is capped at floor(prizeBankrollSats * maxPrizeShortfallFactor). This is a compiled protocol constant. Always 0.5.

roundsrequiredobject

Runtime round timing constants.

drawDelayBlocksrequiredinteger

Fixed LUCKOTTO-1 protocol delay: exactly 3 blocks after commitment confirmation. Always 3.

sufficientConfirmationBlocksrequiredCount

Confirmations Luckotto requires before crediting ordinary deposits. Minimum: 0.

autoTicketConfirmationBlocksrequiredCount

Confirmations required before an auto-ticket deposit irrevocably creates its ticket and credits its balance remainder. Minimum: 0.

minPayoutConfirmationsrequiredCount

Internal unattended-settlement threshold; it does not change which Bitcoin chain is authoritative. Minimum: 0.

drawrequiredobject

The fixed VDF construction and minimum iteration count.

minimumIterationsrequiredinteger

No field description. Always 1.

constructionrequiredstring

No field description. One of: class-group-repeated-squaring

proofrequiredstring

No field description. One of: nested-wesolowski

discriminantBitsrequiredinteger

No field description. Always 1024.

revealDelaySecondsrequiredinteger

No field description. Always 1800.

startingElementrequiredstring

No field description. One of: 08

seedEncodingrequiredstring

No field description. One of: LUCKOTTO-VDF-SEED-1

proofEncodingrequiredstring

No field description. One of: LUCKOTTO-CHIAVDF-NWESO-1

outputExtractorrequiredstring

No field description. One of: tagged-SHA-256

sampleExpansionrequiredstring

No field description. One of: SHA-256

CreateDepositAddressRequestRequest body for creating one partner deposit address.
idrequiredstring

Required deposit-address UUIDv4 idempotency key. Retry with the exact same fields to replay the original result. Canonical lowercase UUID.

metadataoptionalPrivateMetadata

Private free-form metadata attached to the new deposit address.

autoTicketoptionalobject or null

Optional 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.
payoutAddressrequiredstring

Fixed public Bitcoin payout address for this partner's prize wins. 14-128 characters.

desiredPrizeSatsrequiredSats

Live 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.

createdAtrequiredstring

Time the partner account was created. UTC ISO 8601 date-time.

balanceSatsrequiredinteger

Available partner balance for new tickets. It may be negative after an auto-ticket funding deposit is reorged out.

PublicPartnerPublic partner details.
payoutAddressrequiredstring

Fixed public Bitcoin payout address for this partner's prize wins. 14-128 characters.

desiredPrizeSatsrequiredSats

Desired 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 null

Public partner display name, or null when the partner is presented by its Luckotto payout address alone.

websiterequiredstring or null

Public partner website, or null when the partner has none or is presented by address alone.

PublicPartnerListPublic partner directory.
itemsrequiredarray of PublicPartner

Directory partners, sorted by display name and Luckotto payout address.

DepositAddressA partner-owned Bitcoin deposit address.
idrequiredstring

Deposit address UUID. Partner-supplied when id was provided. Canonical lowercase UUID.

depositAddressrequiredstring

Bitcoin deposit address. Send funds to this value. 14-128 characters.

bip32Pathrequiredstring

Five-segment xpub-relative BIP32 path produced by Hash Tweak V1.

createdAtrequiredstring

Time the deposit address was allocated. UTC ISO 8601 date-time.

metadatarequiredPrivateMetadata

Private partner metadata attached to this address.

autoTicketrequiredobject or null

Auto-ticket configuration, or null for an ordinary deposit address.

statusrequiredstring

Address funding state derived from confirmed and mempool deposits. One of: pending unconfirmed dropped funded

depositCountrequiredCount

Number of deposits observed for this address. Minimum: 0.

pendingSatsrequiredSats

Uncredited sats currently observed for this address. Minimum: 0.

creditedSatsrequiredSats

Sats credited to the partner balance from this address. Minimum: 0.

DepositAddressListDeposit addresses owned by the authenticated partner.
itemsrequiredarray of DepositAddress

Items on this page.

nextCursorrequiredstring

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

hasNextrequiredboolean

Whether another page is available right now.

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.
depositIdrequiredstring

UUIDv7 id of the observed deposit output. Canonical lowercase UUID.

txidrequiredstring

Bitcoin transaction id containing the deposit output. 64 lowercase hex characters.

voutrequiredinteger

Transaction output index. Minimum: 0.

amountSatsrequiredSats

Gross deposit output amount. Minimum: 0.

chainStatusrequiredstring

Latest chain lifecycle state for this outpoint. One of: mempool confirmed dropped reorged_out

creditStatusrequiredstring

Latest 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 null

Deposit fee charged at credit time; null before crediting. Minimum: 0.

creditedSatsrequiredSats or null

Sats credited to the partner balance; null unless credited. Minimum: 0.

blockHeightrequiredCount or null

Confirmation block height; null while unconfirmed. Minimum: 0.

blockHashrequiredstring or null

Confirmation block hash; null while unconfirmed. 64 lowercase hex characters.

blockTimerequiredstring or null

Confirmation block timestamp; null while unconfirmed. UTC ISO 8601 date-time.

txIndexrequiredinteger or null

Confirmation transaction index; null while unconfirmed. Minimum: 0.

confirmationCountrequiredCount or null

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

requiredConfirmationCountrequiredCount or null

Confirmations required for this address type before processing the deposit. Minimum: 0.

autoTicketResultrequiredobject or null

Auto-ticket result, or null for an ordinary deposit address.

updatedAtrequiredstring

Time 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.
idrequiredstring

Deposit address UUID. Partner-supplied when id was provided. Canonical lowercase UUID.

depositAddressrequiredstring

Bitcoin deposit address. Send funds to this value. 14-128 characters.

bip32Pathrequiredstring

Five-segment xpub-relative BIP32 path produced by Hash Tweak V1.

createdAtrequiredstring

Time the deposit address was allocated. UTC ISO 8601 date-time.

metadatarequiredPrivateMetadata

Private partner metadata attached to this address.

autoTicketrequiredobject or null

Auto-ticket configuration, or null for an ordinary deposit address.

statusrequiredstring

Address funding state derived from confirmed and mempool deposits. One of: pending unconfirmed dropped funded

depositCountrequiredCount

Number of deposits observed for this address. Minimum: 0.

pendingSatsrequiredSats

Uncredited sats currently observed for this address. Minimum: 0.

creditedSatsrequiredSats

Sats credited to the partner balance from this address. Minimum: 0.

depositsrequiredarray of AddressDeposit

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

DepositEventOne immutable deposit lifecycle or reversible deposit-only accounting event.
idrequiredstring

UUIDv7 event id. It appears as nextCursor in paginated responses. Canonical lowercase UUID.

typerequiredstring

Deposit 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

eventAtrequiredstring

Event time decoded from the UUIDv7 event id. UTC ISO 8601 date-time.

depositAddressrequiredstring

Deposit address that received this output. 14-128 characters.

bip32Pathrequiredstring

Xpub-relative BIP32 path of the receiving deposit address.

metadatarequiredPrivateMetadata

Private metadata from the receiving deposit address.

depositTxidrequiredstring

Bitcoin transaction id containing the deposit output. 64 lowercase hex characters.

depositVoutrequiredinteger

Transaction output index. Minimum: 0.

amountSatsrequiredSats

Gross deposit output amount. Minimum: 0.

blockHeightrequiredCount or null

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

blockHashrequiredstring or null

Block hash for confirmed and reorged_out lifecycle events; otherwise null. 64 lowercase hex characters.

blockTimerequiredstring or null

Block timestamp for confirmed and reorged_out lifecycle events; otherwise null. UTC ISO 8601 date-time.

txIndexrequiredinteger or null

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

confirmedBlockHeightrequiredCount or null

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

confirmedBlockHashrequiredstring or null

Most recent confirmed block hash for this deposit at or before this event. 64 lowercase hex characters.

confirmedBlockTimerequiredstring or null

Most recent confirmed block timestamp for this deposit at or before this event. UTC ISO 8601 date-time.

confirmedTxIndexrequiredinteger or null

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

balanceDeltaSatsrequiredinteger

Signed 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.
itemsrequiredarray of DepositEvent

Items on this page.

nextCursorrequiredstring

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

hasNextrequiredboolean

Whether another page is available right now.

CreateTicketRequestRequest body for buying one funded Luckotto ticket.
idrequiredstring

Required public canonical UUIDv4 ticket ID. Retry with the same contribution and metadata to replay the original result. Canonical lowercase UUID.

metadataoptionalPublicTicketMetadata

Public 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.

contributedSatsrequiredinteger

Fixed 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.

expectedRoundNumberoptionalinteger

Optional 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.

expectedTicketFeeSatsoptionalSats

Optional 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.
idrequiredstring

Public 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.

partnerPayoutAddressrequiredstring

Full partner Luckotto payout address. 14-128 characters.

roundNumberrequiredinteger

Round number this ticket belongs to. Minimum: 1.

contributedSatsrequiredSats

Fixed contribution A debited at purchase. Minimum: 0.

createdAtrequiredstring

Time the ticket was created. UTC ISO 8601 date-time.

metadatarequiredPublicTicketMetadata

Public ticket display metadata. Its canonical JSON encoding is included in the partner manifest when the round seals. Encrypt private or sensitive values before storage.

ticketFeeSatsrequiredSats

Fee charged to create this ticket. Minimum: 0.

autoTicketFundingrequiredobject or null

Partner-private funding outpoint for an automatic ticket, or null for a manually purchased ticket.

TicketListTickets owned by the authenticated partner.
itemsrequiredarray of Ticket

Items on this page.

nextCursorrequiredstring

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

hasNextrequiredboolean

Whether another page is available right now.

PublicTicketA public funded Luckotto ticket.
idrequiredstring

Public canonical UUIDv4 ticket identity. Canonical lowercase UUID.

metadatarequiredPublicTicketMetadata

Public ticket display metadata. Its canonical JSON encoding is included in the partner manifest when the round seals. Encrypt private or sensitive values before storage.

partnerPayoutAddressrequiredstring

Full partner Luckotto payout address. 14-128 characters.

roundNumberrequiredinteger

Round number this ticket belongs to. Minimum: 1.

contributedSatsrequiredSats

Fixed ticket contribution A. Minimum: 0.

PublicTicketListPublic Luckotto tickets.
itemsrequiredarray of PublicTicket

Items on this page.

nextCursorrequiredstring

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

hasNextrequiredboolean

Whether another page is available right now.

RoundPublic Luckotto round metadata and draw proof fields.
roundNumberrequiredinteger

Round number. Range: 1-2147483647.

opensAtrequiredstring

When this round opened. UTC ISO 8601 date-time.

closesAtrequiredstring

Scheduled ticket-sales transition target. A due round remains accepting until sales move atomically to its successor. UTC ISO 8601 date-time.

soldTicketCountrequiredCount

Number of funded tickets in the round. Minimum: 0.

ticketFundedPotSatsrequiredSats

ticket-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 null

Prize 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 null

Tagged 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 null

Tagged 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 null

Published 32-byte draw-secret input, or null while it remains private; its tagged SHA-256 must equal roundSecretHash. 64 lowercase hex characters.

commitmentTxidrequiredstring or null

Bitcoin transaction id committing to roundManifestHash, or null before broadcast. 64 lowercase hex characters.

commitmentBlockHeightrequiredCount or null

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

drawBlockHeightrequiredCount or null

Block height whose hash derives the fixed VDF discriminant. Minimum: 0.

drawBlockHashrequiredstring or null

Block hash used to derive the public Chia VDF discriminant. 64 lowercase hex characters.

vdfIterationsrequiredinteger or null

One-shot VDF iteration count committed in the round manifest, or null before sealing. Minimum: 1.

delayedDrawSeedrequiredstring or null

Tagged SHA-256 of the canonical VDF output, or null before a solution is accepted. 64 lowercase hex characters.

drawExtensionSeedrequiredstring or null

Public 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 null

Server acceptance time of the canonical VDF solution. UTC ISO 8601 date-time.

roundSecretRevealAtrequiredstring or null

Canonical VDF acceptance plus the fixed 30-minute reveal delay. UTC ISO 8601 date-time.

statusrequiredstring

Derived public round lifecycle state. One of: open locked revealPending resolved

winningPartnerPayoutAddressrequiredstring or null

Winning partner's full Luckotto payout address when the outcome is won, or null. 14-128 characters.

outcomerequiredstring or null

Resolution result: won or noWinner. Null until the round secret is revealed and the one-shot interval is selected. One of: won noWinner

payoutTxidrequiredstring or null

Bitcoin payout transaction id, or null before payout. 1-500 characters.

resolvedAtrequiredstring or null

When the round was resolved, or null before resolution. UTC ISO 8601 date-time.

RoundDetailPublic Luckotto round metadata and draw proof fields.
roundNumberrequiredinteger

Round number. Range: 1-2147483647.

opensAtrequiredstring

When this round opened. UTC ISO 8601 date-time.

closesAtrequiredstring

Scheduled ticket-sales transition target. A due round remains accepting until sales move atomically to its successor. UTC ISO 8601 date-time.

soldTicketCountrequiredCount

Number of funded tickets in the round. Minimum: 0.

ticketFundedPotSatsrequiredSats

ticket-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 null

Prize 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 null

Tagged 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 null

Tagged 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 null

Published 32-byte draw-secret input, or null while it remains private; its tagged SHA-256 must equal roundSecretHash. 64 lowercase hex characters.

commitmentTxidrequiredstring or null

Bitcoin transaction id committing to roundManifestHash, or null before broadcast. 64 lowercase hex characters.

commitmentBlockHeightrequiredCount or null

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

drawBlockHeightrequiredCount or null

Block height whose hash derives the fixed VDF discriminant. Minimum: 0.

drawBlockHashrequiredstring or null

Block hash used to derive the public Chia VDF discriminant. 64 lowercase hex characters.

vdfIterationsrequiredinteger or null

One-shot VDF iteration count committed in the round manifest, or null before sealing. Minimum: 1.

delayedDrawSeedrequiredstring or null

Tagged SHA-256 of the canonical VDF output, or null before a solution is accepted. 64 lowercase hex characters.

drawExtensionSeedrequiredstring or null

Public 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 null

Server acceptance time of the canonical VDF solution. UTC ISO 8601 date-time.

roundSecretRevealAtrequiredstring or null

Canonical VDF acceptance plus the fixed 30-minute reveal delay. UTC ISO 8601 date-time.

statusrequiredstring

Derived public round lifecycle state. One of: open locked revealPending resolved

winningPartnerPayoutAddressrequiredstring or null

Winning partner's full Luckotto payout address when the outcome is won, or null. 14-128 characters.

outcomerequiredstring or null

Resolution result: won or noWinner. Null until the round secret is revealed and the one-shot interval is selected. One of: won noWinner

payoutTxidrequiredstring or null

Bitcoin payout transaction id, or null before payout. 1-500 characters.

resolvedAtrequiredstring or null

When the round was resolved, or null before resolution. UTC ISO 8601 date-time.

winningPrizeShortfallSatsrequiredSats or null

The winning partner's prize shortfall when the outcome is won, or null. Minimum: 0.

prizeSatsrequiredSats or null

The Core prize owed to the winning partner: ticketFundedPotSats + winningPrizeShortfallSats. Null for unresolved and noWinner rounds. Minimum: 0.

RoundListPublic Luckotto rounds.
itemsrequiredarray of Round

Items on this page.

nextCursorrequiredstring

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

hasNextrequiredboolean

Whether another page is available right now.

RoundPartnerOne partner's aggregate participation in a Luckotto round.
roundNumberrequiredinteger

Round number. Range: 1-2147483647.

payoutAddressrequiredstring

Participating partner's full Luckotto payout address. 14-128 characters.

displayNamerequiredstring or null

Public partner display name, or null when the partner is presented by payout address alone.

soldTicketCountrequiredCount

Number of funded tickets bought by this partner in the round. Minimum: 0.

ticketFundedPotSatsrequiredSats

This partner's aggregate contribution to the round ticket-funded pot. Minimum: 0.

desiredPrizeSatsrequiredSats

The partner's live desired prize while ticket sales are open or its immutable snapshot after the atomic ticket-sales close. Minimum: 0.

prizeIfWonSatsrequiredSats or null

The 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 RoundPartner

Participants ordered by aggregate contribution descending, then payout address ascending.

VdfSolutionThe verified solution for the round's current draw block.
outputrequiredstring

Canonical 100-byte Chia BQFC output. 200 lowercase hex characters.

outputHashrequiredstring

Tagged SHA-256 of the canonical VDF output bytes. 64 lowercase hex characters.

witnessTyperequiredinteger

Nested Wesolowski witness depth. Range: 0-64.

proofrequiredstring

LUCKOTTO-CHIAVDF-NWESO-1 proof bytes as lowercase hex. Lowercase hex.

acceptedAtrequiredstring

Database-generated time the first valid solution was accepted. UTC ISO 8601 date-time.

solveDurationMsrequiredinteger

Elapsed server time from observing the draw block to accepting the solution. Minimum: 1.

drawBlockHashrequiredstring

Draw block hash for this solution. 64 lowercase hex characters.

canonicalrequiredboolean

Whether this submission matches the round current draw block.

RoundVdfSolutionThe canonical VDF solution for a round, when available.
roundNumberrequiredinteger

No field description. Range: 1-2147483647.

canonicalDrawBlockHashrequiredstring or null

No field description. 64 lowercase hex characters.

roundManifestHashrequiredstring or null

The tagged manifest hash bound into the VDF seed. 64 lowercase hex characters.

vdfIterationsrequiredinteger or null

No field description. Minimum: 1.

solutionsrequiredarray of VdfSolution

No field description.

SubmitVdfSolutionRequestOne VDF solution proof.
drawBlockHashrequiredstring

No field description. 64 lowercase hex characters.

outputrequiredstring

No field description. 200 lowercase hex characters.

witnessTyperequiredinteger

No field description. Range: 0-64.

proofrequiredstring

No field description. Lowercase hex.

VdfSubmissionResultAccepted or already-stored canonical VDF solution.
statusrequiredstring

No field description. One of: accepted alreadyStored

solutionrequiredVdfSolution

The verified solution for the round's current draw block.

roundStatusrequiredstring

No field description. One of: locked revealPending resolved

roundSecretRevealAtrequiredstring or null

No field description. UTC ISO 8601 date-time.