Skip to main content
← Back to blog
by Engineering
tutorialcryptoairdrop

Verify Airdrop & Giveaway Entries with Interaction Check API

The Problem: Manual Verification Doesn't Scale

Running an airdrop or giveaway 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.

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

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