One call rolls the whole giveaway: say the tweet, how many winners, and your requirements — all in the query string — and get back the winners, an AI authenticity summary and stats for each, and the Solana block proof. One more call rerolls a slot.
Getting started
The whole API is two POST endpoints with every option in the query string — so your first request is just a URL. Pick the tweet, the winner count, and the requirements below and copy the finished call; each field names the parameter it sets.
Paste the X status URL or the bare tweet id — we pull its retweeters.
1–10
Entry requirements — each one is a query param
min_followers
min_account_age_days
must_follow — comma-separated handles, no @
min_media_count
https://api.retweet.gg/v1/roll?tweet=<tweet-id>&winners=3&exclude_bots=truecurl -X POST "https://api.retweet.gg/v1/roll?tweet=<tweet-id>&winners=3&exclude_bots=true" \
-H "Authorization: Bearer rtg_live_xxxxxxxxxxxx" \
--max-time 180A roll charges 1 credit per retweeter in the giveaway — same as the dashboard. Swap in a rtg_live_… key from your settings; use a test key to try it free.
Getting started
The retweet.gg API runs the rolling flow — the same pipeline the dashboard uses to turn a tweet into a verifiable draw — as a single synchronous call. Roll winners pulls the retweeters, applies your requirements, commits a Solana block, draws the winners, and returns them with an AI summary, per-winner stats, and the block proof. Reroll a winner replaces one slot. That is the entire surface — there is nothing else to orchestrate.
Parameters travel in the query string, responses are JSON, and errors use standard HTTP status codes. Every roll is addressed by its public code (e.g. ROLL-8DO2), which is also its verify URL: retweet.gg/verify/{code}.
One engine, two front doors
Getting started
Authenticate with a secret API key in the Authorization header as a bearer token. Create and roll keys from your account settings. Keys carry your credit balance and your ban lists, so treat them like a password.
curl -X POST "https://api.retweet.gg/v1/roll?tweet=1799887766554433221&winners=1" \
-H "Authorization: Bearer rtg_live_xxxxxxxxxxxx"rtg_live_…stringrequiredrtg_test_…stringoptionalKeep keys server-side
Getting started
Each key may make up to 120 requests per minute. A roll counts as one request no matter how many retweeters it pulls or how long it runs. Every response carries your current budget so you can back off gracefully; a 429 means you should retry after the window resets.
X-RateLimit-LimitintegeroptionalX-RateLimit-RemainingintegeroptionalX-RateLimit-ResetintegeroptionalGetting started
retweet.gg uses conventional HTTP status codes. 2xx means success, 4xx means the request was rejected (and the body says why), and 5xx means something failed on our end. Every error body has the same shape.
{
"error": {
"type": "insufficient_credits",
"code": "credits_required",
"message": "Rolling ~1300 retweeters needs ~1300 credits; your balance is 420.",
"param": null
}
}400invalid_requestoptional401authentication_erroroptional402insufficient_creditsoptional404not_foundoptional409invalid_stateoptional422validation_erroroptional429rate_limit_erroroptional500api_erroroptional504roll_timeoutoptionalThe rolling flow
You don’t drive the pipeline — the roll call does. Inside one request it runs six stages in order, and the response carries the artifacts of every one of them.
pullstageoptionalgatestageoptionalcommitstageoptionalrevealstageoptionaldrawstageoptionalanalyzestageoptionalWhy it’s slow — and why that’s the point
--max-time 180) and send an Idempotency-Key so a network retry can’t double-charge you.The rolling flow
End to end in one script: roll three winners, print each winner’s AI read and stats, print the block proof — then reroll a slot. Prefer clicking to typing? The query builder writes the first call for you.
const RTG = "https://api.retweet.gg/v1";
const key = process.env.RTG_API_KEY; // rtg_live_...
// One call runs the whole rolling flow: pull → gate → commit → reveal → draw → analyze.
const params = new URLSearchParams({
tweet: "1799887766554433221",
winners: "3",
min_followers: "100",
must_follow: "retweetgg",
exclude_bots: "true",
});
const roll = await fetch(`${RTG}/roll?${params}`, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
signal: AbortSignal.timeout(180_000), // the block reveal alone takes ~60s
}).then((r) => r.json());
for (const w of roll.winners) {
console.log(
`#${w.position} @${w.entrant.handle} — ${w.ai.summary} (${w.ai.score}/100), ` +
`${w.stats.followers} followers`,
);
}
console.log(`Seeded by Solana block ${roll.proof.rounds[0].blockhash}`);
console.log(`Proof: ${roll.proof.verify_url}`);
// Winner #3 turned out to be a bot? Reroll the slot — free, same committed block.
const reroll = await fetch(
`${RTG}/reroll?code=${roll.code}&position=3&reason=bot`,
{ method: "POST", headers: { Authorization: `Bearer ${key}` } },
).then((r) => r.json());
console.log(`New #3: @${reroll.winner.entrant.handle}`);The rolling flow
The draw engine is pure and deterministic — every winner is a function of the committed blockhash, the entry-list hash, and a nonce. The public verify page re-runs exactly this math from the proof in your response.
index(nonce) = HMAC_SHA256(
key = blockhash,
msg = entries_hash + ":" + nonce
) mod N // interpreted big-endian, N = number of entrantsid:handle lines) is the entries_hash we commit to.The 256-bit HMAC output dwarfs any realistic entry count, so modulo bias is cryptographically negligible. The algorithm is versioned as rtg-draw-v1 and stamped into every proof.
Endpoints
/v1/rollRuns the entire rolling flow synchronously and returns the finished draw: winners with AI summaries and stats, plus the block proof. There is no request body — everything travels in the query string. Expect the call to take 60–90 seconds (the block reveal dominates); set --max-time 180 or your client’s equivalent.
tweetstringrequiredwinnersintegerrequiredmin_followersintegeroptionalmin_account_age_daysintegeroptionalmust_followstringoptionalnot_following) and the roll advances to the next nonce.min_media_countintegeroptionalmust_be_verifiedbooleanoptionaltrue to require an X verified badge.must_have_pfpbooleanoptionaltrue to require a non-default profile picture.exclude_botsbooleanoptionaltrue to exclude accounts flagged by the authenticity model before the draw. Your ban lists are always enforced, with or without this flag.curl -X POST "https://api.retweet.gg/v1/roll?tweet=1799887766554433221&winners=3&min_followers=100&must_follow=retweetgg&exclude_bots=true" \
-H "Authorization: Bearer rtg_live_xxxxxxxxxxxx" \
--max-time 180{
"code": "ROLL-8DO2",
"status": "drawn",
"tweet_id": "1799887766554433221",
"author_handle": "retweetgg",
"winner_count": 3,
"entrant_count": 1284,
"raw_count": 1309,
"duplicates_removed": 25,
"credits_charged": 1284,
"requirements": { "min_followers": 100, "must_follow": ["retweetgg"], "exclude_bots": true },
"winners": [
{
"position": 1,
"entrant": { "id": "1700000000000000847", "handle": "degenmaxi", "display_name": "Degen Maxi", "avatar_url": "https://…", "x_verified": true },
"stats": { "followers": 12840, "following": 890, "account_age_days": 1642, "media_count": 214, "x_verified": true, "has_pfp": true },
"ai": {
"score": 86,
"tier": "clear",
"summary": "Looks human",
"signals": [
{ "label": "Account age", "detail": "4y+ old account", "weight": "positive" },
{ "label": "Follow ratio", "detail": "Balanced follows vs followers", "weight": "positive" },
{ "label": "Verified", "detail": "X verified badge", "weight": "positive" }
],
"source": "ai"
},
"roll": { "round": 0, "nonce": 0, "entry_index": 847 }
},
{
"position": 2,
"entrant": { "id": "1700000000000000219", "handle": "solbuilder", "display_name": "Sol Builder", "avatar_url": "https://…", "x_verified": false },
"stats": { "followers": 3120, "following": 1480, "account_age_days": 512, "media_count": 67, "x_verified": false, "has_pfp": true },
"ai": {
"score": 71,
"tier": "clear",
"summary": "Looks human",
"signals": [
{ "label": "Bio", "detail": "Normal bio", "weight": "positive" }
],
"source": "ai"
},
"roll": { "round": 0, "nonce": 3, "entry_index": 219 }
},
{
"position": 3,
"entrant": { "id": "1700000000000001102", "handle": "wagmiwren", "display_name": "Wagmi Wren", "avatar_url": "https://…", "x_verified": false },
"stats": { "followers": 486, "following": 1730, "account_age_days": 74, "media_count": 12, "x_verified": false, "has_pfp": true },
"ai": {
"score": 47,
"tier": "caution",
"summary": "Mixed signals",
"signals": [
{ "label": "Account age", "detail": "Created 74d ago", "weight": "negative" }
],
"source": "ai"
},
"roll": { "round": 0, "nonce": 4, "entry_index": 1102 }
}
],
"proof": {
"algo_version": "rtg-draw-v1",
"entries_hash": "9f3c1b0e…a71e",
"rounds": [
{ "round": 0, "slot": 296214877, "blockhash": "7Xn2…q9Ah", "source": "solana-mainnet", "committed_at": 1719936120000, "fetched_at": 1719936182000 }
],
"rolls": [
{ "seq": 1, "position": 1, "kind": "initial", "round": 0, "nonce": 0, "entry_index": 847, "handle": "degenmaxi", "superseded": false },
{ "seq": 2, "position": 2, "kind": "initial", "round": 0, "nonce": 3, "entry_index": 219, "handle": "solbuilder", "superseded": false },
{ "seq": 3, "position": 3, "kind": "initial", "round": 0, "nonce": 4, "entry_index": 1102, "handle": "wagmiwren", "superseded": false }
],
"excluded": [
{ "entry_index": 12, "handle": "airdrop_bot_4821", "reason": "bot", "detail": "Flagged as likely bot" },
{ "entry_index": 77, "handle": "smallreach", "reason": "min_followers", "detail": "62 < 100" }
],
"verify_url": "https://retweet.gg/verify/ROLL-8DO2"
}
}What you’re looking at
stats (follower count, account age, media posts…), an ai authenticity summary, and their roll trace. proof.rounds[0].blockhash is the Solana block that seeded the draw, and proof.verify_url is the public page where anyone can re-run it.Endpoints
/v1/rerollReplaces one winner slot — for a no-show, a bot that slipped through, or a manual swap. The reroll reuses the committed block and advances the nonce; the outgoing winner is excluded so they can’t reappear. It is appended to the proof’s roll log, so every reroll stays independently verifiable. Rerolls are free and return in a couple of seconds.
codestringrequiredROLL-8DO2).positionintegerrequiredreasonstringrequiredmanual, bot, no_response, fake_account. Tallied in the proof so a giveaway’s replacement rate is auditable.curl -X POST "https://api.retweet.gg/v1/reroll?code=ROLL-8DO2&position=3&reason=bot" \
-H "Authorization: Bearer rtg_live_xxxxxxxxxxxx"{
"code": "ROLL-8DO2",
"position": 3,
"reason": "bot",
"replaced": { "handle": "wagmiwren", "reason": "bot" },
"winner": {
"position": 3,
"entrant": { "id": "1700000000000000640", "handle": "novalabs", "display_name": "Nova Labs", "avatar_url": "https://…", "x_verified": false },
"stats": { "followers": 5410, "following": 620, "account_age_days": 980, "media_count": 88, "x_verified": false, "has_pfp": true },
"ai": {
"score": 78,
"tier": "clear",
"summary": "Looks human",
"signals": [
{ "label": "Follow ratio", "detail": "Balanced follows vs followers", "weight": "positive" }
],
"source": "ai"
},
"roll": { "round": 0, "nonce": 7, "entry_index": 640 }
},
"proof": {
"algo_version": "rtg-draw-v1",
"entries_hash": "9f3c1b0e…a71e",
"rounds": [
{ "round": 0, "slot": 296214877, "blockhash": "7Xn2…q9Ah", "source": "solana-mainnet", "committed_at": 1719936120000, "fetched_at": 1719936182000 }
],
"rolls": [
{ "seq": 3, "position": 3, "kind": "initial", "round": 0, "nonce": 4, "entry_index": 1102, "handle": "wagmiwren", "superseded": true },
{ "seq": 4, "position": 3, "kind": "reroll", "reason": "bot", "round": 0, "nonce": 7, "entry_index": 640, "handle": "novalabs", "superseded": false }
],
"excluded": [
{ "entry_index": 1102, "handle": "wagmiwren", "reason": "bot", "detail": "Rerolled out" }
],
"verify_url": "https://retweet.gg/verify/ROLL-8DO2"
}
}The response
Everything the flow produced, in one object. The same shape comes back from a reroll (scoped to the affected slot), and the proof inside it is the public record.
codestringoptionalstatusstringoptionaldrawn on success. Errors never return a partial draw.tweet_idstringoptionalauthor_handlestring | nulloptionalwinner_countintegeroptionalentrant_countintegeroptionalraw_countintegeroptionalduplicates_removedintegeroptionalcredits_chargedintegeroptionalentrant_count.requirementsobjectoptionalwinnersWinner[]optionalproofProofoptionalThe response
One drawn winner: who they are, their profile stats, the AI authenticity read, and the exact (round, nonce) that picked them.
positionintegeroptionalentrantobjectoptionalid (X numeric user id), handle (no @, lowercased), display_name, avatar_url, x_verified.statsobjectoptionalaiobjectoptionalrollobjectoptionalround, nonce, entry_index. Feed these plus the proof’s blockhash into the selection algorithm to recompute the win.followersintegeroptionalfollowingintegeroptionalaccount_age_daysintegeroptionalmedia_countintegeroptionalx_verifiedbooleanoptionalhas_pfpbooleanoptionalThe response
Every winner ships with an authenticity read — the same check the dashboard runs on winner cards — so you can spot a bot before you pay out a prize (and reroll it if one slips through).
scoreintegeroptionaltierstringoptionalclear (≥ 70), caution (40–69), or risk (< 40).summarystringoptionalLooks human, Mixed signals, Likely bot.signalsSignal[]optional{ label, detail, weight }, where weight is positive, neutral, or negative.sourcestringoptionalai when the model produced the read; heuristic when the deterministic fallback scored it.The response
The proof object makes the draw independently checkable: the committed Solana block(s), the entry-list hash, the append-only roll log, and the exclusions. It is byte-for-byte what the public verify page renders.
algo_versionstringoptionalrtg-draw-v1) — pins the recompute path forever.entries_hashstringoptionalroundsRound[]optional{ round, slot, blockhash, source, committed_at, fetched_at } — blockhash is the seed, and slot is public on-chain so anyone can confirm it.rollsRoll[]optional{ seq, position, kind, reason?, round, nonce, entry_index, handle, superseded }. Each entry recomputes independently from its (block, nonce).excludedExclusion[]optional{ entry_index, handle, reason, detail }. Reasons: banned, bot, duplicate, min_followers, account_too_new, not_following, min_media, not_verified, no_pfp.verify_urlstringoptionalPublic by design
retweet.gg/verify/{code} and watch every winner recompute in their own browser.Reference
The REST surface is versioned in the path (/v1). Additive, backwards-compatible changes (new query parameters, new response fields) ship without a version bump — write your parsers to ignore unknown fields. Breaking changes ship under a new path prefix. Separately, the draw math is versioned as rtg-draw-v1 inside every proof so a verifier always knows which recompute path to use, even years later.
Reference
The API spends the same credits as the dashboard: 1 credit per retweeter in the giveaway. A 1,300-retweeter roll costs ~1,300 credits, charged once during the pull stage. Everything else is free.
Roll1 credit / retweeteroptionalRerollfreeoptionalTest keysfreeoptionalIf a roll would exceed your balance you get a 402 insufficient_credits and nothing is charged. Top up here.
Reference
Safely retry either call by sending an Idempotency-Key header with a unique value (e.g. a UUID). If a request with the same key is replayed — say your client timed out mid-roll — you get the original result back instead of a second pull or a second draw, and you are never charged twice.
curl -X POST "https://api.retweet.gg/v1/roll?tweet=1799887766554433221&winners=3" \
-H "Authorization: Bearer rtg_live_xxxxxxxxxxxx" \
-H "Idempotency-Key: 6f1a…-8c2b" \
--max-time 180Official SDKs