Skip to main content
← Back to blog
•by Engineering

Giveaway Verification API: Check Retweets, Replies, Follows

The Problem: Manual Verification Doesn't Scale

Running an airdrop, giveaway or NFT whitelist on X/Twitter? You've probably seen the drill:

1. Post a tweet: "Retweet + Reply (tag 3 friends) + Follow to enter"
2. Thousands of entries flood in
3. Now... how do you verify all of them?

Manually checking 5,000 retweets and 5,000 replies is impossible. Many projects resort to random selection (unfair) or trusting participants (exploitable). Bots and fake entries slip through.

XCROP's Interaction Check API solves this. Six dedicated endpoints let you programmatically verify every single participant (retweets, replies, quotes, follow relationships, and account-quality filters) in minutes.

The 6 Interaction Check Endpoints

EndpointWhat it checksCredits
`GET /v2/tweets/:tweetId/check-retweet`Did user retweet?50
`GET /v2/tweets/:tweetId/check-reply`Did user reply? (+ tag verification)50
`GET /v2/tweets/:tweetId/check-quote`Did user quote-tweet? (+ tag verification)50
`GET /v2/users/:username/check-qualified-account`Account old enough / enough followers?50
`GET /v2/users/:username/check-qualified-name`Display name contains required text?50
`GET /v2/users/check-follow`Does user follow you?50

All endpoints return a simple boolean result, no complex parsing needed.

> Note on likes: X removed public like visibility in June 2024, so like-checking is not offered. Use retweet, reply, and quote checks instead, plus the qualified-account checks above to filter bot entries by account age, follower count, or display-name requirements.

Quick Start: Verify a Single User

PYTHON
import requests
import os

API_KEY = os.environ["XCROP_API_KEY"]
HEADERS = {"Authorization": "Bearer " + API_KEY}
BASE = "https://xcrop.io/api/v2"
TWEET_ID = "1893045621038174208"

# Check retweet
rt = requests.get(
    BASE + "/tweets/" + TWEET_ID + "/check-retweet",
    headers=HEADERS,
    params={"username": "alice_crypto"}
).json()
print("Retweeted:", rt["data"]["retweeted"])

# Check reply with 3 friend tags required
reply = requests.get(
    BASE + "/tweets/" + TWEET_ID + "/check-reply",
    headers=HEADERS,
    params={"username": "alice_crypto", "min_tags": 3}
).json()
print("Replied:", reply["data"]["replied"])
print("Tags met:", reply["data"]["tags_met"])

# Check follow relationship
follow = requests.get(
    BASE + "/users/check-follow",
    headers=HEADERS,
    params={"source": "alice_crypto", "target": "your_project"}
).json()
print("Follows you:", follow["data"]["source_follows_target"])

Bulk Verification: Check All Participants

Here's a complete script to verify an entire airdrop campaign:

PYTHON
import requests
import time
import os
import json

API_KEY = os.environ["XCROP_API_KEY"]
HEADERS = {"Authorization": "Bearer " + API_KEY}
BASE = "https://xcrop.io/api/v2"

TWEET_ID = "1893045621038174208"
PROJECT_USERNAME = "your_project"
MIN_TAGS = 3

# List of participants (from your entry collection system)
participants = ["alice_crypto", "bob_defi", "charlie_nft"]  # ... thousands more

def verify_participant(username):
    """Check all conditions for a single participant."""
    result = {"username": username, "passed": False}

    # 1. Check retweet
    rt = requests.get(
        BASE + "/tweets/" + TWEET_ID + "/check-retweet",
        headers=HEADERS,
        params={"username": username}
    ).json()
    result["retweeted"] = rt["data"]["retweeted"]

    # 2. Check reply with friend tags
    reply = requests.get(
        BASE + "/tweets/" + TWEET_ID + "/check-reply",
        headers=HEADERS,
        params={"username": username, "min_tags": MIN_TAGS}
    ).json()
    result["replied"] = reply["data"]["replied"]
    result["tags_met"] = reply["data"].get("tags_met", False)

    # 3. Check follow
    follow = requests.get(
        BASE + "/users/check-follow",
        headers=HEADERS,
        params={"source": username, "target": PROJECT_USERNAME}
    ).json()
    result["follows"] = follow["data"]["source_follows_target"]

    # All conditions met?
    result["passed"] = (
        result["retweeted"]
        and result["replied"]
        and result["tags_met"]
        and result["follows"]
    )

    return result

# Verify all participants
verified = []
for i, user in enumerate(participants):
    try:
        result = verify_participant(user)
        verified.append(result)
        status = "PASS" if result["passed"] else "FAIL"
        print("[" + str(i + 1) + "/" + str(len(participants)) + "] @" + user + ": " + status)
    except Exception as e:
        print("[" + str(i + 1) + "] @" + user + ": ERROR - " + str(e))
    time.sleep(0.1)  # Be gentle with rate limits

# Results
winners = [r for r in verified if r["passed"]]
print("\nTotal checked:", len(verified))
print("Qualified:", len(winners))
print("Disqualified:", len(verified) - len(winners))

# Save results
with open("airdrop_results.json", "w") as f:
    json.dump({"winners": winners, "all": verified}, f, indent=2)

Tag Verification: Prevent Fake Entries

A common giveaway rule is "Reply and tag 3 friends." The check-reply and check-quote endpoints support the min_tags parameter to verify this automatically:

PYTHON
# Require at least 3 friend tags in the reply
response = requests.get(
    BASE + "/tweets/" + TWEET_ID + "/check-reply",
    headers=HEADERS,
    params={"username": "alice_crypto", "min_tags": 3}
).json()

data = response["data"]
print("Replied:", data["replied"])
print("Tags required:", data["tags_required"])  # 3
print("Tags found:", data["tags_found"])         # 4
print("Tags met:", data["tags_met"])             # True

The tag count excludes the original tweet author. So if your tweet is from @project and Alice replies "@project @bob @carol @dave", the tag count is 3 (bob, carol, dave), not 4.

Whitelist Verification at Submission Time

Checking everyone after the deadline suits a giveaway. NFT whitelists usually work the other way: a participant connects a wallet, enters an X handle and expects a pass or fail on the spot. The same endpoints cover that flow, called from your backend when the form is submitted:

JAVASCRIPT
// Run this in your form handler on the server. Calling XCROP from the browser would expose your API key.
async function checkWhitelistTasks(handle) {
  const headers = { Authorization: "Bearer " + process.env.XCROP_API_KEY };
  const base = "https://xcrop.io/api/v2";

  const [follow, retweet, reply] = await Promise.all([
    fetch(base + "/users/check-follow?source=" + handle + "&target=your_project", { headers }).then(r => r.json()),
    fetch(base + "/tweets/" + TWEET_ID + "/check-retweet?username=" + handle, { headers }).then(r => r.json()),
    fetch(base + "/tweets/" + TWEET_ID + "/check-reply?username=" + handle + "&min_tags=2", { headers }).then(r => r.json()),
  ]);

  // Report each task separately so the user knows what to fix.
  return {
    follows: follow.data.source_follows_target,
    retweeted: retweet.data.retweeted,
    replied: reply.data.verified, // replied AND met min_tags
  };
}

A few things matter once a real person is waiting on the answer:

• Use a timeout and retry once. Most checks return in under 3 seconds, but the odd one is slower. One quiet retry is better than showing a failure.
• Say which task is missing. "You still need to retweet" gets a resubmission. A bare "not eligible" gets a support ticket.
• Re-check before the snapshot. Some people unfollow right after they are approved. Run the bulk script above once more before you lock the list.
• Plan for launch day. The Pro plan allows 600 requests per minute, which is about 200 three-task submissions a minute.

JavaScript / Node.js Example

JAVASCRIPT
const API_KEY = process.env.XCROP_API_KEY;
const BASE = "https://xcrop.io/api/v2";
const TWEET_ID = "1893045621038174208";

async function checkInteractions(username) {
  const headers = { Authorization: "Bearer " + API_KEY };

  const [retweet, reply, relationship] = await Promise.all([
    fetch(BASE + "/tweets/" + TWEET_ID + "/check-retweet?username=" + username, { headers }).then(r => r.json()),
    fetch(BASE + "/tweets/" + TWEET_ID + "/check-reply?username=" + username + "&min_tags=3", { headers }).then(r => r.json()),
    fetch(BASE + "/users/check-follow?source=" + username + "&target=your_project", { headers }).then(r => r.json()),
  ]);

  return {
    username,
    retweeted: retweet.data.retweeted,
    replied: reply.data.replied,
    tags_met: reply.data.tags_met,
    follows: relationship.data.source_follows_target,
    qualified: retweet.data.retweeted && reply.data.replied && reply.data.tags_met && relationship.data.source_follows_target,
  };
}

// Verify participants in parallel (batches of 10)
async function verifyAll(participants) {
  const results = [];
  for (let i = 0; i < participants.length; i += 10) {
    const batch = participants.slice(i, i + 10);
    const batchResults = await Promise.all(batch.map(checkInteractions));
    results.push(...batchResults);
    console.log("Checked " + results.length + "/" + participants.length);
  }
  return results;
}

Cost Estimation

Each interaction check costs 50 credits. Here's what a typical campaign costs:

Campaign sizeChecks per userTotal requestsCreditsCost (Pro)
1,000 users3 (RT + Reply + Follow)3,000150K$0.75
5,000 users315,000750K$3.75
5,000 users2 (RT + Reply)10,000500K$2.50
10,000 users330,0001.5M$7.50

That works out to about $0.25 per 1,000 checks on Pro (50,000 credits), or about $0.35 on Basic.

With the Pro plan ($9.9/month, includes 2M credits), you can verify ~13,000 users with all 3 checks included in your monthly allowance.

Pay-as-you-go credit packs start at $2.90 for 400K credits (~2,600 full verifications).

Best Practices

1. Batch with delays: Add 100ms between requests to stay within rate limits
2. Run checks in parallel: Use Promise.all (JS) or asyncio (Python) for 3 checks per user simultaneously
3. Cache results: Store verification results to avoid re-checking the same user
4. Set a deadline: Run verification after the campaign ends, not during. This ensures all entries are captured
5. Use min_tags wisely: Set it to match your campaign rules exactly. Don't over-require
6. Handle edge cases: Some users change usernames or go private after entering. Catch errors gracefully
7. Export results: Save to JSON/CSV for transparency and dispute resolution

Frequently Asked Questions

How do I check if a user retweeted a tweet with an API?

Call GET /v2/tweets/:tweetId/check-retweet with the username. It returns retweeted as true or false and costs 50 credits. Replies (check-reply), quotes (check-quote) and follows (check-follow) work the same way.

Can I use this to verify an NFT whitelist?

Yes. Call the checks from your backend when someone submits their X handle, tell them which task is still missing, and run the checks again before the final snapshot, since some people unfollow after they are approved.

How much does giveaway verification cost?

Each check costs 50 credits, about $0.25 per 1,000 checks on the Pro plan. Verifying 1,000 entrants on retweet, reply and follow is 150,000 credits, roughly $0.75.

Can the API check likes?

No. X made likes private in June 2024, so no API can verify them reliably. Use retweet, reply, quote and follow checks instead.

Build this with the XCROP API

One API for X/Twitter data: profiles, tweets, followers, search and real-time streams. Start free with 5,000 credits/month, no card required.