API reference
The BitRaffle API
A REST API over JSON. Every request is authenticated with your secret key and only ever sees your own organisation's data.
Base URL
Use the host your key was issued for: your own subdomain, or the main domain. All paths below are relative to it.
Authentication
Send your secret key as a bearer token. The Draws endpoints also need a BitRaffle-Proof header signed with your registered key; see Signed requests.
Responses
Successful responses wrap their payload in data (the Draws endpoints return their object directly). Errors carry a machine-readable error code and a human message; see Errors. Money is always an integer: USD amounts are cents.
Prefer a machine-readable spec? openapi.json imports into Postman, Insomnia or any OpenAPI code generator.
https://www.bitraffle.io/api/v1curl https://www.bitraffle.io/api/v1/competitions \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"competitions:read
Competitions
Read your live competitions: price, tickets left, closing time and how each can be paid for.
List live competitions
Your organisation's currently active raffles, newest first. Requires the competitions:read scope.
Responses
curl https://www.bitraffle.io/api/v1/competitions \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/competitions";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/competitions"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": 71,
"title": "Win a Tudor Black Bay 58",
"description": "Tudor Black Bay 58, 39 mm, black dial…",
"ticketPriceUSDCents": 200,
"paymentToken": "USDC",
"maxTickets": 3000,
"ticketsSold": 412,
"status": "active",
"state": "open",
"endDate": "2026-10-24T01:49:13.000Z",
"imageUrl": "/uploads/1790921518423-Scf1NxFD.avif",
"payment": {
"acceptedTokens": [
"USDC",
"USDT"
],
"fiat": false
}
}
]
}Get one competition
Requires the competitions:read scope. 404 if the id doesn't belong to your organisation.
Path parameters
Responses
curl https://www.bitraffle.io/api/v1/competitions/71 \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/competitions/71";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/competitions/71"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": 71,
"title": "Win a Tudor Black Bay 58",
"description": "Tudor Black Bay 58, 39 mm, black dial…",
"ticketPriceUSDCents": 200,
"paymentToken": "USDC",
"maxTickets": 3000,
"ticketsSold": 412,
"status": "active",
"state": "open",
"endDate": "2026-10-24T01:49:13.000Z",
"imageUrl": "/uploads/1790921518423-Scf1NxFD.avif",
"payment": {
"acceptedTokens": [
"USDC",
"USDT"
],
"fiat": false
}
}
}winners:read
Winners
Published winners, each with the proof of how they were drawn.
List published winners
Winners your organisation has published, each with its commit-reveal draw proof. Requires the winners:read scope.
Responses
curl https://www.bitraffle.io/api/v1/winners \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/winners";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/winners"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"competitionId": 61,
"competitionTitle": "Win a Rolex Datejust 41",
"winnerWalletAddress": "0x7Fb6…2e19",
"wonAt": "2026-09-30T14:02:11.000Z",
"draw": {
"method": "vrf",
"seed": "0x5c1e…9a04",
"winnerIndex": 1287,
"totalTickets": 2600,
"vrfRequestId": "8841…2203",
"fulfillmentTx": "0x2b7d…c1f0",
"blockNumber": null
}
}
]
}entries:create
Entries and checkout
Sell entries from your own product: ask the entry question, start a hosted checkout, and follow it until tickets are issued.
Get the entry question
Whether a skill question is required at checkout, and one to ask. Requires entries:create.
Query parameters
Optional. Picks a question that fits this competition's prize category.
Responses
curl https://www.bitraffle.io/api/v1/entry-question \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/entry-question";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/entry-question"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"required": true,
"question": {
"id": 17,
"question": "On an analogue watch, how many degrees does the minute hand move in one minute?",
"options": [
"1",
"6",
"12",
"30"
],
"category": "watch"
}
}
}Check a buyer's answer
A correct answer returns a single-use pass to send with the checkout session. Requires entries:create.
Body
Responses
curl -X POST https://www.bitraffle.io/api/v1/entry-question/verify \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"questionId": 17,
"answerIndex": 1
}'const url = "https://www.bitraffle.io/api/v1/entry-question/verify";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"questionId": 17,
"answerIndex": 1
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/entry-question/verify"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"questionId": 17,
"answerIndex": 1,
},
)
data = res.json()["data"]{
"data": {
"correct": true,
"pass": "eyJxIjoxNywidCI6MSwiZSI6MTc5…Yw.k3Xb…"
}
}Start a checkout session
Prices the entries, reserves the buyer's credit, checks the entry window and the entry condition, then hands the order to the competition's payment rail. Render payment for the buyer; poll the session until ticketsIssued. Requires entries:create.
Body
Default 1. Between 1 and 100.
One of email or walletAddress. The buyer becomes (or maps to) an account so tickets and results share one identity.
Show child attributes
From POST /entry-question/verify, when the entry question is required.
Must equal the competition's charge currency when given.
Apply the buyer's account credit to the charge.
Default true.
Where redirect rails send the buyer afterwards, with ?ref=<session id>&status=success|failed appended. HTTPS, and its host must be one of your organisation's partner return hosts (Settings → Webhooks). Honoured by Checkout.com (after 3DS) and OnePay; in-page rails (Stripe, Worldpay, CVPay QR) never leave your page.
Responses
curl -X POST https://www.bitraffle.io/api/v1/checkout/sessions \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"competitionId": 71,
"quantity": 2,
"buyer": {
"email": "buyer@example.com"
},
"skillPass": "eyJxIjoxNywidCI6MSwiZSI6MTc5…Yw.k3Xb…",
"returnUrl": "https://your-site.com/raffle/return"
}'const url = "https://www.bitraffle.io/api/v1/checkout/sessions";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"competitionId": 71,
"quantity": 2,
"buyer": {
"email": "buyer@example.com"
},
"skillPass": "eyJxIjoxNywidCI6MSwiZSI6MTc5…Yw.k3Xb…",
"returnUrl": "https://your-site.com/raffle/return"
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/checkout/sessions"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"competitionId": 71,
"quantity": 2,
"buyer": {
"email": "buyer@example.com",
},
"skillPass": "eyJxIjoxNywidCI6MSwiZSI6MTc5…Yw.k3Xb…",
"returnUrl": "https://your-site.com/raffle/return",
},
)
data = res.json()["data"]{
"data": {
"id": "RAF1789046392849TpO2yXOd",
"status": "pending",
"provider": "checkout",
"amount": 400,
"currency": "USD",
"payment": {
"type": "PAY_URL",
"redirectUrl": "https://pay.checkout.com/…"
}
}
}Get a checkout session
Path parameters
Responses
curl https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "RAF1789046392849TpO2yXOd",
"status": "paid",
"competitionId": 71,
"quantity": 2,
"amount": 400,
"currency": "USD",
"ticketsIssued": true,
"error": null
}
}Confirm a Worldpay session
Worldpay only. The hosted card fields on your page (mounted by the embed SDK) produce session hrefs; this authorizes and settles synchronously and issues the tickets. Requires entries:create.
Path parameters
Body
Responses
curl -X POST https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd/confirm \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionHref": "https://access.worldpay.com/sessions/…",
"cvcHref": "https://access.worldpay.com/sessions/…"
}'const url = "https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd/confirm";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"sessionHref": "https://access.worldpay.com/sessions/…",
"cvcHref": "https://access.worldpay.com/sessions/…"
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/checkout/sessions/RAF1789046392849TpO2yXOd/confirm"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"sessionHref": "https://access.worldpay.com/sessions/…",
"cvcHref": "https://access.worldpay.com/sessions/…",
},
)
data = res.json()["data"]{
"data": {
"id": "RAF1789046392849TpO2yXOd",
"status": "paid",
"ticketsIssued": true,
"paymentId": "wp_8a3…"
}
}competitions:write
Prizes and competitions
Create prizes and competitions, open them, and cancel them.
List your prizes
Responses
curl https://www.bitraffle.io/api/v1/prizes \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/prizes";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/prizes"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": 66,
"category": "electronics",
"name": "MacBook Pro 14",
"brand": "Apple",
"model": "MacBook Pro 14-inch (M5)",
"referenceNumber": null,
"condition": "new",
"retailValueUSDCents": 199900,
"description": "M5, 16 GB, 512 GB SSD, Space Black.",
"imageUrls": [
"/uploads/1790921180124-zCYDenet.png"
]
}
]
}Create a prize
Body
Default "watch".
Default "new".
newunwornexcellentgoodAt most 12 items.
Responses
curl -X POST https://www.bitraffle.io/api/v1/prizes \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"category": "electronics",
"brand": "Apple",
"model": "MacBook Pro 14-inch (M5)",
"condition": "new",
"retailValueUSDCents": 199900,
"description": "M5, 16 GB, 512 GB SSD, Space Black.",
"imageUrls": [
"https://your-cdn.com/macbook-pro-14.png"
]
}'const url = "https://www.bitraffle.io/api/v1/prizes";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"category": "electronics",
"brand": "Apple",
"model": "MacBook Pro 14-inch (M5)",
"condition": "new",
"retailValueUSDCents": 199900,
"description": "M5, 16 GB, 512 GB SSD, Space Black.",
"imageUrls": [
"https://your-cdn.com/macbook-pro-14.png"
]
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/prizes"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"category": "electronics",
"brand": "Apple",
"model": "MacBook Pro 14-inch (M5)",
"condition": "new",
"retailValueUSDCents": 199900,
"description": "M5, 16 GB, 512 GB SSD, Space Black.",
"imageUrls": ["https://your-cdn.com/macbook-pro-14.png"],
},
)
data = res.json()["data"]{
"data": {
"id": 66,
"category": "electronics",
"name": null,
"brand": "Apple",
"model": "MacBook Pro 14-inch (M5)",
"referenceNumber": null,
"condition": "new",
"retailValueUSDCents": 199900,
"description": "M5, 16 GB, 512 GB SSD, Space Black.",
"imageUrls": [
"https://your-cdn.com/macbook-pro-14.png"
]
}
}Create a competition
Body
Markdown.
Default "USDC".
USDCUSDTDefault false.
Rail overrides; null or omitted inherits the organisation's rails. Must leave at least one usable rail.
Show child attributes
Responses
curl -X POST https://www.bitraffle.io/api/v1/competitions \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"prizeId": 66,
"title": "Win a MacBook Pro 14",
"description": "One winner. Draw by Chainlink VRF when entries close.",
"ticketPriceUSDCents": 100,
"maxTickets": 3000,
"startDate": "2026-10-02T12:00:00Z",
"endDate": "2026-11-06T12:00:00Z",
"paymentToken": "USDC"
}'const url = "https://www.bitraffle.io/api/v1/competitions";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"prizeId": 66,
"title": "Win a MacBook Pro 14",
"description": "One winner. Draw by Chainlink VRF when entries close.",
"ticketPriceUSDCents": 100,
"maxTickets": 3000,
"startDate": "2026-10-02T12:00:00Z",
"endDate": "2026-11-06T12:00:00Z",
"paymentToken": "USDC"
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/competitions"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"prizeId": 66,
"title": "Win a MacBook Pro 14",
"description": "One winner. Draw by Chainlink VRF when entries close.",
"ticketPriceUSDCents": 100,
"maxTickets": 3000,
"startDate": "2026-10-02T12:00:00Z",
"endDate": "2026-11-06T12:00:00Z",
"paymentToken": "USDC",
},
)
data = res.json()["data"]{
"data": {
"id": 70,
"title": "Win a MacBook Pro 14",
"description": "Tudor Black Bay 58, 39 mm, black dial…",
"ticketPriceUSDCents": 100,
"paymentToken": "USDC",
"maxTickets": 3000,
"ticketsSold": 0,
"status": "draft",
"state": "draft",
"endDate": "2026-11-06T12:00:00.000Z",
"imageUrl": null,
"payment": {
"acceptedTokens": [
"USDC",
"USDT"
],
"fiat": false
}
}
}Update a competition
Path parameters
Body
Open a draft with active. Closing and cancelling have their own paths.
draftactiveShow child attributes
Responses
curl -X PATCH https://www.bitraffle.io/api/v1/competitions/71 \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "active"
}'const url = "https://www.bitraffle.io/api/v1/competitions/71";
const res = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "active"
}),
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/competitions/71"
res = requests.patch(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"status": "active",
},
)
data = res.json()["data"]{
"data": {
"id": 70,
"title": "Win a MacBook Pro 14",
"description": "Tudor Black Bay 58, 39 mm, black dial…",
"ticketPriceUSDCents": 100,
"paymentToken": "USDC",
"maxTickets": 3000,
"ticketsSold": 0,
"status": "active",
"state": "open",
"endDate": "2026-11-06T12:00:00.000Z",
"imageUrl": "/uploads/1790921518423-Scf1NxFD.avif",
"payment": {
"acceptedTokens": [
"USDC",
"USDT"
],
"fiat": false
}
}
}Cancel a competition
Final. Closes the lot, refunds affiliate commissions on every sale, and (on-chain) leaves buyers to claim refunds from the contract. A lot with a winner cannot be cancelled.
Path parameters
Responses
curl -X POST https://www.bitraffle.io/api/v1/competitions/71/cancel \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/competitions/71/cancel";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const { data } = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/competitions/71/cancel"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": 70,
"title": "Win a MacBook Pro 14",
"description": "Tudor Black Bay 58, 39 mm, black dial…",
"ticketPriceUSDCents": 200,
"paymentToken": "USDC",
"maxTickets": 3000,
"ticketsSold": 412,
"status": "cancelled",
"state": "cancelled",
"endDate": "2026-10-24T01:49:13.000Z",
"imageUrl": "/uploads/1790921518423-Scf1NxFD.avif",
"payment": {
"acceptedTokens": [
"USDC",
"USDT"
],
"fiat": false
}
}
}draws:create
Draws
Provably-fair draws over any list you supply. Commit first, reveal second, and hand anyone the proof.
Price, limits and where to deposit
Read before your first draw. Bearer key only, no proof needed.
Responses
curl https://www.bitraffle.io/api/v1/draws/info \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/draws/info";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/info"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
body = res.json(){
"algorithm": "fair-draw-v1",
"proofHeader": "BitRaffle-Proof",
"unitCostCents": 1,
"maxCandidates": 10000,
"maxProofTtlSec": 300,
"settlement": {
"depositAddress": "0x26A8…1324",
"tokenAddress": "0xaf88…5831",
"tokenSymbol": "USDC",
"tokenDecimals": 6
},
"flow": [
"register-key",
"deposit (fund balance)",
"commit",
"reveal",
"verify at /verify"
]
}Register your signing key
One time, per API key. Bearer key only: you are registering the key your proofs will be signed with. A second registration is refused (409); rotate instead.
Body
Your Ed25519 public key: SPKI DER, base64url (no padding).
Responses
curl -X POST https://www.bitraffle.io/api/v1/draws/register-key \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"publicKey": "MCowBQYDK2VwAyEA3v1Jx…"
}'const url = "https://www.bitraffle.io/api/v1/draws/register-key";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"publicKey": "MCowBQYDK2VwAyEA3v1Jx…"
}),
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/register-key"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
json={
"publicKey": "MCowBQYDK2VwAyEA3v1Jx…",
},
)
body = res.json(){
"ok": true,
"keyPrefix": "sk_4f3a9c1e"
}Credit a USDC deposit
Send USDC to settlement.depositAddress, then report the transaction. We read it on-chain and credit what the chain shows, once per transaction.
Body
The USDC transfer to settlement.depositAddress.
Responses
curl -X POST https://www.bitraffle.io/api/v1/draws/deposit \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "BitRaffle-Proof: $PROOF" \
-H "Content-Type: application/json" \
-d '{
"txHash": "0x9b1c4e…e4a2"
}'const url = "https://www.bitraffle.io/api/v1/draws/deposit";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"BitRaffle-Proof": proof({ method: "POST", url }), // see Signed requests
"Content-Type": "application/json",
},
body: JSON.stringify({
"txHash": "0x9b1c4e…e4a2"
}),
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/deposit"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
"BitRaffle-Proof": proof("POST", url), # see Signed requests
},
json={
"txHash": "0x9b1c4e…e4a2",
},
)
body = res.json(){
"balanceCents": 2500,
"creditedCents": 2500,
"duplicate": false
}Commit a draw
We generate a secret server seed and publish its hash. Keep the commitment: it proves the seed existed before you chose yours. No body.
Responses
curl -X POST https://www.bitraffle.io/api/v1/draws/commit \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "BitRaffle-Proof: $PROOF"const url = "https://www.bitraffle.io/api/v1/draws/commit";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"BitRaffle-Proof": proof({ method: "POST", url }), // see Signed requests
},
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/commit"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
"BitRaffle-Proof": proof("POST", url), # see Signed requests
},
)
body = res.json(){
"drawId": "4b6f0c1e-8d2a-4f7b-9c3e-1a5d7e9f2b4c",
"serverSeedHash": "3f1c9a7be0d24c5f8a1e6b2d9c4f7a0e5b8d1c3f6a9e2b5d8c1f4a7e0b3d6c9f",
"algorithm": "fair-draw-v1",
"maxProofTtlSec": 300
}Reveal a draw
Send your seed and the candidate list; get the winners and a proof anyone can recompute. Charged once (unitCostCents) from your prepaid balance. Re-sending identical inputs returns the same result at no charge; different inputs on a revealed draw are refused.
Path parameters
Body
Your seed. Choose it AFTER you receive the commitment.
At most 200 characters.
The set to draw from, in a fixed order.
At most 10000 items.
How many distinct winners to draw (at most the candidate count).
At least 1.
Default "0". At most 80 characters.
Responses
curl -X POST https://www.bitraffle.io/api/v1/draws/4b6f0c1e-8d2a-4f7b-9c3e-1a5d7e9f2b4c/reveal \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "BitRaffle-Proof: $PROOF" \
-H "Content-Type: application/json" \
-d '{
"clientSeed": "acme-weekly-2026-10-02",
"candidates": [
"alice@example.com",
"bob@example.com",
"carol@example.com"
],
"pick": 1
}'const url = "https://www.bitraffle.io/api/v1/draws/4b6f0c1e-8d2a-4f7b-9c3e-1a5d7e9f2b4c/reveal";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"BitRaffle-Proof": proof({ method: "POST", url }), // see Signed requests
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientSeed": "acme-weekly-2026-10-02",
"candidates": [
"alice@example.com",
"bob@example.com",
"carol@example.com"
],
"pick": 1
}),
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/4b6f0c1e-8d2a-4f7b-9c3e-1a5d7e9f2b4c/reveal"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
"BitRaffle-Proof": proof("POST", url), # see Signed requests
},
json={
"clientSeed": "acme-weekly-2026-10-02",
"candidates": ["alice@example.com", "bob@example.com", "carol@example.com"],
"pick": 1,
},
)
body = res.json(){
"result": {
"winners": [
"bob@example.com"
],
"winnerIndices": [
1
]
},
"proof": {
"alg": "fair-draw-v1",
"serverSeedHash": "3f1c9a7be0d24c5f8a1e6b2d9c4f7a0e5b8d1c3f6a9e2b5d8c1f4a7e0b3d6c9f",
"serverSeed": "a41b0c7e9d2f5a8c1e4b7d0a3f6c9e2b5d8a1c4f7e0b3d6a9c2f5e8b1d4a7c0e",
"clientSeed": "acme-weekly-2026-10-02",
"nonce": "0",
"candidatesHash": "9d5e2a8c1f4b7e0d3a6c9f2b5e8d1a4c7f0b3e6d9a2c5f8b1e4d7a0c3f6b9e2d",
"candidateCount": 3,
"pick": 1,
"winnerIndices": [
1
]
},
"costCents": 1
}Balance and usage
Responses
curl https://www.bitraffle.io/api/v1/draws/usage \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/draws/usage";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/usage"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
body = res.json(){
"draws": 118,
"costCents": 118,
"balanceCents": 2382,
"unitCostCents": 1,
"settlement": {
"depositAddress": "0x26A8…1324",
"tokenAddress": "0xaf88…5831",
"tokenSymbol": "USDC",
"tokenDecimals": 6
}
}Deposits and draw charges
Your organisation's 20 most recent ledger entries, newest first.
Responses
curl https://www.bitraffle.io/api/v1/draws/ledger \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY"const url = "https://www.bitraffle.io/api/v1/draws/ledger";
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
},
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/ledger"
res = requests.get(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
},
)
body = res.json(){
"entries": [
{
"kind": "draw",
"amountCents": -1,
"txHash": null,
"drawId": "4b6f0c1e-8d2a-4f7b-9c3e-1a5d7e9f2b4c",
"createdAt": "2026-10-02T08:14:03.000Z"
},
{
"kind": "deposit",
"amountCents": 2500,
"txHash": "0x9b1c4e…e4a2",
"drawId": null,
"createdAt": "2026-10-01T09:30:41.000Z"
}
]
}Rotate your signing key
Replace the registered key. The proof must be signed by the CURRENT key, so a leaked bearer key alone cannot take over your draws.
Body
Your Ed25519 public key: SPKI DER, base64url (no padding).
Responses
curl -X POST https://www.bitraffle.io/api/v1/draws/rotate-key \
-H "Authorization: Bearer $BITRAFFLE_SECRET_KEY" \
-H "BitRaffle-Proof: $PROOF" \
-H "Content-Type: application/json" \
-d '{
"publicKey": "MCowBQYDK2VwAyEAq8Lm…"
}'const url = "https://www.bitraffle.io/api/v1/draws/rotate-key";
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITRAFFLE_SECRET_KEY}`,
"BitRaffle-Proof": proof({ method: "POST", url }), // see Signed requests
"Content-Type": "application/json",
},
body: JSON.stringify({
"publicKey": "MCowBQYDK2VwAyEAq8Lm…"
}),
});
const body = await res.json();import os, requests
url = "https://www.bitraffle.io/api/v1/draws/rotate-key"
res = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['BITRAFFLE_SECRET_KEY']}",
"BitRaffle-Proof": proof("POST", url), # see Signed requests
},
json={
"publicKey": "MCowBQYDK2VwAyEAq8Lm…",
},
)
body = res.json(){
"ok": true,
"keyPrefix": "sk_4f3a9c1e"
}Outbound
Webhook events
Events we POST to your endpoint as they happen. Set the endpoint and its secret in your admin under Webhooks; the Webhooks guide shows how to verify each delivery.
The event envelope
What your webhook endpoint receives (POST, application/json). Deliveries are at-least-once: id is stable across retries, dedupe on it. Headers: X-BitRaffle-Event (the type), X-BitRaffle-Delivery (the id), X-BitRaffle-Signature: t=<unix seconds>,v1=<hex> where v1 = HMAC-SHA256(secret, ${t}.${rawBody}). Verify over the RAW body, reject deliveries older than 5 minutes, answer 2xx within 10 seconds. A 4xx is final; 5xx and timeouts are retried with backoff for up to ~6 hours.
Attributes
entry.confirmedpayment.settledlot.closedlot.cancelleddraw.completedpingEvent payload. entry.confirmed: lotId, userId, quantity, ticketNumbers, source, orderId|txHash. payment.settled: orderId, lotId, userId, rail, amount, currency, quantity. lot.closed / lot.cancelled: lotId. draw.completed: lotId, winnerUserId, winnerTicketId.
| Event | When |
|---|---|
entry.confirmed | Tickets were issued. data: lotId, userId, quantity, ticketNumbers, source (fiat, crypto or free), orderId or txHash. |
payment.settled | A card or bank payment settled. data: orderId, lotId, userId, rail, amount, currency, quantity. |
lot.closed | A competition reached its end time and stopped selling. data: lotId, closedAt, endDate, ticketsSold. |
lot.cancelled | A competition was cancelled; buyers can claim refunds. data: lotId, cancelledAt, ticketsSold. |
draw.completed | A winner was drawn. data: lotId, winnerUserId, winnerTicketId. |
ping | A test delivery from your admin. |
{
"id": "evt_1042",
"type": "entry.confirmed",
"createdAt": "2026-10-02T08:14:03.000Z",
"data": {
"lotId": 70,
"userId": 5531,
"quantity": 2,
"ticketNumbers": [
118,
119
],
"source": "fiat",
"orderId": "RAF1789046392849TpO2yXOd",
"txHash": null
}
}X-BitRaffle-Event: entry.confirmed
X-BitRaffle-Delivery: evt_1042
X-BitRaffle-Signature: t=1790928843,v1=5f2b…9c1e