{"openapi":"3.0.3","info":{"title":"BitRaffle Partner API","version":"1.0.0","description":"Read access to YOUR organisation's raffle data. Every request is authenticated with a secret API key (`Authorization: Bearer sk_…`) which resolves your organisation — responses only ever contain your own competitions, winners and settings. Ask your account manager to issue a key. Money amounts are integers: `ticketPriceUSDCents` is USD cents (2500 = $25.00). Keys carry scopes: `competitions:read` and `winners:read` by default; `entries:create` lets you start hosted checkout sessions for your buyers; `competitions:write` lets you create prizes and lots, activate and cancel them (both granted explicitly). Rate limited per key — see the X-RateLimit-* response headers; a 429 carries Retry-After. Webhooks: your organisation can register an HTTPS endpoint (Admin → Webhooks) to receive entry.confirmed, payment.settled, lot.closed, lot.cancelled and draw.completed as they happen — see the WebhookEnvelope schema for the payload and the X-BitRaffle-Signature scheme. Embed SDK: /embed.js (global `BitRaffle`) mounts a lot card, a checkout session's payment step, or a results panel on your own page — see packages/embed/README.md.","contact":{"name":"Partner support","url":"/developers"}},"servers":[{"url":"/api/v1","description":"Same host you were issued a key for (tenant subdomain or main domain)."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your secret key, e.g. `Authorization: Bearer sk_live_…`. Never expose it in a browser."}},"schemas":{"Payment":{"type":"object","description":"How this competition can be paid for right now (org rails ∩ competition override).","properties":{"acceptedTokens":{"type":"array","items":{"type":"string","enum":["USDC","USDT"]},"description":"Crypto tokens purchasable now. Empty when crypto is off for this competition."},"fiat":{"type":"boolean","description":"Whether card/bank (fiat) checkout is available."},"fiatCurrency":{"type":"string","nullable":true,"example":"VND","description":"The ONE fiat currency this competition charges."}}},"Competition":{"type":"object","properties":{"id":{"type":"integer","example":42},"title":{"type":"string","example":"Win a Rolex Submariner Date"},"description":{"type":"string","nullable":true,"description":"Markdown."},"ticketPriceUSDCents":{"type":"integer","example":2500,"description":"Ticket price in USD cents."},"paymentToken":{"type":"string","example":"USDC","description":"The crypto token this competition is priced in."},"maxTickets":{"type":"integer","example":500},"ticketsSold":{"type":"integer","example":103},"status":{"type":"string","enum":["draft","active","ended","cancelled"],"description":"Coarse persisted status. Prefer `state`."},"state":{"type":"string","enum":["draft","scheduled","open","closing","drawn","retired","cancelled"],"description":"Derived lifecycle state — the source of truth for whether entries are accepted. Only `open` lots sell."},"endDate":{"type":"string","format":"date-time","description":"When the draw closes (ISO 8601)."},"imageUrl":{"type":"string","nullable":true},"payment":{"$ref":"#/components/schemas/Payment"}}},"Draw":{"type":"object","nullable":true,"description":"Provably-fair commit-reveal proof. winnerIndex = keccak256(blockHash · competitionId) mod totalTickets.","properties":{"blockNumber":{"type":"integer"},"blockHash":{"type":"string"},"winnerIndex":{"type":"integer"},"totalTickets":{"type":"integer"}}},"Winner":{"type":"object","properties":{"competitionId":{"type":"integer"},"competitionTitle":{"type":"string"},"winnerWalletAddress":{"type":"string","example":"0x1234…abcd"},"wonAt":{"type":"string","format":"date-time"},"draw":{"$ref":"#/components/schemas/Draw"}}},"EntryQuestion":{"type":"object","description":"The entry condition. When `required`, answer before starting a checkout; a correct answer mints a single-use `pass` valid for 15 minutes.","properties":{"required":{"type":"boolean"},"question":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"category":{"type":"string","nullable":true}}}}},"CheckoutSessionRequest":{"type":"object","required":["competitionId","buyer"],"properties":{"competitionId":{"type":"integer","example":42},"quantity":{"type":"integer","minimum":1,"maximum":100,"default":1},"buyer":{"type":"object","description":"One of email or walletAddress. The buyer becomes (or maps to) an account so tickets and results share one identity.","properties":{"email":{"type":"string","format":"email"},"walletAddress":{"type":"string","example":"0x1234…abcd"}}},"skillPass":{"type":"string","description":"From POST /entry-question/verify, when the entry question is required."},"currency":{"type":"string","example":"USD","description":"Must equal the competition's charge currency when given."},"applyCredit":{"type":"boolean","default":true,"description":"Apply the buyer's account credit to the charge."},"returnUrl":{"type":"string","format":"uri","description":"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."}}},"CheckoutSession":{"type":"object","properties":{"id":{"type":"string","example":"RAF1789046392849TpO2yXOd","description":"Poll GET /checkout/sessions/{id}."},"status":{"type":"string","enum":["pending","paid","failed"]},"provider":{"type":"string","nullable":true,"enum":["checkout","stripe","worldpay","onepay","cvpay"]},"amount":{"type":"integer","description":"In the currency's normalized units (USD: cents; VND: whole đồng)."},"currency":{"type":"string","example":"USD"},"payment":{"type":"object","nullable":true,"description":"What to render. `redirectUrl` (type PAY_URL/CODE_URL): send the buyer there. Otherwise `data` is the rail's in-page session (CKO_FLOW, STRIPE_EMBED, WP_SDK) for the embed SDK to mount. WP_SDK additionally needs POST /checkout/sessions/{id}/confirm with the hosted fields' session hrefs — the SDK's `confirm` callback.","properties":{"type":{"type":"string"},"redirectUrl":{"type":"string"},"data":{"type":"string"}}}}},"CheckoutStatus":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","paid","failed"]},"competitionId":{"type":"integer"},"quantity":{"type":"integer"},"amount":{"type":"integer"},"currency":{"type":"string"},"ticketsIssued":{"type":"boolean","description":"True once settlement has issued the buyer's tickets."},"error":{"type":"string","nullable":true,"description":"The rail's reason when status is failed."}}},"Prize":{"type":"object","properties":{"id":{"type":"integer"},"category":{"type":"string","example":"watch"},"name":{"type":"string","nullable":true},"brand":{"type":"string","nullable":true},"model":{"type":"string","nullable":true},"referenceNumber":{"type":"string","nullable":true},"condition":{"type":"string","enum":["new","unworn","excellent","good"]},"retailValueUSDCents":{"type":"integer"},"description":{"type":"string","nullable":true},"imageUrls":{"type":"array","items":{"type":"string"}}}},"PrizeCreate":{"type":"object","required":["retailValueUSDCents"],"description":"A prize needs a name, or a brand and model.","properties":{"category":{"type":"string","default":"watch"},"name":{"type":"string"},"brand":{"type":"string"},"model":{"type":"string"},"referenceNumber":{"type":"string"},"condition":{"type":"string","enum":["new","unworn","excellent","good"],"default":"new"},"yearManufactured":{"type":"integer"},"retailValueUSDCents":{"type":"integer","example":850000},"description":{"type":"string"},"imageUrls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":12}}},"CompetitionCreate":{"type":"object","required":["prizeId","title","ticketPriceUSDCents","maxTickets","startDate","endDate"],"description":"Off-chain lots are created as `draft`; PATCH `status: active` to open them. `createOnChain: true` creates the lot on the raffle contract and it is active at once (crypto rail).","properties":{"prizeId":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","description":"Markdown."},"ticketPriceUSDCents":{"type":"integer","example":2500},"maxTickets":{"type":"integer","example":500},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"paymentToken":{"type":"string","enum":["USDC","USDT"],"default":"USDC"},"imageUrl":{"type":"string","format":"uri"},"createOnChain":{"type":"boolean","default":false},"payment":{"type":"object","description":"Rail overrides; null or omitted inherits the organisation's rails. Must leave at least one usable rail.","properties":{"crypto":{"type":"boolean","nullable":true},"fiat":{"type":"boolean","nullable":true},"fiatCurrency":{"type":"string","nullable":true,"example":"USD"}}}}},"CompetitionUpdate":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["draft","active"],"description":"Open a draft with `active`. Closing and cancelling have their own paths."},"payment":{"type":"object","properties":{"crypto":{"type":"boolean","nullable":true},"fiat":{"type":"boolean","nullable":true},"fiatCurrency":{"type":"string","nullable":true}}}}},"WebhookEnvelope":{"type":"object","description":"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.","properties":{"id":{"type":"string","example":"evt_1042"},"type":{"type":"string","enum":["entry.confirmed","payment.settled","lot.closed","lot.cancelled","draw.completed","ping"]},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true,"description":"Event 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."}}},"Error":{"type":"object","properties":{"error":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"},"required":{"type":"string","description":"Present on insufficient_scope: the scope needed."}}}}},"security":[{"bearerAuth":[]}],"tags":[{"name":"Competitions","description":"Your live raffles."},{"name":"Winners","description":"Published winners with their draw proofs."},{"name":"Entries","description":"Sell entries: the entry question, then a hosted checkout session. Requires the `entries:create` scope."},{"name":"Webhooks","description":"Outbound: events delivered to your endpoint, signed. Configured by your admin; documented under the WebhookEnvelope schema."},{"name":"Manage","description":"Run a raffle: create prizes and lots, open and cancel them. Requires the `competitions:write` scope."}],"paths":{"/competitions":{"post":{"tags":["Manage"],"summary":"Create a lot","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetitionCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Competition"}}}}}},"400":{"description":"Invalid body, or a payment choice that leaves no usable rail"},"502":{"description":"On-chain creation failed"}}},"get":{"tags":["Competitions"],"summary":"List active competitions","description":"Your organisation's currently active raffles, newest first. Requires the `competitions:read` scope.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Competition"}}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the seconds in Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/competitions/{id}":{"patch":{"tags":["Manage"],"summary":"Update a lot","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetitionUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Competition"}}}}}},"400":{"description":"Invalid body"},"404":{"description":"Not found"}}},"get":{"tags":["Competitions"],"summary":"Get one competition","description":"Requires the `competitions:read` scope. 404 if the id doesn't belong to your organisation.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"example":42}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Competition"}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Key lacks the required scope"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/winners":{"get":{"tags":["Winners"],"summary":"List published winners","description":"Winners your organisation has published, each with its commit-reveal draw proof. Requires the `winners:read` scope.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Winner"}}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Key lacks the required scope"}}}},"/entry-question":{"get":{"tags":["Entries"],"summary":"The entry condition for your buyers","description":"Whether a skill question is required at checkout, and one to ask. Requires `entries:create`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/EntryQuestion"}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Key lacks the required scope"}}}},"/entry-question/verify":{"post":{"tags":["Entries"],"summary":"Verify a buyer's answer","description":"A correct answer returns a single-use `pass` to send with the checkout session. Requires `entries:create`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questionId","answerIndex"],"properties":{"questionId":{"type":"integer"},"answerIndex":{"type":"integer"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"correct":{"type":"boolean"},"pass":{"type":"string"}}}}}}}},"404":{"description":"Unknown question"}}}},"/checkout/sessions":{"post":{"tags":["Entries"],"summary":"Start a hosted checkout for a buyer","description":"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`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionRequest"}}}},"responses":{"201":{"description":"Session started","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutSession"}}}}}},"400":{"description":"Invalid request, entries closed, sold out, or the entry condition was not met","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key lacks the required scope, or fiat checkout is off for this competition"},"404":{"description":"Competition not found"},"412":{"description":"Fiat payments are not configured for your organisation"}}}},"/checkout/sessions/{id}":{"get":{"tags":["Entries"],"summary":"Poll a checkout session","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutStatus"}}}}}},"404":{"description":"Not your session"}}}},"/checkout/sessions/{id}/confirm":{"post":{"tags":["Entries"],"summary":"Confirm a Worldpay session","description":"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`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessionHref"],"properties":{"sessionHref":{"type":"string","format":"uri"},"cvcHref":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Authorized and settled","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["paid","failed","pending"]},"ticketsIssued":{"type":"boolean"},"paymentId":{"type":"string","nullable":true}}}}}}}},"400":{"description":"Declined, not a Worldpay session, already final, or an invalid session href"},"404":{"description":"Not your session"}}}},"/prizes":{"get":{"tags":["Manage"],"summary":"List your prizes","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Prize"}}}}}}}}},"post":{"tags":["Manage"],"summary":"Create a prize","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrizeCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Prize"}}}}}},"400":{"description":"Invalid body (issues listed)"}}}},"/competitions/{id}/cancel":{"post":{"tags":["Manage"],"summary":"Cancel a lot","description":"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.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Competition"}}}}}},"400":{"description":"Already has a winner"},"404":{"description":"Not found"}}}}}}