You ran a giveaway on X, a few hundred people entered, and now you have to pick one. Web-based Twitter giveaway picker tools often want you to log in with your X account, and they show you a winner without any way to check how they got there. A short Python script gives you the same result, and your entrants can verify the draw.

It collects entries from replies and reposts, removes accounts created to farm giveaways, checks who follows you, then draws with a seed anyone can reproduce. We run TwittrAPI, which the script uses for reading. It doesn't need your X login.

Before you run the giveaway

Three decisions are much easier before the post goes out, so make them first.

Pick an entry action you can verify. You can read replies, reposts and quotes of any post. You can't read who liked it, as X made likes private: only the author sees the list, through their own session. So "like to enter" is the one rule that's awkward to check.

Publish the rules. State the deadline, entry action, number of winners, how you'll pick and any minimum account age. X's guidelines for promotions recommend discouraging multiple accounts, for instance by stating that anyone found to use them to enter is ineligible. Filters that weren't announced look like favoritism when a disappointed entrant notices.

Check the law. Sweepstakes, prize and tax rules differ by country and US state. Some places require written terms or ban purchase requirements. Ask someone qualified if the prize is valuable. This post isn't legal advice.

Step 1: Collect the entries

Create a key at /start. New accounts get $0.50 of test credit for 7 days. Run this at the deadline: the retweeters list has no timestamps, so a repost made after the deadline can't be told apart later.

import json
import os
import time
 
import requests
 
API = "https://api.twittrapi.com"
HEADERS = {"X-API-KEY": os.environ["TWITTRAPI_API_KEY"]}
TWEET_ID = "2117335720645759113"  # your giveaway post
 
 
def pages(path, **params):
    params["format"] = "simple"
    while True:
        r = requests.get(f"{API}{path}", headers=HEADERS, params=params, timeout=30)
        r.raise_for_status()
        page = r.json()
        yield from page["data"]
        if not page["hasNextPage"]:
            return
        params["cursor"] = page["nextCursor"]
        time.sleep(0.2)
 
 
def retweeters(tweet_id):
    return {u["id"]: u for u in pages(f"/tweet/{tweet_id}/retweets", count=100)}
 
 
def repliers(tweet_id, deadline=None):
    users = {}
    for t in pages(f"/tweet/{tweet_id}/replies", rankingMode="Recency"):
        if t.get("unavailable") or (deadline and t["createdAt"] > deadline):
            continue
        users[t["author"]["id"]] = t["author"]
    return users
 
 
entries = retweeters(TWEET_ID)
print(f"{len(entries)} accounts reposted")
 
with open("entries.json", "w") as f:
    json.dump(entries, f)

Keying by user ID means someone who replies ten times gets one entry. That's usually what you want, and your rules should say so.

For "repost and reply", intersect the two sets:

rt = retweeters(TWEET_ID)
rp = repliers(TWEET_ID, deadline="2026-11-10T18:00:00Z")
entries = {uid: rt[uid] for uid in rt.keys() & rp.keys()}

createdAt values are ISO strings in UTC, so comparing them as text works as long as both use the same format. You can use /tweet/{id}/quotes the same way for quote posts.

Save entries.json right away, and compare the size with counts.retweets on the post. X may not return every account, and suspended accounts drop out, so your list can come up short. Note both numbers in your announcement.

Step 2: Filter throwaway accounts

Giveaways attract throwaway accounts. On a NASA post we checked while testing the endpoint, the first page of reposters included an account created two days earlier with no followers and a single post.

Apply only the filters you announced.

from datetime import datetime, timedelta, timezone
 
MIN_AGE_DAYS = 30
MIN_POSTS = 5
HOST = "yourhandle"
now = datetime.now(timezone.utc)
 
 
def eligible(user):
    if not user["createdAt"]:
        return False
    created = datetime.fromisoformat(user["createdAt"].replace("Z", "+00:00"))
    if now - created < timedelta(days=MIN_AGE_DAYS):
        return False
    if user["counts"]["tweets"] < MIN_POSTS:
        return False
    if user["protected"] or user["username"].lower() == HOST.lower():
        return False
    return True
 
 
pool = {uid: u for uid, u in entries.items() if eligible(u)}
print(f"{len(pool)} of {len(entries)} entries pass")

Keep the bar low. These filters are for obvious throwaways, and a stricter rule mainly hurts real people with quiet accounts. For a more detailed score, see the fake follower check.

Step 3: Make your Twitter giveaway picker verifiable

Most pickers are a black box. To make yours checkable, use a method anyone can rerun:

  1. Sort the eligible IDs so the order doesn't depend on how the API returned them.
  2. Seed the random generator with a value you don't control and can't know in advance.
  3. Publish the list and the seed.
import hashlib
import random
 
SEED = "paste-the-public-value-here"
 
 
def draw(pool, seed, winners=1, backups=3):
    ids = sorted(pool, key=int)
    rng = random.Random(hashlib.sha256(seed.encode()).hexdigest())
    picks = rng.sample(ids, k=min(winners + backups, len(ids)))
    return picks[:winners], picks[winners:]
 
 
winners, backups = draw(pool, SEED)
for uid in winners + backups:
    print(pool[uid]["username"], f"https://x.com/{pool[uid]['username']}")

Python's generator gives the same output for the same seed, so anyone with the ID list and the same Python version gets the same winner. Publish the version with the result.

For the seed, announce the source before the deadline, for example "the hash of the first Bitcoin block after 18:00 UTC" or a drand randomness round. Avoid using your own post's ID: you could keep posting until you liked the result. Most small giveaways don't need this level of proof, but it costs you nothing, and it ends any argument.

Take the backups in order. Winners don't always reply, and some won't meet the rules when you look closer.

Step 4: Check the winner

Don't check conditions for everyone. With a 2,000 person pool, checking each follow takes 2,000 calls, and only the winners matter. Check the winner, then move down the backups if needed.

def follows(user, host):
    r = requests.get(
        f"{API}/user/friendships/show",
        headers=HEADERS,
        params={"source_screen_name": user, "target_screen_name": host, "format": "simple"},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["data"]["following"]
 
 
for uid in winners + backups:
    name = pool[uid]["username"]
    if follows(name, HOST):
        print("Winner:", name)
        break
    print(name, "does not follow, trying the next one")

Then read the winner's profile yourself. A quick look at their recent posts will reveal an account that only ever reposts giveaways, and takes less time than dealing with a complaint.

Step 5: Announce it

Post the winner, the number of entries before and after filtering, the seed source and a link to the ID list. Publish IDs and not usernames: that's enough to rerun the draw, and it isn't a readable list of everyone who entered. IDs can still be traced back to accounts, so if your entrants wouldn't expect that, publish a SHA-256 hash of the list and share the list itself on request. Then contact the winner, and be careful with DMs that ask for details, because fake winner messages are a known scam. Say in your rules how you'll get in touch.

What it costs

Each page is one call at $0.0001 on the Standard tier.

Giveaway sizeCallsCost
500 repostsabout 5 to 15under $0.01
5,000 reposts and 5,000 repliesa few hundredabout $0.03
Follow check for one winner1 per person checked$0.0001 each

The $0.50 trial credit will cover most giveaways.

Frequently asked questions

How do I pick a random winner from Twitter replies?

Download the replies, keep one entry per user ID, sort the IDs and draw with a seeded random generator. The script above does this and removes throwaway accounts first, using the filters you announced.

Can I pick a winner from people who liked a tweet?

Only for your own posts and only through your own session, because X shows who liked a post just to its author. Use replies or reposts as the entry action if you want an easier check that anyone can verify.

Why is my retweet list shorter than the retweet count?

Suspended or deleted accounts drop out and X may stop the list early. Compare the list with counts.retweets and mention both numbers when you announce the result.

How do I stop bots from winning?

Announce a minimum account age and post count in your rules, filter on them and check the winner by hand. Have backup winners ready. No filter catches everything, so the manual check matters.

Can I check whether entrants follow me?

Yes. GET /user/friendships/show returns following for two handles. It's one call per check, so run it on the winner and the backups rather than the whole pool.

Sources

TwittrAPI is not affiliated with X Corp. It reads only public data.