Intelligence API

v2.0.0

One authenticated REST surface over everything the platform collects: the multi-platform account catalog, the engagement and research layers built on it, the marketplace, the analysis tools and the B2B lead graph. This page is generated from the live endpoint catalog, so it describes exactly what is deployed right now.

Endpoints

74

Groups

10

Scopes in use

8 / 8

Live-metered

28

Reach an upstream source

Overview

Everything is JSON over HTTPS. Every request carries an API key, every response is wrapped in the same envelope, and every list is paged with an opaque cursor. Learn those three things and the rest of the surface is lookup.

Base URL

https://api.playersells.com/v2

Every path in this document is relative to that URL. Version 2.0.0 is served at /api/v2; the version is in the path, so a future v3 will not move this one.

Authentication

Send the key either way. They are equivalent; use whichever your HTTP client makes easier.

Authorization: Bearer psk_live_...

x-api-key: psk_live_...

Keys prefixed psk_test_ behave identically but are tagged as test traffic in usage reporting.

60-second quickstart

Issue a key in the console, export it, and call /api/v2/me. That endpoint needs no scope, so it answers for every valid key and tells you exactly which scopes, tier and limits the key carries. If it answers, your integration works and everything else is a matter of picking a path.

bash
export PLAYERSELLS_API_KEY="psk_live_..."

curl -sS "https://api.playersells.com/v2/api/v2/me" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"

Success envelope

json
{
  "data": [ /* the payload, shape depends on the endpoint */ ],
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "count": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ"
    }
  }
}

Error envelope

json
{
  "error": {
    "code": "forbidden_scope",
    "message": "This key does not carry the leads:read scope required by this endpoint.",
    "request_id": "req_9f2c41a8b3d5"
  }
}

A failure carries error instead of data and meta. Branch on error.code, which is stable, never on the message prose, which is not.

Authentication and scopes

A scope is the unit of authorization. Every key carries a set of them and every endpoint declares exactly one. A call with a valid key but the wrong scope is a 403, not a 401, and no amount of retrying changes that: the fix is on the key.

ScopeUnlocksEndpointsSensitivity
directory:readDirectoryRead the account and channel catalog across all seven platforms.15Standard
scrape:liveLive scrapeFetch a profile or channel live from the source, bypassing the catalog.12Sensitive
insights:readInsightsEngagement rates, top posts, viral patterns, cohort benchmarks.12Standard
research:readResearchFindings, runs and events from the autonomous research engine.4Standard
engines:readEngine healthThroughput, freshness, catalog size and crawl backlog per engine.2Standard
leads:readLeadsLinkedIn company and contact data, and X follower-graph exports.4Sensitive
tools:useToolsRun the analysis tools: valuation, follower audit, scoring, and more.20Sensitive
marketplace:readMarketplacePublic marketplace data: listings, platform stats, leaderboard, pricing.5Standard

What sensitive means

Sensitive scopes are never granted by default. They either export personal data (lead contacts, follower graphs) or spend real upstream budget on every call (live scrapes, tool runs). Grant them one key at a time, to a key you can revoke, and keep an IP allowlist on that key.

Rate limits and quotas

Two buckets apply at once: a per-minute burst ceiling and a per-day quota, both from the key's tier. Endpoints that reach an upstream source are capped by a third, separate bucket, so a client is free to page the catalog fast without being able to hammer the scraper pool at the same rate.

TierRequests / minuteRequests / dayLive calls / minute
free301.0K5
standard12025.0K20
pro600250.0K60
unlimited6.0K10.0M600

Response headers

X-Request-IdUnique id for this request. Echoed in meta.request_id and in every error body. Quote it in a support ticket.
X-RateLimit-LimitRequests allowed in the current one-minute window, from your tier.
X-RateLimit-RemainingRequests left in the current one-minute window.
X-RateLimit-ResetUnix seconds at which the current one-minute window resets.
X-Quota-LimitRequests allowed today, from your tier.
X-Quota-RemainingRequests left in today's quota. Resets at 00:00 UTC.
Retry-AfterSeconds to wait before retrying. Sent only with 429; obey it rather than backing off blindly.
X-PlanThe plan this key bills against. Absent when the key is not on a plan, which is different from being out of quota.
X-Plan-LimitRequests included in your plan for the current billing period.
X-Plan-RemainingIncluded requests left in the period. Reads 0 once you are in overage, which is not the same as being refused: watch the status code, not this number, to know whether you are still being served.
X-Plan-OverageRequests served past the included quota this period and not yet settled. Non-zero means you are accruing a charge.
X-Plan-Period-EndISO 8601 timestamp at which the current billing period ends and the included quota resets.

What a 429 looks like

http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1755938052
X-Quota-Limit: 25000
X-Quota-Remaining: 18344

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry in 12 seconds.",
    "request_id": "req_9f2c41a8b3d5"
  }
}

Read error.code before backing off. rate_limited clears within the minute and Retry-After says exactly when; quota_exceeded does not clear until 00:00 UTC, so a retry loop on it just burns your own CPU.

The live bucket

Endpoints marked live spend the live-per-minute bucket in addition to the normal per-minute ceiling. There are 28 of them. Design around it: enrich from the catalog in bulk, and reach for a live call only when you need one specific account fresher than the crawler has it.

Errors

Every failure returns the same envelope with one of these 14 codes. The codes are stable and are the only thing worth branching on. Every error also carries request_id, which resolves to the exact request in our logs.

CodeStatusMeaningWhat to do
unauthorized401No key was presented on the request.Send the key as Authorization: Bearer, or in x-api-key. Check that your HTTP client is not stripping the header on redirect.
invalid_key401The key is unknown, revoked, or past its expiry date.Stop retrying. Rotating or revoking a key takes effect immediately, so this will not clear on its own. Issue a new key in the console.
forbidden_scope403The key is valid but does not carry the scope the endpoint needs.Grant the scope named in the message to the key, or call an endpoint the key can reach. Retrying without changing the key will always fail.
forbidden_ip403The key has an IP allowlist and the request came from outside it.Add the calling address to the key's allowlist, or clear the allowlist. Watch for this after a server migration or a proxy change.
not_found404The addressed resource does not exist in the catalog.For a catalog read, the handle may simply not be crawled yet: try the live-scrape endpoint for that platform. For an id this API gave you, treat it as deleted.
invalid_request422A parameter is malformed, out of range, or conflicts with another.Read error.details: it names the offending fields. This is a bug in the caller, so do not retry the same request.
rate_limited429retryableThe per-minute burst ceiling for your tier is spent.Wait the number of seconds in Retry-After, then continue. This always clears within the minute.
quota_exceeded429A request budget is spent: either the daily ceiling for your tier, or the requests included in your plan for this period.The daily ceiling clears at 00:00 UTC and backing off before then will not help. A spent plan quota does not clear until the period renews, so the fix there is to upgrade, or to enable overage and keep serving.
payment_required402The plan quota is spent and overage cannot cover the request: it is switched off, the cap you set has been reached, or the wallet cannot fund it.Retrying will not help - nothing about this clears on a timer. Top up the wallet, raise or clear the overage cap, or upgrade the plan. Which of the three it is, is named in the message.
subscription_inactive402The key is valid but the subscription behind it is not: the billing period lapsed, or it was cancelled.Renew or restart the plan. The key itself stays valid throughout and does not need rotating, so nothing has to be redeployed once billing is current.
upstream_error502retryableA source the endpoint depends on failed.Retry with exponential backoff. If it persists across a whole platform, check the engine health endpoints before assuming it is your key.
upstream_timeout504retryableA source did not answer in time.Retry with backoff. Live scrapes are the usual source of this; the catalog read for the same account will normally still answer.
not_configured503retryableThe capability has no backing service in this environment.Retrying will not help: this is a deployment state, not a fault. Report it rather than looping on it.
internal_error500retryableSomething failed on our side. Internals are never leaked.Retry once with backoff. If it repeats, quote error.request_id in a support ticket - it resolves to the exact request in our logs.

Pagination

List endpoints page with an opaque cursor, never an offset. The cursor encodes the position of the last row in the current sort, so paging stays fast at any depth and rows do not shift under you while the crawlers insert new ones. Deep offset paging would do neither.

bash
# 1. First page
curl -sS "https://api.playersells.com/v2/api/v2/directory/{platform}?limit=100" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"

# -> { "data": [ ... ], "meta": { "page": {
#      "limit": 100, "count": 100,
#      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ"
#    } } }

# 2. Next page: hand the cursor back verbatim
curl -sS "https://api.playersells.com/v2/api/v2/directory/{platform}?limit=100&cursor=eyJrIjoiMTczNDU2IiwiZCI6ImEifQ" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"

# Stop when meta.page.next_cursor is null. Do NOT stop on a short page:
# a filtered page can come back short and still have more behind it.

Rules

  • Pass the cursor back verbatim. It is opaque: do not parse it, decode it or build one.
  • Stop when next_cursor is null. That is the only reliable stop condition.
  • Do not stop on a short page. A filtered page can come back short and still have more behind it.
  • limit defaults to 50 and caps at 500. Asking for more is a 422, not a silent clamp.
  • meta.page.total appears only when counting is cheap. Never assume it is there.

Capabilities matrix

What the platform can actually serve, per platform. Read off the endpoint metadata rather than maintained by hand, so it cannot claim coverage that does not exist. A dash means the capability is not sliced per platform at all: it is one dataset that applies across the board.

X

55

capabilities

Telegram

47

capabilities

Bluesky

42

capabilities

YouTube

45

capabilities

TikTok

46

capabilities

Instagram

47

capabilities

LinkedIn

34

capabilities

CapabilityScopeXTelegramBlueskyYouTubeTikTokInstagramLinkedIn
What a check can answer, per platformGET/accounts/platformsdirectory:read
Check one account against our catalogGET/accounts/{platform}/{handle}directory:read
Check one account live, at the sourceGET/accounts/{platform}/{handle}/livelivescrape:live
Check up to 50 accounts at oncePOST/accounts/checkdirectory:read
Find one handle on every platform at onceGET/accounts/resolvedirectory:read
Catalog capability mapGET/directorydirectory:read
List and filter a catalogGET/directory/{platform}directory:read.
One account or channelGET/directory/{platform}/{id}directory:read.
Daily audience historyGET/directory/{platform}/{id}/growthdirectory:read.
Inbound graph edgesGET/directory/{platform}/{id}/followersdirectory:read.
Outbound graph edgesGET/directory/{platform}/{id}/followingdirectory:read.
Resolve up to 100 records at oncePOST/directory/{platform}/bulkdirectory:read.
Distribution of the catalogGET/directory/{platform}/facetsdirectory:read.
Search every platform at onceGET/directory/searchdirectory:read.
Find one handle across every catalogGET/people/lookupdirectory:read.
Find accounts matching audience and engagement criteriaGET/people/searchdirectory:read.
Live X profileGET/scrape/x/userlivescrape:live......
Live X profiles, batchedPOST/scrape/x/userslivescrape:live......
Prove ownership of an X accountGET/scrape/x/verifylivescrape:live......
Live Instagram profileGET/scrape/instagram/userlivescrape:live......
Prove ownership of an Instagram accountGET/scrape/instagram/verifylivescrape:live......
Live Telegram channel readGET/scrape/telegram/channellivescrape:live......
Prove ownership of a Telegram channelGET/scrape/telegram/verifylivescrape:live......
Prove ownership of a Bluesky accountGET/scrape/bluesky/verifylivescrape:live......
Prove ownership of a TikTok accountGET/scrape/tiktok/verifylivescrape:live......
Prove ownership of a YouTube accountGET/scrape/youtube/verifylivescrape:live......
Live backend health and capacityGET/scrape/statusscrape:live
Complete intelligence dossier for one X accountGET/insights/x/{handle}insights:read......
Engagement metrics for one X accountGET/insights/x/{handle}/engagementinsights:read......
The account's permanently kept best postsGET/insights/x/{handle}/top-tweetsinsights:read......
What makes this account's posts performGET/insights/x/{handle}/viral-patternsinsights:read......
This account against its follower cohortGET/insights/x/{handle}/benchmarkinsights:read......
Accounts ranked by how well they engageGET/insights/x/leadersinsights:read......
How much of the catalog has tweet-level coverageGET/insights/x/coverageinsights:read......
Side-by-side metrics for up to ten accountsPOST/insights/x/compareinsights:read......
Who follows an account, and what they have in commonGET/insights/audienceinsights:read
Measured Instagram engagement, placed against its cohortGET/insights/instagram/{handle}insights:read......
How many subscribers actually see a Telegram postGET/insights/telegram/{username}/reachinsights:read......
The reach percentile ladders every Telegram score is read againstGET/insights/telegram/reach-bandsinsights:read......
Findings the research engine is willing to stateGET/research/findingsresearch:read
One finding with its full test historyGET/research/findings/{id}research:read
Research pass historyGET/research/runsresearch:read
The research changelogGET/research/eventsresearch:read
Operational health of every insight engineGET/enginesengines:read
One engine in detailGET/engines/{key}engines:read
Browse active marketplace listingsGET/marketplace/listingsmarketplace:read..
Get one public listingGET/marketplace/listings/{id}marketplace:read
Platform totalsGET/marketplace/statsmarketplace:read
Best-selling sellersGET/marketplace/leaderboardmarketplace:read
Live fee schedule and boost packagesGET/marketplace/pricingmarketplace:read
List every analysis toolGET/toolstools:use
Value an X accountPOST/tools/valuationlivetools:use
Check an X account for visibility filteringPOST/tools/shadowban-checklivetools:use
Audit an X account's followers for fakesPOST/tools/follower-auditlivetools:use
Measure an X account's engagement ratePOST/tools/engagement-calculatorlivetools:use
Find when an X account should postPOST/tools/best-posting-timelivetools:use
Score an X account against the ranking signalsPOST/tools/algorithm-scorelivetools:use
Score and rewrite an X bio (AI)POST/tools/bio-optimizerlivetools:use
Roast and fix an X profile (AI)POST/tools/profile-roastlivetools:use
Analyze a single X postPOST/tools/tweet-analyzerlivetools:use
See who is mentioning an X accountPOST/tools/mention-checkerlivetools:use
Project how long a follower target takesPOST/tools/growth-simulatortools:use
Fetch an X profile picture URLGET/tools/profile-piclivetools:use
Search the Telegram, TikTok and X catalogGET/tools/audience-findertools:use....
Measure an Instagram account's engagementPOST/tools/instagram/engagementlivetools:use......
Estimate Instagram sponsorship earningsPOST/tools/instagram/moneylivetools:use......
Measure a TikTok account's engagementPOST/tools/tiktok/engagementlivetools:use......
Estimate TikTok brand-deal earningsPOST/tools/tiktok/moneylivetools:use......
Estimate YouTube ad earningsPOST/tools/youtube/moneylivetools:use......
Estimate what a YouTube channel is worthPOST/tools/youtube/channel-valuelivetools:use......
LinkedIn company records, filterableGET/leads/linkedin/companiesleads:read......
One company with socials, contacts and decision makersGET/leads/linkedin/companies/{id}leads:read......
Named people and decision makersGET/leads/linkedin/peopleleads:read......
Follower-graph export for one X accountGET/leads/x/followersleads:read......

Coverage counts a capability once per platform it serves, plus every capability that is not platform specific.

Coverage is about surface area, not depth. How fresh and how complete each catalog is belongs to the engine health endpoints.

Endpoint reference

Every endpoint, its parameters, a copyable sample in three languages and the exact response envelope. Switching the sample language on one card switches it on all of them.

Accounts

directory:readlive5 endpoints

Check one account, on any of the seven platforms, in one shape: does it exist, how big is it, is it verified, when was it created, is it in our catalog and how stale is that reading. The catalog checks are index probes and cost nothing; add /live to read the platform itself.

GET/api/v2/accounts/platformsdirectory:read

What a check can answer, per platform

The capability map for the account surface: for each of the seven platforms, whether a catalog check is available, which backend the live check reads, how many live reads per minute that source allows across all callers, and whether ownership can be proved with a bio code. Read this once before you integrate - it is static, free, and it tells you which fields will be null on which platform before you discover it in production.

Operation id accounts.platforms - requires directory:read

  • -precision: rounded means the platform rounds before we ever see the number. YouTube has done this to subscriber counts since 2019, so a channel on 1,243,912 reports 1.24M and no amount of polling recovers the rest.
  • -catalog.available is false only for LinkedIn, and that is a policy decision rather than a coverage gap: we hold LinkedIn company data under the leads:read scope for our own outreach and do not republish it cheaply. The live check still covers LinkedIn.
  • -live.reads_per_minute is a ceiling on the SOURCE, shared across every caller, not a per-key limit. It is null on X and Instagram, where the lookup service in front of the source runs its own concurrency control and your tier's live limit is the only ceiling you will meet. It is a real number on the five platforms we scrape directly, and it is four a minute on LinkedIn because company pages are heavy and LinkedIn blocks anything that reads them faster.
  • -ownership_verification is offered on six of the seven. LinkedIn is the exception, and it is the source rather than the feature: a company page carries no owner-editable text we can read logged out, so there is nothing for a code to live in. It is not offered-but-broken anywhere.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/accounts/platforms" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platforms": [
      {
        "platform": "x",
        "label": "X",
        "object": "account",
        "audience_metric": "followers",
        "precision": "exact",
        "catalog": {
          "available": true,
          "note": null,
          "fields": [
            "created_at",
            "citation_score",
            "country"
          ],
          "directory_path": "/best-x-accounts"
        },
        "live": {
          "available": true,
          "source": "live:xlookup",
          "reads_per_minute": null,
          "note": "Read live from our own X scraper. Exact follower count, no rounding."
        },
        "ownership_verification": {
          "available": true,
          "field": "bio",
          "endpoint": "/scrape/x/verify"
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/accounts/{platform}/{handle}directory:read

Check one account against our catalog

Everything our crawlers know about one handle on one platform, in a shape that is identical across all seven: identity, audience, verification, creation date where the platform publishes one, and how old the reading is. This is the cheap check - one index probe, no upstream, bounded only by your tier. Add /live when you need the number as it is this second.

Operation id accounts.check - requires directory:read

  • -exists is true when a source confirmed the account and null when nothing authoritative was asked. It is NEVER set to false by a catalog miss: our crawlers track the accounts worth ranking, which is a small slice of any platform, so catalog.state=absent means we have not indexed it and nothing more. Call /live for a real existence answer.
  • -Every count is number or null, and null means not measured. It never means zero.
  • -catalog.data_age_s is how stale the reading is. Telegram is the one platform whose freshness comes from a different column, and channels with no stored fetch timestamp report null rather than a guess.
  • -created_at is populated on X, Bluesky and YouTube. Instagram, TikTok and Telegram do not publish an account creation date on any surface we read, so it is null there rather than estimated.
  • -catalog.metrics carries only what that platform genuinely measures. Instagram is the one catalog whose crawl returns per-post like and comment counts, so it is the one that reports a measured engagement rate; Telegram is the one that gets average post views.
  • -Accounts flagged as adult content are excluded from every v2 catalog read, so one reports as absent here.
  • -This addresses accounts by HANDLE only. To look one up by its platform id - which survives a rename - use GET /directory/{platform}/{id}, which accepts either.

Parameters

NameInTypeDescription
platformreqpathstringWhich platform the handle belongs to.One of: x, instagram, tiktok, youtube, telegram, bluesky, linkedin
handlereqpathstringThe account handle, with or without the @. Bluesky handles are domains and a bare name is completed to <name>.bsky.social. LinkedIn takes the company slug.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/accounts/instagram/nasa" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "instagram",
    "handle": "nasa",
    "checked_at": "2026-08-30T09:14:02.113Z",
    "exists": true,
    "evidence": "catalog",
    "identity": {
      "id": "528817151",
      "handle": "nasa",
      "name": "NASA",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
      "url": "https://www.instagram.com/nasa/",
      "object": "account"
    },
    "audience": {
      "count": 104443318,
      "metric": "followers",
      "precision": "exact"
    },
    "flags": {
      "verified": true,
      "private": false,
      "nsfw": false
    },
    "created_at": null,
    "catalog": {
      "state": "present",
      "status": "active",
      "tier": "a",
      "category": "science",
      "language": "en",
      "country": null,
      "last_seen": "2026-08-30T04:02:11.000Z",
      "data_age_s": 18711,
      "directory_url": "/best-instagram-accounts/nasa",
      "metrics": {
        "engagement_rate": 0.4213,
        "avg_likes": 402118,
        "avg_comments": 2904,
        "posts_analyzed": 12,
        "citation_score": 91
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/accounts/{platform}/{handle}/livescrape:livelive

Check one account live, at the source

Reads the platform right now and folds our catalog in behind it, so a single call answers both 'does this account exist and how big is it this second' and 'what have we been watching it do'. This is the endpoint to put in a fraud review, a listing check or a hand-off. It is metered as a live call because every one of them spends real upstream capacity.

Operation id accounts.live - requires scrape:live(sensitive scope, granted only on request)

  • -live.state is the whole answer. `live` means the number is real. `not_found` means the source was asked and said no, so exists is false - that is an answer, returned as a 200, not an error. `unreadable` means the account may well exist but the source will not publish the number: a protected X account, a private Instagram profile, a YouTube channel hiding its subscriber count, a Telegram group with no public member count, or a TikTok bot challenge. exists stays null there.
  • -502 and 504 mean our side or the source failed, not that the account is missing. Retry those; never write a not_found into your database on the strength of a 504.
  • -On the five platforms we scrape directly - Telegram, Bluesky, TikTok, YouTube and LinkedIn - this draws on an upstream budget shared with the rest of the product, and a spent budget is a 429 that names the source and its ceiling. Slow down rather than retrying harder; GET /accounts/platforms publishes every ceiling up front. LinkedIn's is four a minute and is not negotiable: company pages are heavy and LinkedIn blocks anything that reads them faster.
  • -X and Instagram carry no ceiling of their own here, because the lookup services in front of them already run one across every caller including our crawler. Your tier's live limit is what you will meet there. Both of those reads still cost real upstream capacity that the seller-facing listing flow shares, so treat the catalog as the default and this as the exception.
  • -The catalog half is best-effort: if the engine database is busy you still get the live reading, with catalog.state=unavailable.
  • -created_at is only ever populated from a live read on X. No other source we read publishes one.

Parameters

NameInTypeDescription
platformreqpathstringWhich platform the handle belongs to.One of: x, instagram, tiktok, youtube, telegram, bluesky, linkedin
handlereqpathstringThe account handle, with or without the @. LinkedIn takes the company slug.
catalogquerybooleanInclude what our catalog knows alongside the live reading. Costs one index probe. Turn it off if you only want the live number.Default: true

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/accounts/instagram/nasa/live?catalog=true" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "instagram",
    "handle": "nasa",
    "checked_at": "2026-08-30T09:14:02.113Z",
    "exists": true,
    "evidence": "live",
    "identity": {
      "id": "528817151",
      "handle": "nasa",
      "name": "NASA",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
      "url": "https://www.instagram.com/nasa/",
      "object": "account"
    },
    "audience": {
      "count": 104451002,
      "metric": "followers",
      "precision": "exact"
    },
    "flags": {
      "verified": true,
      "private": false,
      "nsfw": false
    },
    "created_at": null,
    "catalog": {
      "state": "present",
      "status": "active",
      "tier": "a",
      "category": "science",
      "language": "en",
      "country": null,
      "last_seen": "2026-08-30T04:02:11.000Z",
      "data_age_s": 18711,
      "directory_url": "/best-instagram-accounts/nasa",
      "metrics": {
        "engagement_rate": 0.4213,
        "avg_likes": 402118,
        "avg_comments": 2904,
        "posts_analyzed": 12,
        "citation_score": 91
      }
    },
    "live": {
      "state": "live",
      "source": "live:iglookup",
      "read_at": "2026-08-30T09:14:02.980Z",
      "detail": null,
      "secondary": {
        "following": 78,
        "posts": 4318
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/accounts/checkdirectory:read

Check up to 50 accounts at once

The bulk form of the catalog check: an array of { platform, handle } pairs, each answered independently in the same shape. Built for enriching a list - a CRM export, an influencer roster, a due-diligence sheet - in one round trip instead of fifty.

Operation id accounts.bulk - requires directory:read

  • -One request is one request against your rate limit however many targets it carries. The database work is real per target, so batch to be efficient rather than to evade the limit.
  • -The cap is 50 because each target is a probe on a different table and the read pool is shared with every directory page on the site.
  • -The envelope stays 200 whenever the request was well formed, even if every target failed. Branch on results[].ok and on check.catalog.state, not on the status code.
  • -`found` counts the targets whose catalog state is present. A target that is merely not indexed is not an error.
  • -This is the catalog check only. There is no bulk live check, and that is deliberate: fifty upstream reads in one call would spend more of a source's per-minute budget than any single caller is allowed to hold.

Parameters

NameInTypeDescription
targetsreqbodyarrayArray of { platform, handle } objects, at most 50. Duplicate pairs are collapsed.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/accounts/check" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "targets": [
    {
      "platform": "x",
      "handle": "nasa"
    },
    {
      "platform": "instagram",
      "handle": "nasa"
    }
  ]
}'
json
{
  "data": {
    "requested": 2,
    "found": 2,
    "results": [
      {
        "platform": "x",
        "handle": "nasa",
        "ok": true,
        "check": {
          "platform": "x",
          "handle": "nasa",
          "checked_at": "2026-08-30T09:14:02.113Z",
          "exists": true,
          "evidence": "catalog",
          "identity": {
            "id": "528817151",
            "handle": "nasa",
            "name": "NASA",
            "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
            "url": "https://www.instagram.com/nasa/",
            "object": "account"
          },
          "audience": {
            "count": 104443318,
            "metric": "followers",
            "precision": "exact"
          },
          "flags": {
            "verified": true,
            "private": false,
            "nsfw": false
          },
          "created_at": null,
          "catalog": {
            "state": "present",
            "status": "active",
            "tier": "a",
            "category": "science",
            "language": "en",
            "country": null,
            "last_seen": "2026-08-30T04:02:11.000Z",
            "data_age_s": 18711,
            "directory_url": "/best-instagram-accounts/nasa",
            "metrics": {
              "engagement_rate": 0.4213,
              "avg_likes": 402118,
              "avg_comments": 2904,
              "posts_analyzed": 12,
              "citation_score": 91
            }
          }
        }
      },
      {
        "platform": "instagram",
        "handle": "nasa",
        "ok": true,
        "check": {
          "platform": "instagram",
          "handle": "nasa",
          "checked_at": "2026-08-30T09:14:02.113Z",
          "exists": true,
          "evidence": "catalog",
          "identity": {
            "id": "528817151",
            "handle": "nasa",
            "name": "NASA",
            "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
            "url": "https://www.instagram.com/nasa/",
            "object": "account"
          },
          "audience": {
            "count": 104443318,
            "metric": "followers",
            "precision": "exact"
          },
          "flags": {
            "verified": true,
            "private": false,
            "nsfw": false
          },
          "created_at": null,
          "catalog": {
            "state": "present",
            "status": "active",
            "tier": "a",
            "category": "science",
            "language": "en",
            "country": null,
            "last_seen": "2026-08-30T04:02:11.000Z",
            "data_age_s": 18711,
            "directory_url": "/best-instagram-accounts/nasa",
            "metrics": {
              "engagement_rate": 0.4213,
              "avg_likes": 402118,
              "avg_comments": 2904,
              "posts_analyzed": 12,
              "citation_score": 91
            }
          }
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/accounts/resolvedirectory:read

Find one handle on every platform at once

Takes a handle and checks it against every platform we run - all six catalogs plus LinkedIn - returning the same check shape for each, the combined audience, and the observable evidence for whether they belong to the same owner. One call to open a research task, a brand-protection sweep or an impersonation check.

Operation id accounts.resolve - requires directory:read

  • -This matches a STRING against each catalog. It does not prove one owner: handle collision is common and there is no cross-platform identity key to check against. The summary publishes the evidence - exact handle, verification badge, display-name agreement - and stops there rather than inventing a confidence score.
  • -A handle that cannot be valid on a platform is skipped rather than failing the call, and is listed in summary.unavailable with the reason. X caps handles at 15 characters and Instagram at 30, so a 20-character name is a real question about one and not about the other.
  • -LinkedIn always answers with catalog.state=not_exposed rather than appearing in summary.unavailable: the check ran, and the answer is that we do not serve LinkedIn company records at this scope.
  • -summary.total_audience is reach, not people. The same follower can appear on several platforms and there is no way to de-duplicate them.
  • -For the platform-native record rather than the check shape, GET /people/lookup returns the same six catalogs with every column each one carries.

Parameters

NameInTypeDescription
handlereqquerystringOne handle, with or without the @.
platformsquerystringComma-separated subset to check. Defaults to all seven.One of: x, instagram, tiktok, youtube, telegram, bluesky, linkedin

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/accounts/resolve?handle=nasa&platforms=x%2Cinstagram%2Ctiktok" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "handle": "nasa",
    "checks": [
      {
        "platform": "instagram",
        "handle": "nasa",
        "checked_at": "2026-08-30T09:14:02.113Z",
        "exists": true,
        "evidence": "catalog",
        "identity": {
          "id": "528817151",
          "handle": "nasa",
          "name": "NASA",
          "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
          "url": "https://www.instagram.com/nasa/",
          "object": "account"
        },
        "audience": {
          "count": 104443318,
          "metric": "followers",
          "precision": "exact"
        },
        "flags": {
          "verified": true,
          "private": false,
          "nsfw": false
        },
        "created_at": null,
        "catalog": {
          "state": "present",
          "status": "active",
          "tier": "a",
          "category": "science",
          "language": "en",
          "country": null,
          "last_seen": "2026-08-30T04:02:11.000Z",
          "data_age_s": 18711,
          "directory_url": "/best-instagram-accounts/nasa",
          "metrics": {
            "engagement_rate": 0.4213,
            "avg_likes": 402118,
            "avg_comments": 2904,
            "posts_analyzed": 12,
            "citation_score": 91
          }
        }
      }
    ],
    "summary": {
      "found_on": [
        "instagram",
        "x",
        "youtube",
        "tiktok"
      ],
      "verified_on": [
        "instagram",
        "x",
        "youtube"
      ],
      "unavailable": [],
      "total_audience": 191204882,
      "largest_platform": "instagram",
      "names": [
        "nasa"
      ],
      "names_agree": true
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Directory

directory:read9 endpoints

The account and channel catalog the crawlers maintain across X, Instagram, TikTok, YouTube, Telegram and Bluesky. Reads are served from our own Postgres, so they are fast, cheap and bounded only by your tier.

GET/api/v2/directorydirectory:read

Catalog capability map

One machine-readable description per platform: the object type, an estimated catalog size, how fresh the daily rollup is, and every field, sort key, filter and facet that platform accepts. Read this once at integration time and drive your client from it rather than hardcoding field names - the catalogs do not all carry the same columns.

Operation id directory.index - requires directory:read

  • -Catalog sizes are planner row estimates (pg_class.reltuples), not counts. An exact count(*) over an 18M-row catalog takes 15s+ and would time out.
  • -freshness.daily_stats_through is the most recent day the engine rollup has written for that platform, which is the honest answer to 'how current is this data'.
  • -Cached for 60 seconds.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platforms": [
      {
        "platform": "x",
        "object": "account",
        "id_field": "username",
        "addressable_by": [
          "username",
          "user_id"
        ],
        "catalog": {
          "estimated_rows": 18312640,
          "estimate_source": "pg_class_reltuples"
        },
        "freshness": {
          "daily_stats_through": "2026-08-22"
        },
        "fields": [
          "user_id",
          "username",
          "name",
          "followers_count",
          "weekly_growth"
        ],
        "sorts": [
          "id",
          "followers",
          "following",
          "tweets",
          "likes",
          "media",
          "citations",
          "created"
        ],
        "default_sort": "followers",
        "filters": [
          "category",
          "language",
          "country",
          "tier",
          "min_followers",
          "max_followers"
        ],
        "search": {
          "fulltext": true,
          "substring": true
        },
        "facets": {
          "language": "rollup",
          "country": "rollup",
          "category": "sample",
          "followers": "sample"
        },
        "follower_graph": {
          "supported": true,
          "kind": "follows",
          "note": "Real follow edges, but only among accounts already in the catalog."
        },
        "endpoints": {
          "list": "/api/v2/directory/x",
          "detail": "/api/v2/directory/x/{id}",
          "growth": "/api/v2/directory/x/{id}/growth",
          "followers": "/api/v2/directory/x/{id}/followers",
          "following": "/api/v2/directory/x/{id}/following",
          "bulk": "/api/v2/directory/x/bulk",
          "facets": "/api/v2/directory/x/facets"
        }
      }
    ],
    "defaults": {
      "limit": 50,
      "max_limit": 500,
      "scope": "addressable"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/directory/{platform}directory:read

List and filter a catalog

The main catalog read: cursor-paginated, filterable and sortable. Pagination is always keyset (never OFFSET), so a full-corpus walk stays the same cost on page 1 and page 100,000 - follow meta.page.next_cursor until it is null.

Operation id directory.list - requires directory:read

  • -Unknown query parameters are rejected with 422 rather than ignored, so a typo in a filter never silently returns the unfiltered catalog.
  • -meta.source tells you how the page was produced: `catalog` is a direct index-served read; `catalog:audience_window(20000)` and `catalog:fulltext_window(20000)` mean the result was drawn from a bounded candidate window (see below).
  • -Candidate windows: a sort on a column with no index, a substring search, a derived-metric filter (min_follower_ratio, min_posts_per_day) or a full-text query is resolved inside the top 20,000 rows by audience (or the first 20,000 full-text matches). Ranking inside that window is exact; a term matching more rows than the window is ranked from the window, not from the whole catalog. This cap is why these queries answer in ~100ms instead of timing out - an unbounded full-text sort measured 33.5s on this catalog.
  • -sort=growth is deliberately not offered: weekly growth is a per-row snapshot lookup, so ordering the catalog by it means computing it for every row. Use /directory/{platform}/{id}/growth for a single account's history.
  • -Ranked sorts exclude rows whose sort value is NULL, since a NULL cannot be positioned in a keyset.
  • -Telegram's addressable scope is public channels only (type = 'channel'); groups are in the corpus but not in the ranked listing, because the ordered index is built over channels.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
limitqueryintegerRows per page, 1 to 500.Default: 50
cursorquerystringOpaque keyset position from meta.page.next_cursor. Signed; a hand-written cursor is rejected.
sortquerystringSort key. `id` walks the primary key and is the cheapest full-corpus order; the platform's audience metric (followers / subscribers) is the default and is index-backed to any depth. Other keys are served from an audience-ordered candidate window - see notes.Default: followers (subscribers on YouTube and Telegram)
orderquerystringSort direction.One of: asc, descDefault: desc
qquerystringFull-text search over name and bio, served by the catalog's GIN index. Available on X, Instagram, TikTok, YouTube and Telegram; Bluesky has no such index and returns 422 with a pointer to `search`.
searchquerystringSubstring match on handle and display name. Unindexed by construction, so it is applied inside the audience-ordered candidate window rather than to the whole catalog.
fieldsquerystringComma-separated projection. Any field listed by GET /directory for that platform, plus weekly_growth. Omitting weekly_growth skips a per-row snapshot lookup.
scopequerystring`addressable` (default) is the active, non-NSFW, handled catalog - the rows a public page can link to, and the only scope the ranked indexes cover. `all` is the full corpus including suspended, NSFW and handle-less rows, and can only be walked with sort=id.One of: addressable, allDefault: addressable
min_followersqueryintegerAudience floor. Every platform has a min/max pair on its own metric: min_followers (X, Bluesky, TikTok), min_participants (Telegram), min_subscribers (YouTube).
countryquerystringISO 3166-1 alpha-2, as inferred by the engine's geo classifier. Not available on TikTok.
languagequerystringISO 639-1, as inferred by the engine's language classifier.
categoryquerystringTopic category slug.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x?limit=100&sort=citations&order=desc" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "data": [
      {
        "user_id": "44196397",
        "username": "elonmusk",
        "name": "Elon Musk",
        "description": "",
        "followers_count": 221384902,
        "following_count": 1102,
        "statuses_count": 79431,
        "media_count": 4612,
        "favourites_count": 152889,
        "url": null,
        "is_verified": false,
        "is_blue_verified": true,
        "verified_type": null,
        "is_automated": false,
        "location": "",
        "photo_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
        "banner_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
        "account_created_at": "2009-06-02T20:12:29+00:00",
        "language": "en",
        "country": "US",
        "category": "technology",
        "citation_score": 41882,
        "tier": "S",
        "status": "active",
        "is_nsfw": false,
        "last_seen": "2026-08-22T22:14:07+00:00",
        "weekly_growth": 412004
      }
    ],
    "meta": {
      "request_id": "req_9f2c1a7b4e5d8c30",
      "generated_at": "2026-08-23T09:14:22.318Z",
      "took_ms": 121,
      "page": {
        "limit": 50,
        "next_cursor": "eyJzIjoiZm9sbG93ZXJzIiwibyI6ImRlc2MifQ.Yk9Qb1RrN0hs",
        "count": 50
      },
      "source": "catalog"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/directory/{platform}/{id}directory:read

One account or channel

A single catalog record by handle or by platform id, with up to five notable inbound edges. Use ?include= to fold growth history, the top known followers, engagement and lookalike accounts into the same call instead of making four.

Operation id directory.get - requires directory:read

  • -notable_followers is capped at five and is drawn from the edges we hold, not from the platform's real follower list.
  • -include=engagement is X-only: xdir_account_engagement is the only engagement table in the catalog. The full engagement surface is under /insights.
  • -include=similar matches on audience band (0.4x to 2.5x) and language, ranked by closeness in audience. It is not a content-similarity model.
  • -A handle that has been reused resolves to the largest account holding it.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
idreqpathstringHandle (case-insensitive, with or without @) or platform id. X, Instagram and TikTok accept the numeric user id, YouTube accepts the channel id, Bluesky accepts the DID, Telegram accepts the numeric peer id.
includequerystringComma-separated extras. growth = last 30 daily points. followers = top 10 known inbound edges by audience. engagement = headline engagement metrics (X only). similar = up to 10 accounts in the same audience band and language.One of: growth, followers, engagement, similar

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x/elonmusk?include=growth%2Csimilar" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "x",
    "object": "account",
    "record": {
      "user_id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk",
      "description": "",
      "followers_count": 221384902,
      "following_count": 1102,
      "statuses_count": 79431,
      "media_count": 4612,
      "favourites_count": 152889,
      "url": null,
      "is_verified": false,
      "is_blue_verified": true,
      "verified_type": null,
      "is_automated": false,
      "location": "",
      "photo_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
      "banner_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
      "account_created_at": "2009-06-02T20:12:29+00:00",
      "language": "en",
      "country": "US",
      "category": "technology",
      "citation_score": 41882,
      "tier": "S",
      "status": "active",
      "is_nsfw": false,
      "last_seen": "2026-08-22T22:14:07+00:00",
      "weekly_growth": 412004,
      "notable_followers": [
        {
          "handle": "BarackObama",
          "name": "Barack Obama",
          "metric": 131402119
        }
      ]
    },
    "growth": [
      {
        "day": "2026-08-21",
        "value": 221309004,
        "delta": 41221
      }
    ],
    "similar": [
      {
        "user_id": "10228272",
        "username": "YouTube",
        "name": "YouTube",
        "followers_count": 78112004,
        "language": "en",
        "country": "US",
        "category": "technology"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/directory/{platform}/{id}/growthdirectory:read

Daily audience history

The engine's end-of-day rollup for one account or channel: a point per day with the day-over-day delta, plus a summary carrying the change, the percentage change and the average daily change over the window.

Operation id directory.growth - requires directory:read

  • -Coverage starts when the engine first crawled the account, so a recently discovered account has a short series regardless of ?days=.
  • -A missing day means no snapshot was captured that day, not zero growth. Gaps are left as gaps rather than interpolated.
  • -delta is the engine's stored day-over-day change; summary.change is computed across the returned window.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
idreqpathstringHandle (case-insensitive, with or without @) or platform id. X, Instagram and TikTok accept the numeric user id, YouTube accepts the channel id, Bluesky accepts the DID, Telegram accepts the numeric peer id.
daysqueryintegerHow far back to read, 1 to 365 days.Default: 90

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x/elonmusk/growth?days=180" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "x",
    "id": "elonmusk",
    "metric": "followers_count",
    "days": 90,
    "points": [
      {
        "day": "2026-05-26",
        "value": 219884113,
        "delta": 38902
      },
      {
        "day": "2026-05-27",
        "value": 219931450,
        "delta": 47337
      }
    ],
    "summary": {
      "first_day": "2026-05-26",
      "last_day": "2026-08-22",
      "first_value": 219884113,
      "last_value": 221384902,
      "change": 1500789,
      "change_pct": 0.68,
      "avg_daily_change": 16675.43,
      "days_covered": 89
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/directory/{platform}/{id}/followersdirectory:read

Inbound graph edges

The accounts we know point AT this one. Paginated by peer id so the whole known set can be walked. The response states what the edges mean on that platform, and reports both the audience the platform reports and how many edges we actually hold - they are different numbers and must not be confused.

Operation id directory.followers - requires directory:read

  • -known_edges is what WE hold, not the platform's follower count: 37,812 of @elonmusk's 241M followers, 1,725 of @RTErdogan's 19.8M. Never present it as a follower count.
  • -Telegram has no follower graph and returns 422. It stores forward and mention edges between channels, which are a citation signal, not a subscriber list.
  • -YouTube edges are featured-channel relations, TikTok edges are the suggested-accounts graph and Instagram edges are related profiles. All three are returned under this path with graph.kind saying so - none of them is a subscriber list.
  • -Ordered by peer id, which is the only ordering that can be paged to the end of a graph without re-reading it. Ranking by audience within the page is left to the client.
  • -known_edges is counted through a 250,000-row cap; known_edges_capped: true means the real in-degree is at least that.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
idreqpathstringHandle (case-insensitive, with or without @) or platform id. X, Instagram and TikTok accept the numeric user id, YouTube accepts the channel id, Bluesky accepts the DID, Telegram accepts the numeric peer id.
limitqueryintegerEdges per page, 1 to 500.Default: 50
cursorquerystringKeyset position from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x/elonmusk/followers?limit=50" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "target": {
      "id": "44196397",
      "handle": "elonmusk",
      "name": "Elon Musk",
      "reported_metric": 221384902,
      "known_edges": 37812,
      "known_edges_capped": false
    },
    "graph": {
      "direction": "followers",
      "kind": "follows",
      "note": "Real follow edges, but only among accounts already in the catalog."
    },
    "edges": [
      {
        "peer_id": "813286",
        "edge_type": "follows",
        "weight": 1,
        "edge_seen": "2026-08-19T04:31:52+00:00",
        "username": "BarackObama",
        "name": "Barack Obama",
        "followers_count": 131402119,
        "language": "en",
        "country": "US"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/directory/{platform}/{id}/followingdirectory:read

Outbound graph edges

The accounts this one points AT - the mirror of /followers, served off the edge table's primary key. Same envelope, same caveats about what an edge means per platform.

Operation id directory.following - requires directory:read

  • -The outbound side is served directly by the edge table's primary key, so it pages faster than /followers on high-degree accounts.
  • -Telegram returns 422: no follower graph.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
idreqpathstringHandle (case-insensitive, with or without @) or platform id. X, Instagram and TikTok accept the numeric user id, YouTube accepts the channel id, Bluesky accepts the DID, Telegram accepts the numeric peer id.
limitqueryintegerEdges per page, 1 to 500.Default: 50
cursorquerystringKeyset position from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x/elonmusk/following?limit=50" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "target": {
      "id": "44196397",
      "handle": "elonmusk",
      "name": "Elon Musk",
      "reported_metric": 221384902,
      "known_edges": 1102,
      "known_edges_capped": false
    },
    "graph": {
      "direction": "following",
      "kind": "follows",
      "note": "Real follow edges among catalogued accounts."
    },
    "edges": [
      {
        "peer_id": "1636590253",
        "edge_type": "follows",
        "weight": 1,
        "edge_seen": "2026-08-19T04:31:52+00:00",
        "username": "SpaceX",
        "name": "SpaceX",
        "followers_count": 34112009,
        "language": "en",
        "country": "US"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
POST/api/v2/directory/{platform}/bulkdirectory:read

Resolve up to 100 records at once

One round trip for a batch of handles or ids. Returns the records that exist and an explicit `missing` array for the inputs that do not, so a sync job can tell 'not in the catalog' from 'the call failed'.

Operation id directory.bulk - requires directory:read

  • -Duplicates are collapsed before the lookup; `requested` is the de-duplicated count.
  • -Handles are matched on lower(handle), which is the shape every platform's handle index is built on.
  • -This endpoint is not paginated - the 100-value cap is the page.

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
handlesbodyarrayUp to 100 handles. Case-insensitive, a leading @ is stripped. Send this or 'ids', not both.
idsbodyarrayUp to 100 platform ids (numeric for X, Instagram, TikTok and Telegram, channel id for YouTube, DID for Bluesky).

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/directory/x/bulk" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handles": [
    "elonmusk",
    "nasa",
    "thisdoesnotexist"
  ]
}'
json
{
  "data": {
    "requested": 3,
    "found": [
      {
        "user_id": "44196397",
        "username": "elonmusk",
        "name": "Elon Musk",
        "description": "",
        "followers_count": 221384902,
        "following_count": 1102,
        "statuses_count": 79431,
        "media_count": 4612,
        "favourites_count": 152889,
        "url": null,
        "is_verified": false,
        "is_blue_verified": true,
        "verified_type": null,
        "is_automated": false,
        "location": "",
        "photo_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
        "banner_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
        "account_created_at": "2009-06-02T20:12:29+00:00",
        "language": "en",
        "country": "US",
        "category": "technology",
        "citation_score": 41882,
        "tier": "S",
        "status": "active",
        "is_nsfw": false,
        "last_seen": "2026-08-22T22:14:07+00:00",
        "weekly_growth": 412004
      }
    ],
    "missing": [
      "thisdoesnotexist"
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/directory/{platform}/facetsdirectory:read

Distribution of the catalog

How the addressable catalog splits across language, country, category and audience band. X language and country come from an exact hourly rollup; every other axis is estimated from a block sample and labelled as such.

Operation id directory.facets - requires directory:read

  • -APPROXIMATE BY DESIGN. Every axis marked method: sample is a scaled block sample, not a count. An exact count(*) group by over an 18M-row catalog reads the whole table and blows the query timeout - this is the same reason the directory filter chips are precomputed rather than counted per request.
  • -Block sampling reads whole pages, so values that cluster by insertion order carry more noise than their share suggests. Treat small buckets as directional and large ones as reliable.
  • -X language and country are exact: the engine writes them into xdir_facets hourly.
  • -An axis that cannot be produced inside the query budget is returned as available: false with a reason, never as a slow scan or a silent zero.
  • -Facets always describe the addressable scope (active, non-NSFW, handled).

Parameters

NameInTypeDescription
platformreqpathstringWhich catalog to read.One of: x, telegram, bluesky, youtube, tiktok, instagram
axesquerystringComma-separated axes to compute. Defaults to all four.One of: language, country, category, followers

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/directory/x/facets?axes=language%2Cfollowers" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "x",
    "scope": "addressable",
    "estimated_rows": 18312640,
    "facets": {
      "language": {
        "available": true,
        "method": "rollup",
        "approximate": false,
        "values": [
          {
            "value": "en",
            "count": 7402118
          },
          {
            "value": "tr",
            "count": 242483
          }
        ]
      },
      "followers": {
        "available": true,
        "method": "sample",
        "approximate": true,
        "sampled_rows": 8904,
        "values": [
          {
            "value": "0-1k",
            "count": 11204882,
            "sampled": 5449
          },
          {
            "value": "1k-10k",
            "count": 4918330,
            "sampled": 2392
          }
        ]
      },
      "country": {
        "available": false,
        "reason": "tiktok has no 'country' in the catalog."
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

People

directory:read2 endpoints

Read the account and channel catalog across all seven platforms.

GET/api/v2/people/lookupdirectory:read

Find one handle across every catalog

Takes a handle and probes all six catalogs for it at once, returning a normalized profile per platform, the combined audience, and the evidence for whether they are the same account owner. Use this to open a research task: one call replaces six list queries and the field-name translation between them.

Operation id people.lookup - requires directory:read

  • -This matches a STRING against each catalog. It does not prove one owner: handle collision is common and there is no cross-platform identity key. The `match` block reports the observable evidence (exact handle, verified badge, name agreement) so you can judge; no confidence score is invented.
  • -engagement_rate is populated for Instagram only, because it is the one crawl that returns per-post like and comment counts. null means not measured on that platform, not zero.
  • -totals.audience adds the platform audiences together. It is reach, not people: the same follower can appear on several platforms.
  • -A catalog that times out is listed in `unavailable` and the rest of the answer still returns.

Parameters

NameInTypeDescription
handlereqquerystringHandle to resolve, with or without the @. Up to 10, comma-separated.
platformsquerystringComma-separated subset to probe (x, telegram, bluesky, youtube, tiktok, instagram). Defaults to all six.One of: x, telegram, bluesky, youtube, tiktok, instagram

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/people/lookup?handle=%3Chandle%3E" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "entries": [
      {
        "handle": "nasa",
        "profiles": [
          {
            "platform": "instagram",
            "object": "account",
            "id": "528817151",
            "handle": "nasa",
            "name": "NASA",
            "audience": 104443318,
            "audience_metric": "followers",
            "verified": true,
            "engagement_rate": 0.4213,
            "language": "en",
            "category": "science",
            "url": "https://instagram.com/nasa",
            "record": {
              "user_id": "528817151",
              "handle": "nasa",
              "follower_count": 104443318
            }
          }
        ],
        "totals": {
          "platforms": 4,
          "audience": 191204882,
          "largest_platform": "instagram"
        },
        "match": {
          "found_on": [
            "instagram",
            "x",
            "youtube",
            "tiktok"
          ],
          "verified_on": [
            "instagram",
            "x",
            "youtube"
          ],
          "names": [
            "nasa"
          ],
          "names_agree": true
        }
      }
    ],
    "unavailable": []
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Live scrape

scrape:livelive11 endpoints

On-demand reads that skip the catalog and fetch from the source right now. Slower and separately metered, because every call costs upstream work. Reach for these when you need one handle fresh, not for bulk enrichment.

GET/api/v2/scrape/x/userscrape:livelive

Live X profile

Reads one X profile from the source right now, bypassing the catalog. Use this when you need the number as it is this second - a listing check, a fraud review, a hand-off. For browsing, filtering or anything at volume, read the catalog instead: it is faster, free of upstream limits, and does not compete with the seller-facing listing flow for the same scraper pool.

Operation id scrape.x.user - requires scrape:live(sensitive scope, granted only on request)

  • -Served by our own scraper pool, so it costs no third-party credits. The paid twitterapi.io path is opt-in and disabled by default.
  • -A protected or suspended account answers 404: the account may exist, but no public profile can be read, and that is a final answer rather than a retryable one.
  • -504 means the upstream timed out and 502 means the pool is down. Both are worth retrying; a 404 is not.
  • -meta.cache_age_s is 0 when the answer came from a live fetch.

Parameters

NameInTypeDescription
handlereqquerystringX handle, with or without @. A full x.com URL is accepted and stripped.
freshquerybooleanBypass the upstream's 60-second burst cache. Only worth it when you are checking something that changed seconds ago; it costs a real pool slot.Default: false

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/x/user?handle=elonmusk&fresh=false" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "user_id": "44196397",
    "handle": "elonmusk",
    "display_name": "Elon Musk",
    "bio": "",
    "followers": 221384902,
    "following": 1102,
    "tweets": 79431,
    "is_verified": true,
    "avatar_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
    "header_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
    "account_age_days": 6290,
    "created_at": "2009-06-02T20:12:29.000Z",
    "source": "xlookup"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/scrape/x/usersscrape:livelive

Live X profiles, batched

Up to 25 handles in one call, fetched with bounded concurrency and returned with a per-handle verdict. One bad handle never fails the batch.

Operation id scrape.x.users - requires scrape:live(sensitive scope, granted only on request)

  • -One request is one live call against your rate limit however many handles it carries, which is the reason to batch rather than loop over /scrape/x/user. The upstream work is real per handle, so use it to be efficient, not to evade the limit.
  • -Concurrency is capped at 5 because the upstream pool is shared with the marketplace's own listing flow; pushing harder only queues behind yourself.
  • -The batch always uses the burst cache. If you need guaranteed-fresh reads, call /scrape/x/user with fresh=1 per handle and accept the slower pace.
  • -The envelope stays 200 whenever the request was well formed, even if every handle failed. Branch on results[].ok, not on the status code.

Parameters

NameInTypeDescription
handlesreqbodyarrayArray of X handles, at most 25. Duplicates are collapsed.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/scrape/x/users" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handles": [
    "elonmusk",
    "nasa",
    "handle_that_does_not_exist"
  ]
}'
json
{
  "data": {
    "requested": 3,
    "succeeded": 2,
    "failed": 1,
    "results": [
      {
        "handle": "elonmusk",
        "ok": true,
        "profile": {
          "user_id": "44196397",
          "handle": "elonmusk",
          "display_name": "Elon Musk",
          "bio": "",
          "followers": 221384902,
          "following": 1102,
          "tweets": 79431,
          "is_verified": true,
          "avatar_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
          "header_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
          "account_age_days": 6290,
          "created_at": "2009-06-02T20:12:29.000Z",
          "source": "xlookup"
        }
      },
      {
        "handle": "handle_that_does_not_exist",
        "ok": false,
        "error": {
          "code": "not_found",
          "message": "No X account with that handle."
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/x/verifyscrape:livelive

Prove ownership of an X account

The bio-code check the marketplace itself uses to let a seller prove they control an account: ask the owner to put a one-time code in their X bio, then call this. The read is always fresh, and the answer is a plain verified true/false plus the profile it was checked against.

Operation id scrape.x.verify - requires scrape:live(sensitive scope, granted only on request)

  • -Always a fresh read - the owner edited their bio seconds ago and a cached copy would reject a valid proof.
  • -The match is a plain substring of the bio: no case folding, no whitespace normalization. People paste the code between emoji and links, and every stricter rule we have tried rejected a real owner.
  • -verified: false is a 200, not an error. A non-200 means the account could not be read at all.
  • -Generate a fresh, unguessable code per verification and expire it. A short or reused code is trivially defeated by pasting it into any bio.

Parameters

NameInTypeDescription
handlereqquerystringX handle to check.
codereqquerystringThe code you asked the owner to place in their bio, 6 to 64 characters. Our own flow issues codes shaped PS-XXXX-XXXX from an unambiguous alphabet; yours can be anything as long as it is unguessable.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/x/verify?handle=elonmusk&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "handle": "elonmusk",
    "verified": false,
    "reason": "The code is not in the bio. Make sure the profile was saved, then retry.",
    "profile": {
      "user_id": "44196397",
      "handle": "elonmusk",
      "display_name": "Elon Musk",
      "bio": "",
      "followers": 221384902,
      "following": 1102,
      "tweets": 79431,
      "is_verified": true,
      "avatar_url": "https://pbs.twimg.com/profile_images/1936002956/elon_400x400.jpg",
      "header_url": "https://pbs.twimg.com/profile_banners/44196397/1739948056/1500x500",
      "account_age_days": 6290,
      "created_at": "2009-06-02T20:12:29.000Z",
      "source": "xlookup"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/instagram/userscrape:livelive

Live Instagram profile

Reads one Instagram profile from the source right now: the counts, the business fields, and the twelve most recent posts with their like and comment counts. That last part is what makes this different from every other live read in the API - Instagram is the one platform whose profile fetch returns real per-post engagement, so the rate you get back is measured rather than estimated.

Operation id scrape.instagram.user - requires scrape:live(sensitive scope, granted only on request)

  • -Served by our own scraper, so it costs no third-party credits. There is deliberately no paid fallback on this path.
  • -engagement_rate, avg_likes and avg_comments are computed from the returned posts. They are null - never 0 - when the account has no readable recent posts, because 'we could not measure it' and 'nobody engages with it' are not the same claim.
  • -A private account answers 404: it exists, but nothing about it is publicly readable, and that is a final answer rather than a retryable one.
  • -partial: true means only the fallback endpoint answered. The posts are real; the follower count was not read at all, so followers, following and posts come back null instead of zero.
  • -Every read spends an exit from a proxy pool shared with the Instagram and LinkedIn crawlers, on a monthly budget whose real ceiling we have never been told. This is the most expensive live read in the API - use the catalog for anything at volume.

Parameters

NameInTypeDescription
handlereqquerystringInstagram handle, with or without @. A full instagram.com URL is accepted and stripped.
freshquerybooleanBypass the lookup service's 60-second burst cache. Only worth it when you are checking something that changed seconds ago; it costs a real proxy exit.Default: false

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/instagram/user?handle=nasa&fresh=false" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "user_id": "528817151",
    "handle": "nasa",
    "display_name": "NASA",
    "bio": "Explore the universe and discover our home planet.",
    "followers": 104451002,
    "following": 78,
    "posts": 4318,
    "is_verified": true,
    "is_business": true,
    "category": "Government Organization",
    "external_url": "https://www.nasa.gov/",
    "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
    "engagement_rate": 0.4213,
    "avg_likes": 402118,
    "avg_comments": 2904,
    "posts_analyzed": 12,
    "recent_posts": [
      {
        "shortcode": "C9xExamplE",
        "taken_at": "2026-08-29T18:02:44.000Z",
        "like_count": 511204,
        "comment_count": 3118,
        "view_count": null,
        "is_video": false
      }
    ],
    "partial": false,
    "source": "iglookup"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/instagram/verifyscrape:livelive

Prove ownership of an Instagram account

The bio-code check the marketplace itself uses to let a seller prove they control an Instagram account: ask the owner to put a one-time code in their bio, then call this. The read is always fresh, and the answer is a plain verified true/false plus the profile it was checked against.

Operation id scrape.instagram.verify - requires scrape:live(sensitive scope, granted only on request)

  • -Always a fresh read - the owner edited their bio seconds ago and a cached copy would reject a valid proof.
  • -The match strips whitespace and folds case, unlike the X check which is an exact substring. Instagram bios wrap and people paste the code with a line break inside it they cannot see; rejecting a real owner over that is a support ticket, not a security control.
  • -verified: false is a 200, not an error. A non-200 means the bio could not be read at all.
  • -A private account cannot be verified: its bio is not public. That answers 404 with the reason.
  • -Generate a fresh, unguessable code per verification and expire it. A short or reused code is trivially defeated by pasting it into any bio.

Parameters

NameInTypeDescription
handlereqquerystringInstagram handle to check.
codereqquerystringThe code you asked the owner to place in their bio, 6 to 64 characters. Ours are shaped PS-XXXX-XXXX from an unambiguous alphabet; yours can be anything as long as it is unguessable.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/instagram/verify?handle=nasa&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "handle": "nasa",
    "verified": false,
    "reason": "The code is not in the bio. Make sure the profile was saved, then retry.",
    "profile": {
      "user_id": "528817151",
      "handle": "nasa",
      "display_name": "NASA",
      "bio": "Explore the universe and discover our home planet.",
      "followers": 104451002,
      "following": 78,
      "posts": 4318,
      "is_verified": true,
      "is_business": true,
      "category": "Government Organization",
      "external_url": "https://www.nasa.gov/",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
      "engagement_rate": 0.4213,
      "avg_likes": 402118,
      "avg_comments": 2904,
      "posts_analyzed": 12,
      "recent_posts": [
        {
          "shortcode": "C9xExamplE",
          "taken_at": "2026-08-29T18:02:44.000Z",
          "like_count": 511204,
          "comment_count": 3118,
          "view_count": null,
          "is_video": false
        }
      ],
      "partial": false,
      "source": "iglookup"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/telegram/channelscrape:livelive

Live Telegram channel read

Current title, description, type and member count for a public Telegram channel or group, read through the Bot API, enriched with whatever our catalog already knows about it.

Operation id scrape.telegram.channel - requires scrape:live(sensitive scope, granted only on request)

  • -Public channels only. A private chat is invisible to the Bot API unless our bot has been added as an admin, and answers 404 with that explanation.
  • -member_count_source says where the number came from: `bot` is live from Telegram, `directory` is our last crawl, `none` means neither could answer.
  • -photo_url is a proxy path on this domain, not a Telegram file URL - the real one embeds the bot token and is never returned.
  • -The `directory` block is null for a channel the crawler has not reached yet; that is a coverage gap, not an error.

Parameters

NameInTypeDescription
usernamereqquerystringPublic @username or a t.me link.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/telegram/channel?username=durov" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "username": "durov",
    "title": "Du rove",
    "description": "Thoughts from the Telegram founder.",
    "type": "channel",
    "members": 1402882,
    "member_count_source": "bot",
    "photo_url": "/api/tg-pp/durov",
    "invite_link": null,
    "directory": {
      "participants": 1401119,
      "is_verified": true,
      "category": "technology",
      "language": "en",
      "country": null,
      "avg_views": 402118
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/telegram/verifyscrape:livelive

Prove ownership of a Telegram channel

The channel-description equivalent of the bio-code check. A Telegram channel has no bio, but only somebody with admin rights can edit its description, which is exactly the property an ownership proof needs. Ask the owner to put a one-time code there, then call this.

Operation id scrape.telegram.verify - requires scrape:live(sensitive scope, granted only on request)

  • -The description we read is returned alongside the verdict, so an owner disputing a false negative can see exactly what we saw.
  • -The match strips whitespace and folds case: Telegram's description composer wraps at a fixed width and a pasted code routinely arrives with a newline through the middle of it.
  • -A channel with no description at all answers verified: false with that as the reason, rather than an error.
  • -Public channels only. A private chat is invisible to the Bot API unless our bot has been added as an admin, and answers 404.
  • -The same proof exists on X, Instagram, Bluesky, TikTok and YouTube. LinkedIn is the one platform without it: a company page carries no owner-editable field we can read logged out. GET /accounts/platforms publishes the map.

Parameters

NameInTypeDescription
usernamereqquerystringPublic @username or a t.me link.
codereqquerystringThe code you asked the owner to place in the channel description, 6 to 64 characters.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/telegram/verify?username=durov&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "username": "durov",
    "verified": false,
    "reason": "The code is not in the channel description. Make sure the change was saved, then retry.",
    "title": "Du rove",
    "description": "Thoughts from the Telegram founder."
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/bluesky/verifyscrape:livelive

Prove ownership of a Bluesky account

The same one-time-code proof as the other six platforms, matched against the Bluesky profile description: ask the owner to paste a code you generated into it, then call this. Only somebody who controls the account can edit that field, which is the entire property the proof rests on. The read is live, and the answer is a plain verified true/false plus the text it was checked against.

Operation id scrape.bluesky.verify - requires scrape:live(sensitive scope, granted only on request)

  • -Always a live read: the owner edited their profile description seconds ago and a cached copy would reject a proof that is sitting right there.
  • -The match strips whitespace and folds case, the same rule the Instagram and Telegram checks use. Bluesky's editor wraps, and a code pasted across a line break is invisible to the person who pasted it - rejecting a real owner over that is a support ticket, not a security control.
  • -verified: false is a 200, not an error. A non-200 means the profile could not be read at all, and 502 and 504 are both worth retrying.
  • -profile.bio is the exact text the code was matched against, so an owner disputing a false negative can see precisely what we saw.
  • -Served by the public Bluesky AppView, which needs no auth and no key. It is the cheapest and fastest verification of the seven.
  • -A profile with no description answers verified: false with that as the reason. The AppView omits the field entirely when it is empty, and this reports it as an empty description rather than as an unreadable one.
  • -Metered as a live call and drawn against the shared upstream budget for Bluesky, because this is a raw read with no lookup service in front of it. GET /scrape/status publishes what is left.
  • -Generate a fresh, unguessable code per verification and expire it. A short or reused code is trivially defeated by pasting it into any profile.

Parameters

NameInTypeDescription
handlereqquerystringBluesky handle. These are domains, and a bare name is completed to <name>.bsky.social, so 'alice' and 'alice.bsky.social' are the same request.
codereqquerystringThe code you asked the owner to place in their profile description, 6 to 64 characters. Ours are shaped PS-XXXX-XXXX from an unambiguous alphabet; yours can be anything as long as it is unguessable.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/bluesky/verify?handle=nasa.gov&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "bluesky",
    "handle": "nasa.gov",
    "verified": true,
    "reason": null,
    "profile": {
      "handle": "nasa.gov",
      "display_name": "NASA",
      "bio": "Explore the universe and discover our home planet. PS-7K4M-2QRT",
      "bio_field": "profile description",
      "audience": 412882,
      "audience_metric": "followers",
      "is_verified": true,
      "avatar_url": "https://..."
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/tiktok/verifyscrape:livelive

Prove ownership of a TikTok account

The same one-time-code proof as the other six platforms, matched against the TikTok bio: ask the owner to paste a code you generated into it, then call this. Only somebody who controls the account can edit that field, which is the entire property the proof rests on. The read is live, and the answer is a plain verified true/false plus the text it was checked against.

Operation id scrape.tiktok.verify - requires scrape:live(sensitive scope, granted only on request)

  • -Always a live read: the owner edited their bio seconds ago and a cached copy would reject a proof that is sitting right there.
  • -The match strips whitespace and folds case, the same rule the Instagram and Telegram checks use. TikTok's editor wraps, and a code pasted across a line break is invisible to the person who pasted it - rejecting a real owner over that is a support ticket, not a security control.
  • -verified: false is a 200, not an error. A non-200 means the profile could not be read at all, and 502 and 504 are both worth retrying.
  • -profile.bio is the exact text the code was matched against, so an owner disputing a false negative can see precisely what we saw.
  • -TikTok answers a bot challenge with HTTP 200 and a stub page. That is reported as a 502, not as a missing account: the account is fine and the block is transient, so retry rather than recording a failure against the handle.
  • -The bio is TikTok's `signature` field, which is what the app calls the bio.
  • -Metered as a live call and drawn against the shared upstream budget for TikTok, because this is a raw read with no lookup service in front of it. GET /scrape/status publishes what is left.
  • -Generate a fresh, unguessable code per verification and expire it. A short or reused code is trivially defeated by pasting it into any profile.

Parameters

NameInTypeDescription
handlereqquerystringTikTok handle, with or without the @.
codereqquerystringThe code you asked the owner to place in their bio, 6 to 64 characters. Ours are shaped PS-XXXX-XXXX from an unambiguous alphabet; yours can be anything as long as it is unguessable.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/tiktok/verify?handle=nasa&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "tiktok",
    "handle": "nasa",
    "verified": true,
    "reason": null,
    "profile": {
      "handle": "nasa",
      "display_name": "NASA",
      "bio": "The official NASA account PS-7K4M-2QRT",
      "bio_field": "bio",
      "audience": 4218004,
      "audience_metric": "followers",
      "is_verified": true,
      "avatar_url": "https://..."
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/youtube/verifyscrape:livelive

Prove ownership of a YouTube account

The same one-time-code proof as the other six platforms, matched against the YouTube channel description: ask the owner to paste a code you generated into it, then call this. Only somebody who controls the account can edit that field, which is the entire property the proof rests on. The read is live, and the answer is a plain verified true/false plus the text it was checked against.

Operation id scrape.youtube.verify - requires scrape:live(sensitive scope, granted only on request)

  • -Always a live read: the owner edited their channel description seconds ago and a cached copy would reject a proof that is sitting right there.
  • -The match strips whitespace and folds case, the same rule the Instagram and Telegram checks use. YouTube's editor wraps, and a code pasted across a line break is invisible to the person who pasted it - rejecting a real owner over that is a support ticket, not a security control.
  • -verified: false is a 200, not an error. A non-200 means the profile could not be read at all, and 502 and 504 are both worth retrying.
  • -profile.bio is the exact text the code was matched against, so an owner disputing a false negative can see precisely what we saw.
  • -With a YouTube Data API key configured the description comes from the API and is complete. Without one it comes from the channel page's og:description, which YouTube collapses onto a single line and truncates - so place the code near the START of the description. profile.bio always shows exactly what was matched, which is how you tell the two apart.
  • -A channel that hides its subscriber count cannot be verified on this path. The read that carries the description is the same read that carries the count, and YouTube refuses them as a unit; that comes back as a 502 saying so rather than as a false negative.
  • -Subscriber counts are rounded by YouTube itself to three significant figures above 1,000, so profile.audience is not exact. It has no bearing on the verification.
  • -Metered as a live call and drawn against the shared upstream budget for YouTube, because this is a raw read with no lookup service in front of it. GET /scrape/status publishes what is left.
  • -Generate a fresh, unguessable code per verification and expire it. A short or reused code is trivially defeated by pasting it into any profile.

Parameters

NameInTypeDescription
handlereqquerystringYouTube handle without the @, or a UC... channel id. Handles are case-sensitive on YouTube and are not folded.
codereqquerystringThe code you asked the owner to place in their channel description, 6 to 64 characters. Ours are shaped PS-XXXX-XXXX from an unambiguous alphabet; yours can be anything as long as it is unguessable.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/youtube/verify?handle=MrBeast&code=PS-7K4M-2QRT" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "youtube",
    "handle": "MrBeast",
    "verified": true,
    "reason": null,
    "profile": {
      "handle": "MrBeast",
      "display_name": "MrBeast",
      "bio": "SUBSCRIBE FOR A COOKIE PS-7K4M-2QRT",
      "bio_field": "channel description",
      "audience": 452000000,
      "audience_metric": "subscribers",
      "is_verified": true,
      "avatar_url": "https://..."
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/scrape/statusscrape:live

Live backend health and capacity

Whether the live-scrape backends are up, how much capacity they have, and what the batch limits are. Poll this before a large job and back off on `degraded` rather than discovering it through 502s.

Operation id scrape.status - requires scrape:live(sensitive scope, granted only on request)

  • -This endpoint requires scrape:live but does NOT itself hit a scraper - it is two health probes and is not metered as a live call.
  • -state: degraded means at least one backend is reachable but not serving; expect 502s from that platform.
  • -error_rate is cumulative since the lookup service last restarted, not a rolling window. A step change matters more than the absolute value.
  • -accounts is the size of the authenticated X pool. It is small by design - treat it as the real concurrency ceiling for everything you run.
  • -budgets is the per-source ceiling that the account live check draws against, shared across every caller of this instance. It is set far below what each source would tolerate because two of them - the Instagram proxy pool and the X scraper pool - are the same pools our own crawlers run on. `refused` climbing is the signal to slow down.
  • -Only the backends with a health probe are listed. A working Instagram lookup is confirmed by a real call to /scrape/instagram/user, not by this endpoint - reporting a health state we cannot actually observe would be worse than leaving it out.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/scrape/status" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "state": "operational",
    "backends": [
      {
        "name": "xlookup",
        "state": "up",
        "detail": "Serving from 4 authenticated account(s).",
        "metrics": {
          "ready": true,
          "accounts": 4,
          "uptime_s": 82140,
          "served": 19442,
          "errors": 118,
          "error_rate": 0.006,
          "cached_profiles": 812
        }
      },
      {
        "name": "telegram_bot",
        "state": "up",
        "detail": "Bot @playersells_bot is answering.",
        "metrics": {
          "bot": "playersells_bot"
        }
      }
    ],
    "budgets": [
      {
        "platform": "instagram",
        "per_minute": 10,
        "concurrency": 2,
        "available": 8,
        "in_flight": 1,
        "granted": 1204,
        "refused": 17
      },
      {
        "platform": "x",
        "per_minute": 20,
        "concurrency": 2,
        "available": 20,
        "in_flight": 0,
        "granted": 4881,
        "refused": 3
      }
    ],
    "limits": {
      "batch_max_handles": 25,
      "batch_concurrency": 5,
      "x_cache_ttl_s": 60,
      "x_pool_concurrency": 4
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Insights

insights:read12 endpoints

Derived engagement intelligence: engagement rates, top and viral posts, posting patterns and cohort benchmarks computed over the post layer.

GET/api/v2/insights/x/{handle}insights:read

Complete intelligence dossier for one X account

Everything we hold on one X account in a single call: catalog profile, engagement rates against both followers and impressions, posting-volume profile, permanently kept best posts, the virality patterns measured inside the account's own follower decile, and where its engagement rate lands on the cohort ladder. Sections degrade independently - an account the tweet engine has not reached still returns its profile plus coverage.tweets=false rather than a 404.

Operation id insights.x.dossier - requires insights:read

  • -Tweet-level coverage is a subset of the X catalog. Scanning cadence is follower-weighted, so the accounts with a reliable sample are the very large ones: the smallest account in the measured cohort sits near 1.7M followers and its tenth percentile near 1.8M. Call GET /insights/x/coverage for the live denominator, and read benchmark.population before quoting any rate.
  • -An account needs 8 captured original posts before its medians are treated as settled. Below that, coverage.reliable is false and the numbers are an early reading.
  • -Impressions are present on about 96% of captured original posts, so view_engagement_rate and reach_ratio rest on a slightly smaller sample than the follower-based rates.
  • -viral_patterns is cut inside the account's own follower decile when the engine assigned one, and falls back to the whole tracked cohort otherwise. peer_group tells you which.
  • -leaders, coverage and compare are reserved sub-paths under /insights/x and shadow X handles of the same name.

Parameters

NameInTypeDescription
handlereqpathstringX handle without the @. 1-15 characters, letters, digits and underscore.
includequerystringComma-separated sections to return, from: engagement, volume, top_tweets, viral_patterns, benchmark, narrative. Omit for all of them. account and coverage are always present.
metricquerystringWhich question the viral_patterns section answers.One of: engagement, views, engagement_per_view, bookmarksDefault: engagement

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/elonmusk?include=engagement%2Cbenchmark&metric=engagement" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "user_id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk",
      "followers": 241382119,
      "tier": "S",
      "is_blue_verified": true,
      "language": "en",
      "country": "US",
      "profile_url": "https://x.com/elonmusk"
    },
    "coverage": {
      "tweets": true,
      "reliable": true,
      "originals_sampled": 42,
      "tweets_sampled": 187,
      "window_days": 30,
      "computed_at": "2026-08-19T02:41:07.000Z",
      "min_reliable_sample": 8,
      "note": "Measured over a sample large enough to state as a reading."
    },
    "engagement": {
      "window_days": 30,
      "originals_sampled": 42,
      "rates": {
        "engagement_rate": 0.0471,
        "view_engagement_rate": 0.7425,
        "bookmark_rate": 0.0219,
        "reach_ratio": 1.67
      },
      "medians": {
        "median_engagement": 113804,
        "median_views": 15320411
      },
      "peer": {
        "peer_group": "d10",
        "peer_group_label": "Accounts of similar size (decile 10 of 10)",
        "peer_percentile": 94,
        "band": "Top 10% for its size"
      }
    },
    "benchmark": {
      "engagement_rate": 0.0471,
      "percentile": 75,
      "band": "Top 25%",
      "population": {
        "accounts": 3162,
        "min_followers": 1687752,
        "median_followers": 2840444
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/x/{handle}/engagementinsights:read

Engagement metrics for one X account

The engagement aggregate on its own: rates against followers and against impressions, the save rate, reach ratio, averages and medians, and the account's percentile inside its own follower decile. Same numbers as the engagement section of the dossier, without the rest of the payload.

Operation id insights.x.engagement - requires insights:read

  • -An account needs 8 captured original posts before its medians are treated as settled. Below that, coverage.reliable is false and the numbers are an early reading.
  • -engagement_rate divides by FOLLOWERS and view_engagement_rate divides by IMPRESSIONS. The two differ by roughly two orders of magnitude across the cohort, so never compare one against a figure quoted with the other denominator.
  • -peer_percentile is uniform by construction: 50 really is the middle of the same-size cohort and 90 really is the top tenth.

Parameters

NameInTypeDescription
handlereqpathstringX handle without the @.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/NASA/engagement" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "username": "NASA",
      "followers": 96204881,
      "tier": "S"
    },
    "coverage": {
      "tweets": true,
      "reliable": true,
      "originals_sampled": 31,
      "window_days": 30
    },
    "engagement": {
      "computed_at": "2026-08-19T02:41:07.000Z",
      "rates": {
        "engagement_rate": 0.0117,
        "view_engagement_rate": 0.7425,
        "bookmark_rate": 0.0142,
        "reach_ratio": 1.66
      },
      "averages": {
        "avg_likes": 14902,
        "avg_retweets": 2118,
        "avg_views": 1604233
      },
      "medians": {
        "median_likes": 9441,
        "median_engagement": 11308
      },
      "peer": {
        "peer_group": "d09",
        "peer_percentile": 62,
        "band": "Middle of its size range"
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/x/{handle}/top-tweetsinsights:read

The account's permanently kept best posts

The best-performing original posts the engine keeps forever for this account, with each post's viral multiple against the author's own median. The raw tweet stream is on a sliding retention window and is deliberately not read here, so these rows do not disappear as old data ages out.

Operation id insights.x.topTweets - requires insights:read

  • -The kept set is capped at 20 posts per account by the engine (XTWEETS_TOP_N).
  • -sort values other than engagement reorder the kept set in memory. They are not a ranking over the account's whole history.
  • -viral_multiple is engagement divided by the author's own median post, so 3.0 means triple their norm rather than anything about the catalog.

Parameters

NameInTypeDescription
handlereqpathstringX handle without the @.
limitqueryintegerRows to return, 1-100. The engine keeps at most 20 posts per account, so a higher limit simply returns everything there is.Default: 10
sortquerystringOrder within the kept set. engagement is the engine's own ranking; the others reorder the same rows.One of: engagement, likes, retweets, replies, recentDefault: engagement

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/BillGates/top-tweets?limit=20&sort=engagement" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "username": "BillGates",
      "followers": 65130442
    },
    "coverage": {
      "tweets": true,
      "reliable": true,
      "originals_sampled": 24
    },
    "sort": "engagement",
    "tweets": [
      {
        "tweet_id": "1908244251923312641",
        "rank": 1,
        "created_at": "2026-06-14T16:02:11.000Z",
        "url": "https://x.com/BillGates/status/1908244251923312641",
        "likes": 184221,
        "retweets": 21004,
        "replies": 9118,
        "views": 41882337,
        "engagement": 214343,
        "viral_multiple": 9.4,
        "has_media": true
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/insights/x/{handle}/viral-patternsinsights:read

What makes this account's posts perform

The measured effect of timing, format, length, media, links, hashtags, mentions and posting client, cut inside the account's own follower decile, plus a format-fit table lining the account's posting mix up against those catalog-wide effects. Every bucket carries its 95% interval, its per-account vote and whether it clears the publication bar.

Operation id insights.x.viralPatterns - requires insights:read

  • -A bucket is only publishable when its 95% interval clears zero after a Benjamini-Hochberg correction and at least 30 independent accounts contributed a paired comparison. Measured on 2026-08-19, 137 of 904 rows that pass the uncorrected test do not survive the correction.
  • -accounts_sampled is the evidence, not sample_size. Tweets inside one account are not independent observations of how X behaves; accounts are.
  • -format_fit applies CATALOG-wide effects to this account's own posting mix. We keep one median per account, not one per format per account, so it cannot tell you how video performs for this specific account.
  • -not_measured=true on a dimension means the comparison could not be run at all, which is a different statement from a measured null result.

Parameters

NameInTypeDescription
handlereqpathstringX handle without the @.
metricquerystringWhich question the buckets answer. Media moves engagement by roughly +53% while leaving reach flat, so the metric matters.One of: engagement, views, engagement_per_view, bookmarksDefault: engagement
peer_groupquerystringOverride the decile the patterns are cut inside. d01 (smallest) to d10, or * for every tracked account. Defaults to the account's own decile.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/naval/viral-patterns?metric=engagement&peer_group=*" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "username": "naval",
      "followers": 2641009
    },
    "coverage": {
      "tweets": true,
      "reliable": true,
      "originals_sampled": 19
    },
    "viral_patterns": {
      "metric": "engagement",
      "peer_group": "d08",
      "peer_group_label": "Accounts of similar size (decile 8 of 10)",
      "highlights": [
        {
          "dimension": "link",
          "bucket": "no",
          "lift_pct": 106,
          "interval": "+95% to +115%",
          "accounts_sampled": 1732,
          "account_vote": "1,426 of 1,732 accounts"
        }
      ],
      "timing": {
        "hours_summary": "Across 62,293 posts, 23:00 UTC is the strongest hour at +10% and 06:00 UTC the weakest at -11%."
      },
      "format_fit": [
        {
          "label": "Image or video",
          "account_share_pct": 34,
          "catalog_lift_pct": 52.6,
          "catalog_accounts": 1805,
          "strength": "strong"
        }
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/x/{handle}/benchmarkinsights:read

This account against its follower cohort

Where the account's engagement rate lands on the live percentile ladder of the tracked cohort, with the ladder itself, the population it is built from and the reach counterweight. The ladder is a distribution of per-account rates, so a placement is a rank among real accounts rather than a comparison against an average of averages.

Operation id insights.x.benchmark - requires insights:read

  • -The reference population is NOT X as a whole. It is the accounts our crawl has scanned often enough to measure, and every one of them is large - the floor sits near 1.7M followers.
  • -Engagement rate falls as follower counts rise, so a smaller account will place higher on this ladder than the comparison really justifies. Use it to locate a large account among peers, not to grade a new one.
  • -The ladder is rebuilt from live percentiles on every cache refresh, never hardcoded, so a placement is against today's cohort.

Parameters

NameInTypeDescription
handlereqpathstringX handle without the @.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/Cristiano/benchmark" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "username": "Cristiano",
      "followers": 114902338
    },
    "coverage": {
      "tweets": true,
      "reliable": true,
      "originals_sampled": 27
    },
    "benchmark": {
      "engagement_rate": 0.0476,
      "percentile": 75,
      "band": "Top 25%",
      "sentence": "At 0.048%, Cristiano sits above the 75th percentile of the 3,162 accounts in this comparison.",
      "population": {
        "accounts": 3162,
        "min_followers": 1687752,
        "median_followers": 2840444,
        "median_view_rate": 0.7425,
        "median_reach_pct": 1.66
      },
      "percentiles": [
        {
          "p": 10,
          "rate": 0.0003
        },
        {
          "p": 25,
          "rate": 0.0018
        },
        {
          "p": 50,
          "rate": 0.0117
        },
        {
          "p": 75,
          "rate": 0.0476
        },
        {
          "p": 90,
          "rate": 0.1458
        },
        {
          "p": 99,
          "rate": 0.7742
        }
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/x/leadersinsights:read

Accounts ranked by how well they engage

The engagement leaderboard: accounts ranked by percentile inside their own size cohort, by raw engagement rate, by engagement per impression, or by absolute median engagement. Only accounts with a reliable sample are eligible, so the ranking cannot be won by a single lucky post.

Operation id insights.x.leaders - requires insights:read

  • -Eligibility requires at least 8 captured original posts and a non-null engagement rate.
  • -Pagination is depth-capped at 500 rows per filter combination. Narrow with tier, category or min_followers to reach further.
  • -Language and country are not filterable here. The leaderboard is keyed on the engagement rollup, which carries a primary language but has no index to filter it at this scale - use the directory endpoints when you need those axes.
  • -Tweet-level coverage is a subset of the X catalog. Scanning cadence is follower-weighted, so the accounts with a reliable sample are the very large ones: the smallest account in the measured cohort sits near 1.7M followers and its tenth percentile near 1.8M. Call GET /insights/x/coverage for the live denominator, and read benchmark.population before quoting any rate.

Parameters

NameInTypeDescription
sortquerystringauthenticity ranks by percentile against same-size accounts, rate by raw engagement rate, views by engagement per impression, engagement by absolute median engagement.One of: authenticity, rate, engagement, viewsDefault: authenticity
tierquerystringFollower band from the catalog. S is the largest.One of: S, A, B, C
categoryquerystringCatalog category slug. Empty on most rows, so a filter here narrows hard.
min_followersqueryintegerFollower floor.Default: 5000
limitqueryintegerRows per page, 1-200.Default: 50
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/leaders?sort=authenticity&tier=S&category=crypto" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "sort": "authenticity",
    "leaders": [
      {
        "user_id": "44196397",
        "username": "elonmusk",
        "name": "Elon Musk",
        "followers": 241382119,
        "tier": "S",
        "engagement_rate": 0.0471,
        "view_engagement_rate": 0.7425,
        "peer_group": "d10",
        "peer_percentile": 99.7,
        "median_engagement": 113804,
        "tweets_per_day": 24.1,
        "originals_sampled": 42
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/insights/x/coverageinsights:read

How much of the catalog has tweet-level coverage

The honest denominator behind every other insights endpoint: how many accounts carry an engagement aggregate, how many of those have a sample large enough to state as fact, how many posts sit behind them, and the cohort medians. Read this before quoting any figure from this domain.

Operation id insights.x.coverage - requires insights:read

  • -median_reach_ratio is the single most quotable number in the dataset: for the median tracked account a typical post is seen by about 1.67% of its follower count.
  • -view_coverage_pct is the share of sampled originals that carried an impression count, measured at about 96%.
  • -These figures describe the measured cohort, which is follower-weighted and large. They are not a sample of X as a whole.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/x/coverage" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "accounts_measured": 5492,
    "accounts_reliable": 3162,
    "reliable_share_pct": 57.6,
    "tweets_behind_aggregates": 241629,
    "median_engagement_rate": 0.0117,
    "median_view_engagement_rate": 0.7425,
    "median_reach_ratio": 1.67,
    "view_coverage_pct": 96,
    "last_computed_at": "2026-08-19T02:41:07.000Z",
    "min_reliable_sample": 8
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
POST/api/v2/insights/x/compareinsights:read

Side-by-side metrics for up to ten accounts

Competitive analysis in one call: the same engagement block and cohort placement for every handle in the body, plus a ranking by engagement rate so follower count does not decide the answer. Handles we have not measured come back with null metrics and their coverage marker rather than being dropped.

Operation id insights.x.compare - requires insights:read

  • -At most 10 handles per call.
  • -Ranking excludes handles with no tweet coverage; they are still present in accounts with coverage.tweets=false.
  • -Rates are only comparable between accounts of similar size. peer_percentile is the size-adjusted number and is the fairer column to sort a competitive set on.

Parameters

NameInTypeDescription
handlesreqbodyarrayArray of X handles without the @, 1-10 of them, for example ["elonmusk","BillGates","NASA"]. Duplicates are collapsed case-insensitively before they take a slot.
metricbodystringEchoed onto the response for clients that batch several metrics.One of: engagement, views, engagement_per_view, bookmarksDefault: engagement

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/insights/x/compare" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handles": [
    "<handles>"
  ],
  "metric": "engagement"
}'
json
{
  "data": {
    "requested": 3,
    "resolved": 3,
    "ranking": [
      {
        "handle": "elonmusk",
        "engagement_rate": 0.0471,
        "peer_percentile": 99.7,
        "followers": 241382119
      },
      {
        "handle": "BillGates",
        "engagement_rate": 0.0142,
        "peer_percentile": 71,
        "followers": 65130442
      },
      {
        "handle": "NASA",
        "engagement_rate": 0.0117,
        "peer_percentile": 62,
        "followers": 96204881
      }
    ],
    "accounts": [
      {
        "handle": "elonmusk",
        "found": true,
        "coverage": {
          "tweets": true,
          "reliable": true
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/audienceinsights:read

Who follows an account, and what they have in common

Aggregate composition of an account's known followers: the language and country breakdown of every follower that is itself in our X catalog, alongside the honest gap between what X reports and what we hold. Aggregates only - the individual follower rows are an audience export and live under leads:read.

Operation id insights.audience - requires insights:read

  • -followers_known counts only followers already in our catalog, so known_share_pct is routinely well under 1% for very large accounts. The composition describes that tracked slice, not the whole follower base.
  • -The facet scan is capped at the first 60,000 known edges per account. On accounts above that the breakdown is over a bounded sample rather than the full known set.
  • -Category is deliberately absent: it is empty on 82-93% of catalog rows and a breakdown built on it would describe the gap rather than the audience.

Parameters

NameInTypeDescription
handlereqquerystringX handle without the @.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/audience?handle=elonmusk" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "account": {
      "user_id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "coverage": {
      "followers_reported": 241382119,
      "followers_known": 37812,
      "known_share_pct": 0.0157
    },
    "composition": {
      "languages": [
        {
          "value": "en",
          "count": 21904,
          "share_pct": 62.4
        },
        {
          "value": "es",
          "count": 3118,
          "share_pct": 8.9
        }
      ],
      "countries": [
        {
          "value": "US",
          "count": 9442,
          "share_pct": 41.2
        },
        {
          "value": "IN",
          "count": 2331,
          "share_pct": 10.2
        }
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/instagram/{handle}insights:read

Measured Instagram engagement, placed against its cohort

Instagram is the one catalog whose crawl returns per-post like and comment counts, so this rate is measured rather than modelled. The response pairs the account's own numbers with the distribution of accounts its own size, because 'is 4.2% good' is only answerable next to the population it is being compared with - and that population is published alongside the answer rather than assumed.

Operation id insights.instagram.engagement - requires insights:read

  • -engagement.measured is false when the crawl has not been able to read this account's posts. Every number in the block is then null: not measured is not the same claim as zero engagement, and this API never collapses the two.
  • -rate is interactions per follower per post, in percent, over the twelve most recent posts the crawl read. It is not a lifetime average.
  • -cohort is a bounded sample, not a census: up to 5,000 accounts in the 0.4x-2.5x follower band around this one, read in follower order. `scanned` is how many rows were read and `measured` is how many of those carry a rate - the percentiles' real n. Quote `measured`, not `scanned`.
  • -The mean is carried beside the median deliberately. On a distribution this skewed the mean sits well above it, and that gap is the reason to quote the median.
  • -An account the catalog has never reached answers 404. Use GET /accounts/instagram/{handle}/live to read one directly.
  • -There is no equivalent endpoint for Bluesky, YouTube or TikTok. Those engines store profile counters and daily snapshots but nothing post-level, so any engagement rate we published for them would be a follower ratio wearing a measurement's clothes.

Parameters

NameInTypeDescription
handlereqpathstringInstagram handle, with or without the @.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/instagram/nasa" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "handle": "nasa",
    "account": {
      "user_id": "528817151",
      "handle": "nasa",
      "full_name": "NASA",
      "url": "https://www.instagram.com/nasa/",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/...",
      "followers": 104443318,
      "following": 78,
      "posts": 4318,
      "verified": true,
      "private": false,
      "business": true,
      "ig_category": "Government Organization",
      "category": "science",
      "language": "en",
      "tier": "a",
      "status": "active",
      "last_post_at": "2026-08-29T18:02:44.000Z",
      "last_seen": "2026-08-30T04:02:11.000Z",
      "data_age_s": 18711
    },
    "engagement": {
      "measured": true,
      "rate": 0.4213,
      "avg_likes": 402118,
      "avg_comments": 2904,
      "avg_interactions": 405022,
      "posts_analyzed": 12
    },
    "cohort": {
      "band": {
        "min_followers": 41777327,
        "max_followers": 261108295
      },
      "scanned": 812,
      "measured": 774,
      "percentile": 63,
      "percentiles": {
        "p10": 0.0611,
        "p25": 0.1402,
        "p50": 0.3118,
        "p75": 0.7204,
        "p90": 1.4402
      },
      "mean": 0.6119
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/telegram/{username}/reachinsights:read

How many subscribers actually see a Telegram post

Telegram publishes per-post view counts, which makes it the one platform where the gap between an audience and its attention is directly observable. This scores a channel's average views against its own subscriber count and ranks that ratio inside the percentile ladder for channels of the same size. It is the strongest signal we have anywhere that a subscriber list was bought rather than earned.

Operation id insights.telegram.reach - requires insights:read

  • -score.scored is false whenever the ratio cannot be ranked, and `reason` says which of four things happened: no_view_sample (the crawler has never seen a post view count), not_a_channel (Telegram publishes no per-post views for groups), below_floor (under 1,000 subscribers the ratio is too noisy to rank) and no_benchmark (the ladder for that size has not been built). None of those is an error and none of them is scored as zero.
  • -ratio is average views divided by subscribers. It is computed from the two stored inputs rather than read from the engine's own engagement column, which is still being backfilled and would make the score appear for some channels and not others for no visible reason.
  • -A ratio above 1.0 is common and is not proof of a real audience: forwards, cross-posts and paid promotion all deliver views from outside the subscriber list. That is what the `amplified` verdict says out loud.
  • -The percentile is against tracked channels in the same size band, not against Telegram. GET /insights/telegram/reach-bands publishes those ladders with their sample sizes and rebuild timestamps.
  • -This lookup deliberately does not apply the directory's active/channel scope: someone checking a handle wants to hear 'this is a group' or 'this channel is no longer public' rather than a blank miss.

Parameters

NameInTypeDescription
usernamereqpathstringPublic @username or a t.me link.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/telegram/durov/reach" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "username": "durov",
    "title": "Du rove",
    "type": "channel",
    "subscribers": 1402882,
    "avg_views": 402118,
    "posts_per_day": 1.4,
    "last_seen": "2026-08-30T02:14:00.000Z",
    "score": {
      "scored": true,
      "ratio": 0.2866,
      "percentile": 94,
      "verdict": "amplified",
      "verdict_label": "Pushed from outside",
      "verdict_summary": "Each post is seen far more times than this channel has subscribers, so most of the reach is arriving from outside the subscriber list.",
      "avg_views": 402118,
      "subscribers": 1402882,
      "band": {
        "band": "500k+",
        "min_subs": 500000,
        "max_subs": null,
        "sample_size": 1193,
        "exact": true,
        "median": 0.0383,
        "computed_at": "2026-08-29T03:00:00.000Z"
      }
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/insights/telegram/reach-bandsinsights:read

The reach percentile ladders every Telegram score is read against

The five size bands the reach score ranks a channel inside, each with its full 101-point percentile ladder, the number of channels behind it and when the engine last rebuilt it. Published so a percentile is never a number with an invisible population behind it.

Operation id insights.telegram.reachBands - requires insights:read

  • -exact: false means the ladder was built from a TABLESAMPLE of the band rather than the whole of it. The shape is sound; a percentile from it is an estimate.
  • -An empty bands array is a defined state, not an error: it means the rollup has not built the ladders in this environment, and every reach score will answer scored: false with reason no_benchmark until it has.
  • -floor is the subscriber count below which the ratio is too noisy to rank at all.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/insights/telegram/reach-bands" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "floor": 1000,
    "bands": [
      {
        "band": "20k-100k",
        "min_subs": 20000,
        "max_subs": 100000,
        "sample_size": 1116,
        "exact": false,
        "median": 0.0908,
        "computed_at": "2026-08-29T03:00:00.000Z"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Research

research:read4 endpoints

Findings, runs and events from the autonomous research engine, which decides on its own which pools to expand and reports what it learned.

GET/api/v2/research/findingsresearch:read

Findings the research engine is willing to state

The current state of every published hypothesis: what the effect is, the interval around it, how many independent accounts voted for it, its corrected q-value, how many consecutive passes it has held, and whether it has drifted. Retired and reversed findings are served alongside the promoted ones on purpose - a research feed that only ever adds is indistinguishable from one that never re-checks.

Operation id research.findings.list - requires research:read

  • -A finding is only promoted after it clears a Benjamini-Hochberg correction at alpha 0.05 inside its pre-registered family, a minimum effect of 10%, at least 50 contributing accounts, an account split-half, and 3 consecutive passes.
  • -consecutive_passed counts PASSES, not weeks and not independent replications. Passes measure a 90-day window a week apart, so two consecutive ones share 83 days of evidence. The independent check is the account split-half inside each pass.
  • -provisional and not_supported rows are deliberately not served. They number in the thousands per pass and exist to make the correction reproducible, not to be read as claims.
  • -Pagination is depth-capped at 500 rows per status.
  • -The pooled cohort is every account with an engagement rollup, which on live data means a follower floor in the millions. Any rate quoted from this domain without that caveat is an order of magnitude wrong for a normal account.

Parameters

NameInTypeDescription
statusquerystringpromoted returns current claims, retired returns withdrawn and reversed ones, all returns both.One of: promoted, retired, allDefault: promoted
limitqueryintegerRows per page, 1-200.Default: 50
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/research/findings?status=promoted&limit=50" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "status": "promoted",
    "findings": [
      {
        "hypothesis_id": "media|yes|*|*|engagement|effect",
        "tier": "core",
        "kind": "effect",
        "dimension": "media",
        "bucket": "yes",
        "stratum": "*",
        "peer_group": "*",
        "metric": "engagement",
        "status": "promoted",
        "effect": {
          "lift_pct": 53,
          "lift_lo_pct": 45,
          "lift_hi_pct": 61,
          "lift_label": "+53.0%",
          "interval_label": "+45.0% to +61.0%"
        },
        "evidence": {
          "accounts_sampled": 1862,
          "sample_size": 90412,
          "p_value": 4.6e-59,
          "q_value": 1.2e-56,
          "half_concordant": true
        },
        "history": {
          "consecutive_passed": 6,
          "best_streak": 6,
          "runs_tested": 6,
          "runs_promoted": 6,
          "streak_label": "Held for 6 consecutive passes",
          "promoted_at": "2026-07-22T02:00:00.000Z"
        },
        "drift": {
          "state": "stable",
          "note": null,
          "prev_lift_pct": 52.4
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/research/findings/{id}research:read

One finding with its full test history

A single finding plus the per-pass record behind it: every time the hypothesis was tested, what the effect measured, its p-value, both q-values, the account split-half result, whether it passed, and which rule stopped it when it did not. This is the evidence trail that makes a published claim checkable rather than assertable.

Operation id research.findings.get - requires research:read

  • -Only promoted, retired and reversed hypotheses are addressable, matching what the list endpoint serves.
  • -The test history is capped at the 24 most recent passes.
  • -q_value is corrected inside the pre-registered family; q_pooled applies the same correction over the whole run as one family. Both are returned so nobody has to take the family definition on trust - if they disagree for a published row, that is worth knowing.
  • -Failed passes are included. A finding whose only visible history is its wins is indistinguishable from one that is never re-checked.

Parameters

NameInTypeDescription
idreqpathstringHypothesis id: dimension|bucket|stratum|peer_group|metric|kind. URL-encode it - it contains | and * characters.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/research/findings/media%257Cyes%257C*%257C*%257Cengagement%257Ceffect" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "finding": {
      "hypothesis_id": "media|yes|*|*|engagement|effect",
      "status": "promoted",
      "effect": {
        "lift_pct": 53,
        "lift_lo_pct": 45,
        "lift_hi_pct": 61
      },
      "evidence": {
        "accounts_sampled": 1862,
        "q_value": 1.2e-56,
        "half_concordant": true
      }
    },
    "tests": [
      {
        "run_id": 6,
        "run_started_at": "2026-08-19T02:00:00.000Z",
        "family": "engagement:core",
        "lift_pct": 53,
        "p_value": 4.6e-59,
        "q_value": 1.2e-56,
        "q_pooled": 3.9e-56,
        "split_half": {
          "lo_pct": 48.2,
          "hi_pct": 57.9,
          "p_max": 1.1e-21,
          "concordant": true
        },
        "passed": true,
        "fail_reason": null
      }
    ],
    "gate": {
      "alpha": 0.05,
      "min_accounts": 50,
      "min_lift_pct": 10,
      "min_consecutive_runs": 3
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/research/runsresearch:read

Research pass history

One row per research pass: the window it measured, how many accounts and posts it pooled, how many hypotheses it tested in each tier, how many survived the correction, how many cleared every promotion rule, and the fingerprint of the candidate space the pass was generated from. tests.run is the denominator behind every claim the corresponding findings make.

Operation id research.runs.list - requires research:read

  • -space_digest is the fingerprint of the code that generated the candidate space. If it changes, a streak computed under a different space can be spotted rather than silently carried forward - this is the defence against quietly widening the search to fit the data.
  • -window.start and window.end are stored explicitly so overlap between passes is visible. Consecutive weekly passes over a 90-day window share 83 days of evidence.
  • -Failed passes (status other than ok) are returned too. Filtering them out would hide exactly the gaps a reader needs to interpret a streak.
  • -The pooled cohort is every account with an engagement rollup, which on live data means a follower floor in the millions. Any rate quoted from this domain without that caveat is an order of magnitude wrong for a normal account.

Parameters

NameInTypeDescription
limitqueryintegerRows per page, 1-100.Default: 25
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/research/runs?limit=25" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "runs": [
      {
        "run_id": 6,
        "started_at": "2026-08-19T02:00:00.000Z",
        "finished_at": "2026-08-19T02:03:00.000Z",
        "status": "ok",
        "scope": {
          "window_days": 90,
          "accounts_pooled": 5492,
          "tweets_pooled": 241629,
          "min_followers": 1644259
        },
        "tests": {
          "run": 3484,
          "core": 2812,
          "mined": 672,
          "discoveries": 1149,
          "promoted": 346,
          "alpha": 0.05
        },
        "window": {
          "start": "2026-05-21T02:00:00.000Z",
          "end": "2026-08-19T02:00:00.000Z"
        },
        "space_version": "1.0",
        "space_digest": "dd56fe32a9836880",
        "duration_ms": 180412
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/research/eventsresearch:read

The research changelog

What was learned, revised or withdrawn, newest first. An event is written only on a state change, so this stream is a list of things worth reading rather than a log. Each event carries a self-contained sentence written to stand on its own with no surrounding context.

Operation id research.events.list - requires research:read

  • -Event kinds are promoted, reversed, shifted, lost, retired, recovered and refuted. refuted is a measured NON-effect: the whole interval sits inside the smallest effect worth caring about.
  • -detail_source is template or llm. The template is authoritative - nothing about whether an event exists, or what its numbers are, depends on a model call.
  • -Pagination is depth-capped at 500 rows.

Parameters

NameInTypeDescription
limitqueryintegerRows per page, 1-200.Default: 50
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/research/events?limit=50" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "events": [
      {
        "event_id": 3,
        "run_id": 6,
        "hypothesis_id": "hour_utc|12|*|*|engagement|effect",
        "event": "lost",
        "event_label": "No longer supported",
        "at": "2026-08-19T02:00:00.000Z",
        "metric": "engagement",
        "lift_pct": -3.4,
        "prev_lift_pct": -12.1,
        "consecutive_passed": 0,
        "detail": "Posting with hour_utc = 12 no longer shows an effect on engagement that clears the publication bar, measured across 1,768 tracked X accounts and 41,220 posts.",
        "detail_source": "template"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}

Engines

engines:read2 endpoints

Operational health of every crawler: throughput, freshness, catalog size and crawl backlog. This is the surface to alert on.

GET/api/v2/enginesengines:read

Operational health of every insight engine

Throughput over the last hour and day, minutes since the most recent write, catalog size, crawl backlog, scan coverage where progress is measured that way, and the 14-day daily plus 24-hour hourly series behind them. This is the same read the internal engine dashboard runs.

Operation id engines.list - requires engines:read

  • -status is live (wrote in the last hour), idle (quiet but recent), stale (silent for more than two hours), no_data (nothing written and an empty catalog, which on LinkedIn usually means the schema has not been applied) or unavailable (the health query did not come back, so nothing here was measured).
  • -Catalogs above two million rows report active, total and due as block-sample estimates scaled by the planner's row count, not exact counts. The X catalog is in that regime; every other engine is counted exactly.
  • -Each engine runs four independent queries, and at most two engines are read at a time so one slow table cannot starve the others of connections. A query that fails still degrades only its own fields, and the `unread` block says which fields those are: an unread field is zero in the payload but must not be read as zero. status='unavailable' means the health read itself did not come back.
  • -The hourly series is generated from a time series and left-joined, so a quiet hour is a visible zero rather than a missing bucket. An engine that stopped and an engine nobody asked about look identical once a gap is closed up.
  • -due counts active rows never fetched or older than their tier's cadence: S hourly, A six-hourly, B daily, C weekly.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/engines" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "engines": [
      {
        "key": "x",
        "label": "X (Twitter)",
        "unit": "accounts",
        "status": "live",
        "throughput": {
          "last_1h": 412,
          "last_24h": 9120,
          "avg_per_hour_24h": 380
        },
        "freshness": {
          "last_snapshot_minutes": 3,
          "stale": false,
          "stale_after_minutes": 120
        },
        "catalog": {
          "active": 13984112,
          "total": 14206330,
          "due": 284907,
          "due_share_pct": 2
        },
        "coverage": null,
        "series": {
          "daily": [
            {
              "day": "2026-08-22",
              "count": 8812
            }
          ],
          "hourly": [
            {
              "hour": "2026-08-23 04:00",
              "label": "04:00",
              "count": 412
            }
          ]
        }
      },
      {
        "key": "xtweets",
        "label": "X Tweets",
        "unit": "tweets",
        "status": "live",
        "throughput": {
          "last_1h": 2940,
          "last_24h": 61204,
          "avg_per_hour_24h": 2550
        },
        "coverage": {
          "done": 5492,
          "remaining": 13978620,
          "label": "accounts scanned",
          "done_share_pct": 0.04
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/engines/{key}engines:read

One engine in detail

The same metrics as the list endpoint for a single engine, plus the caveats specific to it - which counts are estimated, what its throughput unit actually measures, and what a zeroed card means for that engine in particular.

Operation id engines.get - requires engines:read

  • -Catalogs above two million rows report active, total and due as block-sample estimates scaled by the planner's row count, not exact counts. The X catalog is in that regime; every other engine is counted exactly.
  • -Each engine runs four independent queries, and at most two engines are read at a time so one slow table cannot starve the others of connections. A query that fails still degrades only its own fields, and the `unread` block says which fields those are: an unread field is zero in the payload but must not be read as zero. status='unavailable' means the health read itself did not come back.
  • -coverage is only present on engines whose progress is coverage of another catalog rather than throughput against their own. Today that is xtweets alone.

Parameters

NameInTypeDescription
keyreqpathstringEngine key.One of: x, xtweets, telegram, bluesky, youtube, tiktok, instagram, linkedin

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/engines/xtweets" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "engine": {
      "key": "xtweets",
      "label": "X Tweets",
      "unit": "tweets",
      "status": "live",
      "throughput": {
        "last_1h": 2940,
        "last_24h": 61204,
        "avg_per_hour_24h": 2550
      },
      "freshness": {
        "last_snapshot_minutes": 1,
        "stale": false,
        "stale_after_minutes": 120
      },
      "catalog": {
        "active": 13984112,
        "total": 14206330,
        "due": 284907,
        "due_share_pct": 2
      },
      "coverage": {
        "done": 5492,
        "remaining": 13978620,
        "label": "accounts scanned",
        "done_share_pct": 0.04
      }
    },
    "notes": [
      "This engine writes posts, not follower snapshots, so throughput counts tweets captured rather than accounts refreshed."
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Marketplace

marketplace:read5 endpoints

Public marketplace data: active listings, platform statistics, the seller leaderboard and current pricing. The same numbers the site shows, with nothing key-holder-specific in them.

GET/api/v2/marketplace/listingsmarketplace:read

Browse active marketplace listings

The public marketplace feed: accounts and channels currently for sale, across X, Instagram, TikTok, Telegram and YouTube. Only active listings are served - drafts, pending, suspended, rejected, sold and archived listings are never returned. Filter by platform, price, audience size, category and verification, then page with an opaque cursor.

Operation id marketplace.listings - requires marketplace:read

  • -Public fields only. Seller identity is limited to username, display name, avatar, role, verification tier, premium flag and join date - never email, IP, country, signup data or the internal user id. Also withheld: the Telegram invite_link (for a private channel that link is the asset), moderation notes, the internal engagement display uplift, member-count provenance and bump bookkeeping.
  • -Only status = active listings are returned. There is no parameter that widens this.
  • -Prices are USD cents. price_usd is provided so the value never has to be divided twice.
  • -The default sort (newest) is the marketplace's own ranking: paid pinning boosts float to the top, then the rest are interleaved so one seller cannot blanket the front. It is computed over a pool of the newest 500 rows, so paging stops there - use price_asc, price_desc or followers_desc to reach the whole result set.
  • -meta.page.total is the exact number of listings matching the filters, even when the cursor stops earlier on the default sort.
  • -In mock mode (NEXT_PUBLIC_USE_MOCK_DATA=true) this serves the in-memory fixture set with the same filters and the same shape.

Parameters

NameInTypeDescription
platformquerystringRestrict to one platform. Omit for all platforms.One of: twitter, instagram, tiktok, telegram, youtube
categoryquerystringCategory slug as chosen by the seller, e.g. crypto, tech, news, entertainment. Categories differ per platform.
min_price_centsqueryintegerMinimum asking price in USD cents. 185000 is $1,850.
max_price_centsqueryintegerMaximum asking price in USD cents.
min_followersqueryintegerMinimum audience on the platform's primary metric: followers on X, Instagram and TikTok, members on Telegram, subscribers on YouTube.
max_followersqueryintegerMaximum audience on the platform's primary metric.
verifiedquerybooleanOnly accounts carrying the platform's verified mark.
has_original_emailquerybooleanOnly listings where the original registration email transfers with the account.
qquerystringFree text over handle, display name and description. Max 100 characters.
sortquerystringnewest is the marketplace's own seller-diverse ordering. The other four are plain orderings and can be paged to the end.One of: newest, price_asc, price_desc, followers_desc, rating_descDefault: newest
limitqueryintegerRows per page, 1 to 100.Default: 25
cursorquerystringOpaque cursor from meta.page.next_cursor. It is bound to the filter set and page size that produced it; change either and start again without a cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/marketplace/listings?platform=twitter&category=crypto&min_price_cents=50000" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": [
    {
      "id": "6f1c2e30-9c1a-4c86-9b8f-1f2a3b4c5d6e",
      "platform": "twitter",
      "handle": "@cryptodaily_",
      "display_name": "Crypto Daily",
      "description": "Six-year-old crypto news account. Organic growth, no purchased followers, original email included.",
      "category": "crypto",
      "price_cents": 185000,
      "price_usd": 1850,
      "primary_metric": {
        "key": "followers",
        "value": 84210
      },
      "metrics": {
        "followers": 84210,
        "following": 412,
        "posts": 12840,
        "subscribers": null,
        "members": null,
        "videos": null,
        "likes": null,
        "total_views": null
      },
      "account_age_days": 2214,
      "is_verified": false,
      "has_original_email": true,
      "owner_verified": true,
      "listing_type": "community",
      "featured": false,
      "telegram_chat_type": null,
      "avatar_url": "https://cdn.playersells.com/listings/6f1c2e30/avatar.jpg",
      "header_url": null,
      "screenshots": [
        "https://cdn.playersells.com/listings/6f1c2e30/analytics-1.png"
      ],
      "listing_views": 1842,
      "active_boosts": [
        "priority"
      ],
      "created_at": "2026-07-14T09:12:44.000Z",
      "bumped_at": "2026-08-22T06:00:11.000Z",
      "seller": {
        "username": "chainmarket",
        "display_name": "ChainMarket",
        "avatar_url": "https://cdn.playersells.com/avatars/chainmarket.jpg",
        "role": "seller",
        "verification_tier": "id_verified",
        "is_premium": true,
        "member_since": "2025-11-02T18:44:10.000Z"
      },
      "url": "/marketplace/6f1c2e30-9c1a-4c86-9b8f-1f2a3b4c5d6e"
    }
  ],
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 1
    }
  }
}
GET/api/v2/marketplace/listings/{id}marketplace:read

Get one public listing

A single marketplace listing by id, in the same public shape the feed returns. Returns 404 not_found for an unknown id and also for any listing that is not currently active, so a draft, suspended or archived listing is indistinguishable from one that never existed.

Operation id marketplace.listing - requires marketplace:read

  • -Public fields only. Seller identity is limited to username, display name, avatar, role, verification tier, premium flag and join date - never email, IP, country, signup data or the internal user id. Also withheld: the Telegram invite_link (for a private channel that link is the asset), moderation notes, the internal engagement display uplift, member-count provenance and bump bookkeeping.
  • -A non-active listing returns 404, not a listing with a status field. Moderation state is not public information.
  • -screenshots are seller-uploaded proof images (analytics, insights panels). They are served from our own CDN and are part of the public listing.

Parameters

NameInTypeDescription
idreqpathstringListing UUID, as returned by /marketplace/listings.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/marketplace/listings/6f1c2e30-9c1a-4c86-9b8f-1f2a3b4c5d6e" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "id": "6f1c2e30-9c1a-4c86-9b8f-1f2a3b4c5d6e",
    "platform": "twitter",
    "handle": "@cryptodaily_",
    "display_name": "Crypto Daily",
    "description": "Six-year-old crypto news account. Organic growth, no purchased followers, original email included.",
    "category": "crypto",
    "price_cents": 185000,
    "price_usd": 1850,
    "primary_metric": {
      "key": "followers",
      "value": 84210
    },
    "metrics": {
      "followers": 84210,
      "following": 412,
      "posts": 12840,
      "subscribers": null,
      "members": null,
      "videos": null,
      "likes": null,
      "total_views": null
    },
    "account_age_days": 2214,
    "is_verified": false,
    "has_original_email": true,
    "owner_verified": true,
    "listing_type": "community",
    "featured": false,
    "telegram_chat_type": null,
    "avatar_url": "https://cdn.playersells.com/listings/6f1c2e30/avatar.jpg",
    "header_url": null,
    "screenshots": [
      "https://cdn.playersells.com/listings/6f1c2e30/analytics-1.png"
    ],
    "listing_views": 1842,
    "active_boosts": [
      "priority"
    ],
    "created_at": "2026-07-14T09:12:44.000Z",
    "bumped_at": "2026-08-22T06:00:11.000Z",
    "seller": {
      "username": "chainmarket",
      "display_name": "ChainMarket",
      "avatar_url": "https://cdn.playersells.com/avatars/chainmarket.jpg",
      "role": "seller",
      "verification_tier": "id_verified",
      "is_premium": true,
      "member_since": "2025-11-02T18:44:10.000Z"
    },
    "url": "/marketplace/6f1c2e30-9c1a-4c86-9b8f-1f2a3b4c5d6e"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/marketplace/statsmarketplace:read

Platform totals

The aggregate figures PlayerSells already publishes on its homepage: completed escrow transactions, total escrow volume, live listing count and the average seller rating. Aggregates only - no deal, buyer or seller row is exposed.

Operation id marketplace.stats - requires marketplace:read

  • -Cached for 10 minutes server-side. meta.cache_age_s is not tracked per caller, so treat these as up to 10 minutes old.
  • -Volume is USD cents. total_volume_usd is the same figure already divided.
  • -avg_rating excludes shadowbanned reviews.
  • -In mock mode this returns the fixture totals rather than live figures.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/marketplace/stats" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "total_transactions": 2847,
    "total_volume_cents": 125000000,
    "total_volume_usd": 1250000,
    "active_listings": 156,
    "avg_rating": 4.8,
    "accounts_transferred": 1420
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/marketplace/leaderboardmarketplace:read

Best-selling sellers

The public best-sellers board: sellers ranked by completed escrow sales, with the volume, average rating and review count behind each. Banned and anonymized accounts are excluded. This is the same pool the /leaderboard page renders.

Operation id marketplace.leaderboard - requires marketplace:read

  • -Public seller identity only: username, display name, avatar, role, verification tier and premium flag. No email, no internal user id, no location.
  • -The pool is the top 60 sellers by completed sales. sort re-ranks that pool; it does not re-query, so sorting by volume cannot surface a seller outside the top 60 by sales.
  • -Admin-owned seller accounts that opted into identity masking appear as their public persona, exactly as they do on the site.
  • -Cached for 10 minutes server-side.
  • -In mock mode this ranks the fixture sellers with the same algorithm.

Parameters

NameInTypeDescription
limitqueryintegerHow many ranked sellers to return, 1 to 60.Default: 25
sortquerystringRe-rank the same pool. sales is completed-sale count, volume is escrow dollars, rating is average review score.One of: sales, volume, ratingDefault: sales

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/marketplace/leaderboard?limit=10&sort=volume" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": [
    {
      "rank": 1,
      "username": "chainmarket",
      "display_name": "ChainMarket",
      "avatar_url": "https://cdn.playersells.com/avatars/chainmarket.jpg",
      "role": "seller",
      "verification_tier": "id_verified",
      "is_premium": true,
      "sales_count": 61,
      "volume_cents": 9420000,
      "volume_usd": 94200,
      "avg_rating": 4.9,
      "total_reviews": 54
    }
  ],
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 1
    }
  }
}
GET/api/v2/marketplace/pricingmarketplace:read

Live fee schedule and boost packages

Every active platform fee and promotion package, read live from the database that actually charges them. Pass amount_cents to additionally resolve what a specific withdrawal or bank deposit of that size would really cost, after the small-amount rules that the raw fee rows do not express.

Operation id marketplace.pricing - requires marketplace:read

  • -Fees are DB-driven and this reads that database live. The SQL migration files in the repo are stale and must not be used as the schedule - this endpoint is the source of truth, same as the site's own /api/pricing.
  • -Read `unit` before doing arithmetic. A percentage fee's `amount` is a percent (8 means 8%); a flat fee's `amount` is dollars, with `amount_cents` alongside so nothing gets divided twice.
  • -The raw rows do not express the small-amount rules the platform actually applies, such as the flat fee floor on withdrawals under $10. Pass amount_cents to get the effective figure instead of re-deriving it.
  • -Only active fees and packages are returned. A deactivated row is omitted, not returned with a flag.
  • -Cached for up to 60 seconds after an admin change.

Parameters

NameInTypeDescription
amount_centsqueryintegerOptional. A transaction size in USD cents, 1 to 100000000. When present the response gains a `resolved` block with the effective withdrawal, bank deposit and middleman fees for that amount.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/marketplace/pricing?amount_cents=50000" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "fees": [
      {
        "slug": "escrow_fee",
        "label": "Escrow commission",
        "description": "Taken from the seller's payout when a deal completes.",
        "fee_type": "percentage",
        "amount": 8,
        "unit": "percent",
        "amount_cents": null,
        "min_amount_cents": null,
        "max_amount_cents": null,
        "sort_order": 1
      },
      {
        "slug": "withdrawal_fee",
        "label": "Withdrawal fee",
        "description": "Charged per payout request.",
        "fee_type": "flat",
        "amount": 2.5,
        "unit": "usd",
        "amount_cents": 250,
        "min_amount_cents": null,
        "max_amount_cents": null,
        "sort_order": 2
      }
    ],
    "boosts": [
      {
        "slug": "spotlight",
        "name": "Spotlight",
        "description": "Pinned to the very top of the marketplace for 7 days.",
        "price_cents": 4900,
        "price_usd": 49,
        "duration_days": 7,
        "multiplier": 4,
        "features": [
          "Top of marketplace",
          "Homepage placement",
          "Highlighted card"
        ],
        "is_popular": true,
        "sort_order": 1
      }
    ],
    "resolved": {
      "amount_cents": 50000,
      "withdrawal_fee_cents": 250,
      "bank_deposit_fee_cents": 1000,
      "middleman_fee_cents": 1000
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

Tools

tools:uselive20 endpoints

The analysis tools behind the public free-tool pages, callable directly: valuation, follower audit, scoring and the rest. Metered like a live call, because each run costs compute or an upstream fetch.

GET/api/v2/toolstools:use

List every analysis tool

The tool catalog: id, platform, method and path, what each tool takes and returns, whether a call spends an upstream credit, and a typical latency. Free to call and safe to poll; it reads no upstream.

Operation id tools.catalog - requires tools:use(sensitive scope, granted only on request)

  • -Every tool with cost.billable = true spends real money per call. Budget accordingly.
  • -typical_latency_ms is a warm-upstream estimate, not a guarantee. The AI tools are the slow ones.

Parameters

No parameters. Send the key and nothing else.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/tools" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "tools": [
      {
        "id": "valuation",
        "name": "Account valuation",
        "platform": "x",
        "method": "POST",
        "path": "/tools/valuation",
        "description": "Dollar value range for an X account, from followers, engagement, age, authority, activity and profile completeness.",
        "input": [
          "handle"
        ],
        "output": [
          "profile",
          "metrics",
          "engagement",
          "valuation",
          "scores",
          "factors",
          "summary"
        ],
        "cost": {
          "billable": true,
          "upstream": "twitterapi",
          "unit": "up to 3 twitterapi.io calls"
        },
        "typical_latency_ms": 3500,
        "live": true
      }
    ],
    "count": 19,
    "billable_count": 17
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
POST/api/v2/tools/valuationtools:uselive

Value an X account

Estimates what an X account is worth in dollars. Reads the profile plus a sample of recent posts, scores followers, engagement, account age, authority, activity and profile completeness, and returns a low/expected/high range with the multiplier behind each factor.

Operation id tools.valuation - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -Values are USD, not cents.
  • -Results are cached in-process for a short window, so two calls on the same handle within that window may return the same numbers without a second upstream charge.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/valuation" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "naval",
      "twitter_id": "745273",
      "name": "Naval",
      "avatar_url": "https://pbs.twimg.com/profile_images/1256841238298292232/ycqwaMI2.jpg",
      "verified": true,
      "banner_url": "https://pbs.twimg.com/profile_banners/745273/1584485471",
      "bio": "Angel Philosopher",
      "location": "San Francisco",
      "created_at": "2007-02-05T18:07:40.000Z",
      "account_age_days": 7139
    },
    "metrics": {
      "followers": 2418702,
      "following": 2114,
      "total_posts": 32180,
      "total_likes": 12844,
      "media_count": 1290,
      "follower_following_ratio": 1144.1,
      "posts_per_day": 4.5
    },
    "engagement": {
      "engagement_rate": 0.4172,
      "avg_likes": 8940,
      "avg_retweets": 1216,
      "avg_replies": 934,
      "avg_views": 412800,
      "posts_sampled": 20
    },
    "valuation": {
      "estimated_value_usd": 48600,
      "value_low_usd": 36450,
      "value_high_usd": 65610,
      "score": 88,
      "rating": "exceptional"
    },
    "scores": {
      "follower": 100,
      "engagement": 82,
      "age": 100,
      "authority": 95,
      "activity": 74,
      "profile": 100
    },
    "factors": {
      "base_follower_value_usd": 36000,
      "engagement_multiplier": 1.25,
      "age_multiplier": 1.15,
      "authority_multiplier": 1.1,
      "activity_multiplier": 0.95,
      "profile_multiplier": 1.05
    },
    "summary": {
      "size_category": "Mega Account",
      "engagement_rating": "Good",
      "strengths": [
        "Very large, established audience",
        "Account is over 15 years old",
        "Strong follower to following ratio"
      ],
      "weaknesses": [
        "Posting cadence has slowed in the last 30 days"
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/shadowban-checktools:uselive

Check an X account for visibility filtering

Runs four independent probes and reports each as pass, warn or fail with the evidence behind it: ghost ban (the account's posts missing from public timelines), search ban (the account missing from search), reply deboosting (replies hidden behind Show more), and search suggestion ban (the handle missing from typeahead).

Operation id tools.shadowbanCheck - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -A warn is not proof of a penalty. Search indexing lags by minutes, so a very fresh post can look filtered when it is not.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/shadowban-check" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "jack",
      "twitter_id": "12",
      "name": "jack",
      "avatar_url": "https://pbs.twimg.com/profile_images/1661201415899951105/N-hnbGgse.jpg",
      "verified": false,
      "followers": 6512884,
      "following": 4102,
      "total_posts": 29640,
      "created_at": "2006-03-21T20:50:14.000Z",
      "account_age_days": 7460
    },
    "checks": {
      "ghost_ban": {
        "status": "pass",
        "detail": "Recent posts appear in public search results."
      },
      "search_ban": {
        "status": "pass",
        "detail": "Account is discoverable in search."
      },
      "reply_deboosting": {
        "status": "warn",
        "detail": "2 of 15 sampled replies were not returned by public search."
      },
      "search_suggestion_ban": {
        "status": "pass",
        "detail": "Handle appears in user search."
      }
    },
    "verdict": {
      "clean": false,
      "flag_count": 1
    },
    "checked_at": "2026-08-23T09:14:02.881Z"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/follower-audittools:uselive

Audit an X account's followers for fakes

Samples the follower graph and classifies each sampled account as real, suspicious, bot or inactive, then reports the split, the signals that drove the flags, and four component scores (follower quality, engagement, account health, sample quality).

Operation id tools.followerAudit - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -sample_size is what the upstream returned, typically the first page of followers. It is a sample, not a census; treat the percentages as estimates with sampling error.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/follower-audit" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "vercel",
      "twitter_id": "1531841848",
      "name": "Vercel",
      "avatar_url": "https://pbs.twimg.com/profile_images/1767351110131445760/9nAA4mQ0.jpg",
      "verified": true,
      "followers": 412903,
      "following": 361,
      "total_posts": 8912,
      "created_at": "2013-06-18T14:22:11.000Z",
      "account_age_days": 4814
    },
    "composition": {
      "real_percent": 71.4,
      "suspicious_percent": 14.2,
      "bot_percent": 6.8,
      "inactive_percent": 7.6
    },
    "engagement": {
      "engagement_rate": 0.086,
      "avg_likes": 214,
      "avg_retweets": 31,
      "avg_replies": 12,
      "avg_views": 41200
    },
    "scores": {
      "quality": 72,
      "engagement": 48,
      "account_health": 91,
      "follower_sample": 80
    },
    "verdict": "good",
    "sample_size": 200,
    "top_signals": [
      {
        "signal": "no_avatar",
        "count": 24,
        "label": "Default profile picture"
      },
      {
        "signal": "never_posted",
        "count": 19,
        "label": "Has never posted"
      },
      {
        "signal": "follow_ratio",
        "count": 11,
        "label": "Follows far more than it is followed"
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/engagement-calculatortools:uselive

Measure an X account's engagement rate

Computes the true engagement rate from recent posts rather than from follower count alone. Returns per-interaction rates, size-adjusted benchmarks, the best and worst performing post, a recent trend, and the full sampled post list with per-post rates.

Operation id tools.engagementCalculator - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -engagement_rate is a share of followers. view_rate is a share of followers too, so it exceeds 100 for accounts whose reach is larger than their following.
  • -`posts` carries every sampled post; it is the largest part of the payload.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/engagement-calculator" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "levelsio",
      "twitter_id": "10634622",
      "name": "levelsio",
      "avatar_url": "https://pbs.twimg.com/profile_images/1719614758452781056/6Qt3xLpc.jpg",
      "verified": true,
      "followers": 612440,
      "following": 1832,
      "total_posts": 61204,
      "created_at": "2007-11-27T09:12:44.000Z",
      "account_age_days": 6844
    },
    "engagement": {
      "engagement_rate": 1.24,
      "median_engagement_rate": 0.92,
      "consistency": 61,
      "avg_likes": 5420,
      "avg_retweets": 388,
      "avg_replies": 612,
      "avg_quotes": 74,
      "avg_views": 481200,
      "avg_bookmarks": 402,
      "avg_engagement_per_post": 6496,
      "total_engagement": 129920
    },
    "rates": {
      "like_rate": 0.885,
      "retweet_rate": 0.063,
      "reply_rate": 0.1,
      "view_rate": 78.5,
      "bookmark_rate": 0.066,
      "virality_rate": 0.075,
      "conversation_rate": 9.4,
      "view_to_engagement_rate": 1.35,
      "like_dominance": 83.4
    },
    "rating": "excellent",
    "rating_score": 88,
    "benchmarks": {
      "excellent": 1.2,
      "good": 0.7,
      "average": 0.35,
      "below": 0.15
    },
    "reach": {
      "estimated_reach": 481200,
      "follower_following_ratio": 334.3,
      "posting_frequency": 8.9
    },
    "posts_analyzed": 20,
    "trend": "improving",
    "best_post": {
      "id": "1789231114023874812",
      "text": "Shipped it.",
      "likes": 24100,
      "retweets": 1420,
      "replies": 918,
      "quotes": 210,
      "views": 2140000,
      "bookmarks": 1802,
      "engagement_rate": 1.24,
      "created_at": "2026-08-11T07:40:12.000Z"
    },
    "worst_post": null,
    "posts": [],
    "summary": {
      "size_category": "100K-1M followers"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/best-posting-timetools:uselive

Find when an X account should post

Builds a 7x24 engagement heatmap from an account's post history, estimates its posting timezone, and returns the best and worst slots, per-day and per-hour summaries, the posting pattern (cadence, consistency, weekday versus weekend), and a 0-100 timing score.

Operation id tools.bestPostingTime - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -This tool pages the post history and can spend up to five upstream calls, more than any other X tool here.
  • -The timezone is inferred from posting behaviour, not from the account. `heatmap` hours are in that inferred timezone.
  • -heatmap contains only slots the account has actually posted in, not a padded 168-cell grid.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/best-posting-time" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "swyx",
      "twitter_id": "33521530",
      "name": "swyx",
      "avatar_url": "https://pbs.twimg.com/profile_images/1610205747724443648/0DVR0Xnw.jpg",
      "verified": true,
      "followers": 118420,
      "following": 2412,
      "total_posts": 41208,
      "created_at": "2009-04-19T15:02:33.000Z",
      "account_age_days": 6335
    },
    "posts_analyzed": 180,
    "date_range": {
      "from": "2026-05-02T11:20:00.000Z",
      "to": "2026-08-22T18:44:00.000Z"
    },
    "timezone": {
      "estimated": "UTC-8 (US Pacific)",
      "utc_offset": -8
    },
    "heatmap": [
      {
        "day": 2,
        "hour": 9,
        "posts": 14,
        "avg_engagement": 412,
        "avg_likes": 318,
        "avg_retweets": 44,
        "avg_replies": 50,
        "avg_views": 28400,
        "total_engagement": 5768
      }
    ],
    "best_times": [
      {
        "day": 2,
        "day_name": "Tuesday",
        "hour": 9,
        "hour_label": "9:00 AM",
        "avg_engagement": 412,
        "avg_views": 28400,
        "posts": 14
      }
    ],
    "worst_times": [
      {
        "day": 6,
        "day_name": "Saturday",
        "hour": 23,
        "hour_label": "11:00 PM",
        "avg_engagement": 38,
        "avg_views": 2900,
        "posts": 4
      }
    ],
    "best_day": {
      "day": 2,
      "day_name": "Tuesday",
      "posts": 31,
      "avg_engagement": 366,
      "avg_views": 24800,
      "total_engagement": 11346
    },
    "worst_day": {
      "day": 0,
      "day_name": "Sunday",
      "posts": 12,
      "avg_engagement": 92,
      "avg_views": 7100,
      "total_engagement": 1104
    },
    "best_hour": {
      "hour": 9,
      "posts": 27,
      "avg_engagement": 388,
      "avg_views": 26100,
      "total_engagement": 10476
    },
    "worst_hour": {
      "hour": 23,
      "posts": 6,
      "avg_engagement": 41,
      "avg_views": 3100,
      "total_engagement": 246
    },
    "day_summaries": [],
    "hour_summaries": [],
    "pattern": {
      "posts_per_day": 1.6,
      "posts_per_week": 11.2,
      "most_active_day": "Tuesday",
      "most_active_hour": "9:00 AM",
      "consistency": "consistent",
      "avg_hours_between_posts": 14.8,
      "weekday_vs_weekend": {
        "weekday_avg_engagement": 318,
        "weekend_avg_engagement": 121,
        "weekday_posts": 142,
        "weekend_posts": 38,
        "winner": "weekday",
        "difference": 162.8
      }
    },
    "engagement": {
      "engagement_rate": 0.31,
      "avg_likes": 284,
      "avg_retweets": 38,
      "avg_replies": 44,
      "avg_views": 21400
    },
    "timing_score": 74,
    "timing_grade": "good",
    "summary": {
      "recommendations": [
        "Tuesday 9:00 AM is this account's strongest slot by a wide margin.",
        "Weekend posts earn 62% less engagement. Move them into the week."
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/algorithm-scoretools:uselive

Score an X account against the ranking signals

Grades an account 0-100 on the five behaviours X's ranking model rewards: bookmark power, conversation spark, virality factor, reach efficiency and account authority. Each category returns its score, the raw measurement behind it and how that measurement should be read.

Operation id tools.algorithmScore - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: this endpoint bills twitterapi.io credits per call. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, and 502 upstream_error when the credit pool is exhausted or the upstream fails. It never returns an empty 200.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -A category can come back with metric.kind = "unavailable" when the upstream did not return the inputs for it (view counts are the usual gap). Its score is then excluded rather than guessed.
  • -This is a model of the public ranking signals, not X's actual ranker.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/algorithm-score" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "dhh",
      "twitter_id": "5943622",
      "name": "DHH",
      "avatar_url": "https://pbs.twimg.com/profile_images/1734635298264395776/8Xw2fPQK.jpg",
      "verified": true,
      "followers": 582104,
      "following": 412,
      "total_posts": 54120,
      "created_at": "2007-05-14T02:12:44.000Z",
      "account_age_days": 6976
    },
    "overall_score": 71,
    "tier": {
      "name": "Amplified",
      "min": 70,
      "max": 84,
      "description": "The algorithm actively pushes this account beyond its follower base."
    },
    "categories": [
      {
        "key": "bookmark_power",
        "score": 64,
        "value": 0.084,
        "metric": {
          "kind": "rate",
          "fraction_digits": 3
        },
        "label": "Bookmark power",
        "description": "Bookmarks per view. The strongest single quality signal X tracks."
      },
      {
        "key": "conversation_spark",
        "score": 82,
        "value": 0.142,
        "metric": {
          "kind": "rate",
          "fraction_digits": 3
        },
        "label": "Conversation spark",
        "description": "Replies per view. Rewarded heavily; reply chains extend distribution."
      },
      {
        "key": "account_authority",
        "score": 88,
        "value": 22,
        "metric": {
          "kind": "score",
          "out_of": 25
        },
        "label": "Account authority",
        "description": "Age, verification and follower-to-following ratio combined."
      }
    ],
    "averages": {
      "avg_views": 214000,
      "avg_likes": 3120,
      "avg_bookmarks": 180,
      "avg_replies": 304,
      "avg_retweets": 288,
      "avg_quotes": 61
    },
    "posts_analyzed": 20,
    "checked_at": "2026-08-23T09:20:41.204Z"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/bio-optimizertools:uselive

Score and rewrite an X bio (AI)

Scores a bio on search visibility, trust signals and call-to-action strength, lists the specific issues costing conversions, and writes three alternative bios targeted at different niches. Structured scores and issues sit at the top level; the model's prose analysis is under summary.

Operation id tools.bioOptimizer - requires tools:use(sensitive scope, granted only on request)

  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -Costs money twice: twitterapi.io credits for the profile read plus Anthropic tokens for the generation. Metered as a live call.
  • -Returns 503 not_configured when ANTHROPIC_API_KEY is unset or AI is switched off in admin settings, and 502 upstream_error when the model call fails or the Anthropic balance is exhausted. It never fabricates a result.
  • -Output is model-generated prose and scores. Treat it as advisory, not measurement.
  • -The three alternative bios are capped at 160 characters each by the schema the model is constrained to.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/bio-optimizer" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "shl",
      "twitter_id": "13334762",
      "name": "Sahil Lavingia",
      "avatar_url": "https://pbs.twimg.com/profile_images/1735002911286755328/9OaQ6vXk.jpg",
      "verified": true,
      "followers": 218400,
      "following": 1240,
      "total_posts": 41220,
      "bio": "ceo of gumroad",
      "website": "https://sahillavingia.com",
      "location": "New York",
      "created_at": "2008-02-11T04:22:18.000Z",
      "account_age_days": 6768
    },
    "scores": {
      "seo": 42,
      "trust": 71,
      "cta": 18,
      "overall": 44
    },
    "top_issues": [
      "No keywords a buyer would actually search for.",
      "There is no call to action anywhere in the bio.",
      "The link is present but nothing tells a visitor why to click it."
    ],
    "alternatives": [
      {
        "niche": "Founder",
        "bio": "Built Gumroad to $20M/yr. I write about staying small, shipping fast, and pricing. Free playbook below.",
        "reasoning": "Leads with a concrete number so the authority is provable, names three searchable topics, and ends on a reason to click the link."
      }
    ],
    "summary": {
      "seo": "The bio is three words long and none of them are terms anyone searches...",
      "trust": "The Gumroad association does real work here...",
      "cta": "There is no ask. A visitor who is convinced has nowhere to go..."
    },
    "model": "claude-sonnet-4-6",
    "analyzed_at": "2026-08-23T09:22:10.552Z"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/profile-roasttools:uselive

Roast and fix an X profile (AI)

Reads a profile and its recent posts and returns a blunt critique with a score out of 10, the concrete issues behind that score, quick wins, and suggested posts with the strategy behind each. The prose roast and the rewrite plan are under summary; the score, issues and suggestions are structured.

Operation id tools.profileRoast - requires tools:use(sensitive scope, granted only on request)

  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: the X tools always read live data and have no mock path.
  • -The website's captcha and per-visitor daily caps do not apply here; your API key's tier limits do.
  • -Costs money twice: twitterapi.io credits for the profile read plus Anthropic tokens for the generation. Metered as a live call.
  • -Returns 503 not_configured when ANTHROPIC_API_KEY is unset or AI is switched off in admin settings, and 502 upstream_error when the model call fails or the Anthropic balance is exhausted. It never fabricates a result.
  • -Output is model-generated prose and scores. Treat it as advisory, not measurement.
  • -The roast is deliberately harsh copy. Do not surface it to a third party's account holder unsolicited.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/profile-roast" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "somebuilder",
      "twitter_id": "1428800012341",
      "name": "some builder",
      "avatar_url": null,
      "verified": false,
      "followers": 412,
      "following": 2810,
      "total_posts": 88,
      "bio": "building stuff | dm for collab",
      "created_at": "2021-08-20T12:00:00.000Z",
      "account_age_days": 1829
    },
    "score_out_of_10": 3,
    "top_issues": [
      "No profile picture, which caps trust before anyone reads a word.",
      "Following 7x more accounts than follow back.",
      "The bio says building stuff, which describes every account on the platform."
    ],
    "quick_wins": [
      "Add a face photo today.",
      "Name the one thing you build and who it is for.",
      "Unfollow the inactive half of your following list."
    ],
    "suggested_posts": [
      {
        "text": "I spent 6 weeks building the wrong feature. Here is the question I should have asked first.",
        "strategy": "Specific failure with a lesson. Invites replies from people who did the same."
      }
    ],
    "summary": {
      "roast": "Your bio says building stuff. So does a construction crane...",
      "optimization": "Start with the avatar, then the bio, then post cadence..."
    },
    "model": "claude-sonnet-4-6",
    "analyzed_at": "2026-08-23T09:25:03.117Z"
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/tweet-analyzertools:uselive

Analyze a single X post

Breaks one post down: raw metrics, engagement rate against views (or followers when view counts are missing), a 0-100 virality score with a grade, the composition of engagement by interaction type, amplification and save rates, and a sample of the accounts that retweeted it with their combined reach.

Operation id tools.tweetAnalyzer - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: 2 twitterapi.io calls per request (the post, then its retweeters). Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset, 404 not_found for a deleted or protected post, and 502 upstream_error when the upstream fails.
  • -engagement_basis tells you the denominator. Older posts often have no view count, and the rate then falls back to followers, which is not comparable across posts.
  • -retweeters.top is a sample of the first page, not the full amplifier list.

Parameters

NameInTypeDescription
tweetreqbodystringNumeric post id or a full x.com/<user>/status/<id> URL. Also accepted as `tweet_url` or `tweet_id`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/tweet-analyzer" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "tweet": "https://x.com/naval/status/1002103360646823936"
}'
json
{
  "data": {
    "post": {
      "id": "1002103360646823936",
      "url": "https://x.com/naval/status/1002103360646823936",
      "text": "How to Get Rich (without getting lucky):",
      "created_at": "2018-05-31T13:52:11.000Z"
    },
    "author": {
      "id": "745273",
      "handle": "naval",
      "name": "Naval",
      "avatar_url": "https://pbs.twimg.com/profile_images/1256841238298292232/ycqwaMI2.jpg",
      "followers": 2418702,
      "verified": true
    },
    "metrics": {
      "likes": 128400,
      "retweets": 34120,
      "replies": 4210,
      "quotes": 2840,
      "views": 18400000,
      "bookmarks": 61200,
      "total_engagement": 230770
    },
    "engagement_rate": 1.254,
    "engagement_basis": "views",
    "virality_score": 94,
    "virality_grade": "viral",
    "composition": {
      "like_share": 55.6,
      "retweet_share": 14.8,
      "reply_share": 1.8,
      "quote_share": 1.2,
      "bookmark_share": 26.5
    },
    "amplification_rate": 16,
    "save_rate": 26.5,
    "views_per_follower": 7.6,
    "retweeters": {
      "sampled": 100,
      "combined_reach": 4820140,
      "top": [
        {
          "handle": "balajis",
          "name": "Balaji",
          "avatar_url": "https://pbs.twimg.com/profile_images/1626469429208670209/kL0Kbn8L.jpg",
          "followers": 1042800,
          "verified": true
        }
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/mention-checkertools:uselive

See who is mentioning an X account

Pulls recent public mentions of a handle and aggregates them: how many, from how many distinct accounts, how many of those are verified, the combined follower reach behind them, and the single highest-engagement mention and highest-reach mentioner.

Operation id tools.mentionChecker - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: 2 twitterapi.io calls per request. Metered as a live call.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset and 502 upstream_error when the upstream fails.
  • -Only recent public mentions are visible to the upstream. An account with no recent mentions returns 422 invalid_request rather than an empty success, so a silent zero is never mistaken for a working query.
  • -combined_reach sums the mentioners' follower counts. It double counts overlapping audiences and is an upper bound, not impressions.

Parameters

NameInTypeDescription
handlereqbodystringX handle. Accepts a bare name, @name, or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/mention-checker" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "elonmusk"
}'
json
{
  "data": {
    "profile": {
      "handle": "supabase",
      "twitter_id": "1225654873178435584",
      "name": "Supabase",
      "avatar_url": "https://pbs.twimg.com/profile_images/1visible/supabase.jpg",
      "verified": true,
      "followers": 182400
    },
    "totals": {
      "mentions": 48,
      "unique_mentioners": 41,
      "verified_mentioners": 12,
      "combined_reach": 2140800,
      "total_engagement": 9420,
      "avg_engagement": 196
    },
    "biggest_reach_handle": "vercel",
    "top_mention": {
      "id": "1798412008841203712",
      "text": "Shipped our whole auth layer on @supabase in a weekend.",
      "url": "https://x.com/vercel/status/1798412008841203712",
      "created_at": "2026-08-21T16:04:33.000Z",
      "author": {
        "handle": "vercel",
        "name": "Vercel",
        "avatar_url": "https://pbs.twimg.com/profile_images/1767351110131445760/9nAA4mQ0.jpg",
        "followers": 412903,
        "verified": true
      },
      "metrics": {
        "likes": 2140,
        "retweets": 188,
        "replies": 96,
        "views": 184000,
        "total_engagement": 2424
      }
    },
    "top_mentioner": null,
    "mentions": []
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/growth-simulatortools:use

Project how long a follower target takes

Runs the growth model behind the website's simulator: a daily gain derived from content quality, niche weighting, posting cadence and an audience-size scaling ladder, compounded until the target is hit. Returns the time to target, the milestones passed on the way, and the model inputs so the projection can be audited.

Operation id tools.growthSimulator - requires tools:use(sensitive scope, granted only on request)

  • -Free: this endpoint reaches no upstream and spends no credit. It is metered as a normal call, not a live one.
  • -The website's version multiplies each simulated day by a random 0.8-1.2 jitter. The API runs the same model at the jitter's expected value so identical input always returns an identical answer.
  • -The model stops after 30 simulated years. An unreachable target returns projection.reached_target = false with null durations rather than a fabricated number.
  • -This is a planning heuristic, not a forecast of any specific account.

Parameters

NameInTypeDescription
current_followersreqbodyintegerFollower count today. 0 to 100000000.
target_followersreqbodyintegerFollower goal. Must be greater than current_followers.
nichebodystringTopic weighting applied to daily growth.One of: tech, finance, crypto, entertainment, sports, politics, generalDefault: general
qualitybodystringContent quality tier. Sets the baseline daily gain and the minutes spent per post.One of: casual, good, professionalDefault: good
posts_per_daybodyintegerPosting cadence, 1 to 100. Applied as a log2 bonus, so doubling output is worth less each time.Default: 3

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/growth-simulator" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "current_followers": 1200,
  "target_followers": 100000,
  "niche": "crypto",
  "quality": "professional",
  "posts_per_day": 5
}'
json
{
  "data": {
    "input": {
      "current_followers": 1200,
      "target_followers": 100000,
      "niche": "crypto",
      "quality": "professional",
      "posts_per_day": 5
    },
    "projection": {
      "total_days": 892,
      "years": 2,
      "months": 5,
      "total_posts": 4460,
      "total_hours": 2230,
      "reached_target": true
    },
    "daily_followers_gained_at_start": 6.53,
    "milestones": [
      {
        "followers": 2500,
        "days_from_start": 199
      },
      {
        "followers": 5000,
        "days_from_start": 359
      },
      {
        "followers": 10000,
        "days_from_start": 559
      },
      {
        "followers": 25000,
        "days_from_start": 700
      },
      {
        "followers": 50000,
        "days_from_start": 795
      },
      {
        "followers": 100000,
        "days_from_start": 892
      }
    ],
    "model": {
      "niche_multiplier": 1.4,
      "quality_base_followers_per_day": 25,
      "posting_bonus": 1.697,
      "deterministic": true
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/tools/profile-pictools:uselive

Fetch an X profile picture URL

Resolves an X handle to its full-resolution avatar URL. Returns found = false with a null URL when the upstream answered but had no picture, so a missing avatar is distinguishable from a failed call.

Operation id tools.profilePic - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: 1 twitterapi.io call. The upstream response is cached for 24 hours, so repeat lookups of the same handle inside that window are free.
  • -Returns 503 not_configured when TWITTERAPI_IO_KEY is unset and 502 upstream_error when the upstream fails.
  • -Avatar URLs are served by X's CDN and rotate when the user changes their picture. Do not treat one as a permanent identifier.

Parameters

NameInTypeDescription
handlereqquerystringX handle. Accepts a bare name, @name, or a profile URL.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/tools/profile-pic?handle=naval" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "handle": "naval",
    "avatar_url": "https://pbs.twimg.com/profile_images/1256841238298292232/ycqwaMI2.jpg",
    "found": true
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
GET/api/v2/tools/audience-findertools:use

Search the Telegram, TikTok and X catalog

Discovery search over our own directory: filter by audience size, language, category and free text, plus X-only quality filters for account age, follower ratio and posting activity. Reads the catalog we already crawl, so it costs nothing upstream.

Operation id tools.audienceFinder - requires tools:use(sensitive scope, granted only on request)

  • -Free: served from our own catalog and cached for 30 minutes. Metered as a normal call, not a live one.
  • -Bios are contact-stripped before they leave the catalog: email addresses and phone numbers are replaced with placeholders. This is a discovery surface, not a contact database.
  • -No total count is returned. An exact count over the X catalog does not finish inside the query timeout, and an estimate that disagrees with the visible rows reads as a bug. Page with has_more.
  • -Depth is capped at 20 pages (1000 rows) per filter combination. Narrow the filters to reach further.
  • -YouTube and Bluesky are deliberately absent: YouTube data cannot be redistributed under the API terms, and the Bluesky catalog is too stale to be useful here.
  • -A failed catalog query returns 502 upstream_error rather than an empty page, so a database problem is never mistaken for no matches.

Parameters

NameInTypeDescription
platformreqquerystringWhich catalog to search.One of: telegram, tiktok, x
qquerystringFull-text search over name and bio. Capped at 80 characters.
min_audiencequeryintegerMinimum followers (X, TikTok) or members (Telegram).
max_audiencequeryintegerMaximum followers or members.
languagequerystringISO language code as classified by our engine, e.g. en, tr, es.
categoryquerystringCategory slug as classified by our engine, e.g. ai, crypto, news.
sortquerystringOrdering. X supports followers only. Telegram supports subscribers, reach, posts. TikTok supports followers, likes, videos.
pagequeryinteger1-based page. 50 rows per page, 20 pages maximum.Default: 1
agequerystringX only. Account creation window.One of: all, pre2013, pre2016, pre2020, post2023
ratioquerystringX only. Minimum follower-to-following ratio.One of: all, r1, r10, r100
activityquerystringX only. Posts per day band.One of: all, low, normal, high

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/tools/audience-finder?platform=x&q=solidity&min_audience=10000&max_audience=500000" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "platform": "x",
    "rows": [
      {
        "id": "44196397",
        "handle": "cz_binance",
        "name": "CZ",
        "bio": "Ex-Binance. Building. [email hidden]",
        "audience": 9124800,
        "language": "en",
        "category": "crypto",
        "verified": true,
        "avatar_url": "https://pbs.twimg.com/profile_images/1739231242/cz.jpg",
        "profile_url": "https://x.com/cz_binance",
        "weekly_growth": 18402,
        "tier": "S",
        "last_seen": "2026-08-22T04:10:33.000Z",
        "avg_views": null,
        "posts_per_day": null,
        "total_likes": null,
        "video_count": null,
        "bio_link": null,
        "post_count": 41208,
        "account_created_at": "2009-06-02T20:12:29.000Z",
        "location": "Dubai"
      }
    ],
    "page": 1,
    "has_more": true
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
POST/api/v2/tools/instagram/engagementtools:uselive

Measure an Instagram account's engagement

Computes a true engagement rate from the roughly twelve most recent posts, with real per-post like and comment counts. Returns like and comment rates separately, size-adjusted benchmarks, the best and worst post, and the per-post breakdown.

Operation id tools.instagramEngagement - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -A private account returns 404 not_found: the post metrics are not readable.
  • -Only the posts the profile endpoint returns are analyzed, typically twelve. It is a recent-content rate, not a lifetime one.

Parameters

NameInTypeDescription
handlereqbodystringInstagram username. Accepts @name or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/instagram/engagement" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "natgeo"
}'
json
{
  "data": {
    "profile": {
      "handle": "natgeo",
      "name": "National Geographic",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/natgeo.jpg",
      "verified": true,
      "private": false,
      "business": true,
      "category": "Media/News Company",
      "bio": "Experience the world through the eyes of National Geographic photographers.",
      "external_url": "https://on.natgeo.com/instagram",
      "followers": 279400000,
      "following": 148,
      "posts_count": 29841,
      "follower_following_ratio": 1887837.84
    },
    "engagement": {
      "engagement_rate": 0.08,
      "like_rate": 0.079,
      "comment_rate": 0.001,
      "avg_likes": 221400,
      "avg_comments": 1840,
      "avg_views": 1284000,
      "best_post_engagement": 0.21,
      "worst_post_engagement": 0.03
    },
    "rating": "average",
    "rating_score": 52,
    "benchmarks": {
      "below": 0.6,
      "average": 1.2,
      "good": 2,
      "excellent": 3.5
    },
    "posts_analyzed": 12,
    "recent_posts": [
      {
        "likes": 284100,
        "comments": 2140,
        "views": 0,
        "is_video": false,
        "engagement_rate": 0.1
      }
    ],
    "summary": {
      "size_category": "1M+ followers · Mega Influencer"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/instagram/moneytools:uselive

Estimate Instagram sponsorship earnings

Estimates what an account can charge for a sponsored feed post, story and reel, and what that adds up to monthly and yearly. Rates start from the industry baseline per thousand followers and are adjusted by the account's real recent engagement.

Operation id tools.instagramMoney - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -Monthly figures assume four sponsored placements a month. That assumption is the model's, not the account's actual deal flow.
  • -These are market-rate heuristics, not observed deals. Real pricing varies with niche, exclusivity and usage rights.

Parameters

NameInTypeDescription
handlereqbodystringInstagram username. Accepts @name or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/instagram/money" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "garyvee"
}'
json
{
  "data": {
    "profile": {
      "handle": "garyvee",
      "name": "Gary Vaynerchuk",
      "avatar_url": "https://scontent.cdninstagram.com/v/t51.2885-19/garyvee.jpg",
      "verified": true,
      "category": "Entrepreneur",
      "followers": 10240000,
      "posts_count": 14208
    },
    "engagement": {
      "engagement_rate": 0.42,
      "avg_likes": 41200,
      "avg_comments": 1820,
      "posts_analyzed": 12
    },
    "earnings_usd": {
      "feed_post": {
        "low": 28670,
        "high": 71680,
        "avg": 50170
      },
      "story": {
        "low": 11470,
        "high": 28670
      },
      "reel": {
        "low": 40140,
        "high": 100350
      },
      "monthly": {
        "low": 114680,
        "high": 286720
      },
      "yearly": {
        "low": 1376160,
        "high": 3440640
      }
    },
    "tier": "mega",
    "engagement_multiplier": 0.6,
    "engagement_quality": "weak",
    "summary": {
      "tier_label": "Mega Influencer"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/tiktok/engagementtools:uselive

Measure a TikTok account's engagement

Computes engagement from lifetime hearts divided by video count against the follower base, the standard follower-based TikTok rate, and grades it against benchmarks for the account's size band.

Operation id tools.tiktokEngagement - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -TikTok's profile endpoint returns lifetime aggregates, not per-video data, so this is a lifetime average rather than a recent-content rate. It reacts slowly to a change in performance.
  • -Profiles are cached in-process for 5 minutes, so a repeat call on the same handle inside that window does not spend a second credit.

Parameters

NameInTypeDescription
handlereqbodystringTikTok username. Accepts @name or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/tiktok/engagement" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "khaby.lame"
}'
json
{
  "data": {
    "profile": {
      "handle": "khaby.lame",
      "name": "Khabane lame",
      "avatar_url": "https://p16-sign-va.tiktokcdn.com/khaby.jpeg",
      "verified": true,
      "private": false,
      "bio": "If you want to laugh you're in the right place",
      "bio_link": null,
      "followers": 162400000,
      "following": 82,
      "friends": 61,
      "total_likes": 2510000000,
      "video_count": 1284,
      "created_at": "2020-03-14T00:00:00.000Z",
      "account_age_days": 2354,
      "follower_following_ratio": 1980487.8
    },
    "engagement": {
      "engagement_rate": 1.2,
      "avg_likes_per_video": 1954828,
      "likes_per_follower": 15.46
    },
    "rating": "good",
    "rating_score": 71,
    "benchmarks": {
      "below": 0.4,
      "average": 1,
      "good": 2,
      "excellent": 3.5
    },
    "summary": {
      "size_category": "10M+ followers · Mega Influencer"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/tiktok/moneytools:uselive

Estimate TikTok brand-deal earnings

Estimates sponsored-post, monthly and yearly earnings from follower count and engagement, with the posting cadence derived from lifetime output over the account's age and capped to a realistic sponsored range.

Operation id tools.tiktokMoney - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -The engagement multiplier is clamped to 0.6x-1.6x around a 3.5% TikTok baseline, so very large accounts with a diluted lifetime rate land at the floor.
  • -Assumes roughly a quarter of posts are monetized. That is the model's assumption, not the account's actual deal flow.

Parameters

NameInTypeDescription
handlereqbodystringTikTok username. Accepts @name or a profile URL. Also accepted as `username`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/tiktok/money" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "zachking"
}'
json
{
  "data": {
    "profile": {
      "handle": "zachking",
      "name": "Zach King",
      "avatar_url": "https://p16-sign-va.tiktokcdn.com/zachking.jpeg",
      "verified": true,
      "followers": 82100000,
      "total_likes": 1080000000,
      "video_count": 812,
      "account_age_days": 2988
    },
    "engagement": {
      "engagement_rate": 1.62,
      "avg_likes_per_video": 1330049,
      "posts_per_month": 8
    },
    "earnings_usd": {
      "per_post": {
        "low": 631170,
        "high": 1262340,
        "avg": 946760
      },
      "monthly": {
        "low": 1262340,
        "high": 2524680
      },
      "yearly": {
        "low": 15148080,
        "high": 30296160
      }
    },
    "tier": "mega",
    "engagement_multiplier": 0.6,
    "engagement_quality": "weak",
    "summary": {
      "tier_label": "Mega Influencer"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/youtube/moneytools:uselive

Estimate YouTube ad earnings

Estimates ad revenue per video, per month and per year from lifetime views spread over the channel's age, using a net RPM band. Returns the RPM band used so the arithmetic can be checked.

Operation id tools.youtubeMoney - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -est_monthly_views spreads lifetime views evenly across the channel's age. For a channel whose output changed sharply it will be wrong in both directions.
  • -The $1-$5 RPM band is a broad net figure. Real RPM swings by an order of magnitude between niches and audience countries.
  • -Ad revenue only. Sponsorships, memberships and merch are not modelled.

Parameters

NameInTypeDescription
handlereqbodystringYouTube @handle, UC… channel id, or channel URL. Also accepted as `channel` or `url`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/youtube/money" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "mkbhd"
}'
json
{
  "data": {
    "channel": {
      "channel_id": "UCBJycsmduvYEL83R_U4JriQ",
      "handle": "mkbhd",
      "name": "Marques Brownlee",
      "avatar_url": "https://yt3.googleusercontent.com/mkbhd.jpg",
      "verified": true,
      "country": "US",
      "subscribers": 20100000,
      "total_views": 4280000000,
      "video_count": 1684,
      "account_age_days": 6841,
      "joined_date_text": "Joined Mar 21, 2008"
    },
    "views": {
      "avg_views_per_video": 2541567,
      "est_monthly_views": 19024889
    },
    "rpm_usd": {
      "low": 1,
      "high": 5
    },
    "earnings_usd": {
      "per_video": {
        "low": 2540,
        "high": 12710
      },
      "monthly": {
        "low": 19020,
        "high": 95120
      },
      "yearly": {
        "low": 228240,
        "high": 1141440
      }
    },
    "tier": "mega",
    "summary": {
      "tier_label": "Mega Influencer"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}
POST/api/v2/tools/youtube/channel-valuetools:uselive

Estimate what a YouTube channel is worth

Estimates a sale price range for a channel by blending an earnings multiple (18-36x estimated monthly profit) with a per-subscriber asset value, and grades the channel 0-100 on subscribers, views, output volume and upload consistency.

Operation id tools.youtubeChannelValue - requires tools:use(sensitive scope, granted only on request)

  • -Costs money: exactly one ScrapeCreators credit per call. Metered as a live call.
  • -Returns 503 not_configured when SCRAPECREATORS_API_KEY is unset, 404 not_found for an unknown or unreachable account, and 502 upstream_error when the upstream fails.
  • -Ignores NEXT_PUBLIC_USE_MOCK_DATA: there is no mock path for this data.
  • -The valuation rests on est_monthly_earnings_usd, which itself rests on a mid-band RPM and an evenly-spread view history. Errors there flow straight through to the price.
  • -This is a desk estimate for triage, not an appraisal. A real sale prices watch time, audience geography, sponsor history and channel strikes, none of which are visible here.

Parameters

NameInTypeDescription
handlereqbodystringYouTube @handle, UC… channel id, or channel URL. Also accepted as `channel` or `url`.

Example request

curl
curl -sS -X POST "https://api.playersells.com/v2/api/v2/tools/youtube/channel-value" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "handle": "veritasium"
}'
json
{
  "data": {
    "channel": {
      "channel_id": "UCHnyfMqiRRG1u-2MsSQLbXA",
      "handle": "veritasium",
      "name": "Veritasium",
      "avatar_url": "https://yt3.googleusercontent.com/veritasium.jpg",
      "verified": true,
      "country": "US",
      "subscribers": 17800000,
      "total_views": 3140000000,
      "video_count": 412,
      "account_age_days": 6210,
      "joined_date_text": "Joined Jul 21, 2010"
    },
    "views": {
      "avg_views_per_video": 7621359,
      "est_monthly_views": 15169082,
      "est_monthly_earnings_usd": 37920
    },
    "valuation": {
      "estimated_value_usd": 690000,
      "value_low_usd": 519300,
      "value_high_usd": 860800,
      "score": 84,
      "rating": "premium"
    },
    "scores": {
      "subscriber": 38,
      "view": 28,
      "activity": 12,
      "consistency": 6
    },
    "summary": {
      "strengths": [
        "Mega Influencer subscriber base",
        "High average views per video",
        "Established channel with long history",
        "Verified channel"
      ],
      "weaknesses": [
        "Infrequent uploads"
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "cache_age_s": 0
  }
}

Leads

leads:read4 endpoints

B2B lead data: LinkedIn companies and decision makers, plus X follower-graph exports. Sensitive by nature, separately scoped, and never granted by default.

GET/api/v2/leads/linkedin/companiesleads:read

LinkedIn company records, filterable

The B2B company layer: firmographics, LinkedIn reach and its 30-day trend, matched social handles across our own catalogs, a contactability score, and the reasons a row is not actionable yet. Filterable by country, industry, size band, contact availability and website presence.

Operation id leads.linkedin.companies.list - requires leads:read(sensitive scope, granted only on request)

  • -Bulk lead data carries compliance obligations for the caller. These rows are personal data about identifiable people, collected from public sources and held by us under a legitimate-interest basis for our own outreach. Receiving them through this API does not transfer that basis: you need your own lawful ground, you must be able to identify the source and date for anything you store, and you must be able to honour an erasure or objection request against your copy. Suppression on our side does not delete yours.
  • -No address in this API is generated, permuted or pattern-built. best_email is the highest-confidence address the crawler read off a real page; a company with none returns null rather than a guess.
  • -LinkedIn is full of stale duplicate company pages. A row with a handful of linked staff, no follower count and no website is usually one of them; the flags array calls that out as 'likely duplicate page' rather than leaving it to look like a small business.
  • -size and has_website are applied after the catalog filter, inside the scan window. A page can therefore return fewer rows than limit while still carrying a next_cursor - keep paging until next_cursor is null.
  • -Pagination is depth-capped at 500 rows per filter combination. Narrow with country, industry or min_followers rather than paging deeper.
  • -readiness measures how CONTACTABLE and verifiable a lead is, not how valuable the company is. We cannot know the second, and a score that pretended to would be trusted for exactly the decision it is worst at.
  • -country_code is derived from the location string. An older free-text column held values like 'England', 'Ontario' and 'Gujarat', which is why hq.country_text is returned separately and is not filterable.

Parameters

NameInTypeDescription
countryquerystringISO 3166-1 alpha-2, derived by the engine from the free-text location. LinkedIn writes 'City, Region' and rarely names a country, so filter on this rather than on hq.country_text.
industryquerystringIndustry exactly as LinkedIn labels it.
sizequerystringEmployee band exactly as LinkedIn labels it. Applied inside the scan window - see the pagination note.
has_emailquerybooleanOnly companies with at least one non-suppressed contact address.
has_websitequerybooleanOnly companies with a resolved website domain. Applied inside the scan window.
has_decision_makerquerybooleanOnly companies where we can name a founder, C-level, VP or director.
has_socialquerybooleanOnly companies with a social handle that matched a row in one of our catalogs.
min_followersqueryintegerLinkedIn follower floor.
active_daysqueryintegerOnly companies that posted within this many days.
qquerystringSubstring match on company name, slug or website domain.
sortquerystringreadiness orders by the columns the contactability score is built from, then applies the exact score to the page.One of: followers, readiness, recent, staffDefault: followers
limitqueryintegerRows per page, 1-200.Default: 50
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/leads/linkedin/companies?country=DE&industry=Software%20Development&size=51-200%20employees" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "companies": [
      {
        "id": "modash",
        "name": "Modash",
        "tagline": "Find every creator on the planet",
        "industry": "Software Development",
        "size_band": "51-200 employees",
        "staff_count": 132,
        "founded_year": 2018,
        "followers": 31146,
        "follower_delta_30d": 412,
        "avg_reactions": 41,
        "last_post_at": "2026-08-21T09:14:00.000Z",
        "hq": {
          "location": "Tallinn, Estonia",
          "country_text": "Estonia",
          "country_code": "EE"
        },
        "website_domain": "modash.io",
        "linkedin_url": "https://www.linkedin.com/company/modash",
        "contact": {
          "email_count": 3,
          "phone_count": 1,
          "best_email": "[email protected]",
          "best_email_is_role": true,
          "best_email_mx_ok": true
        },
        "socials": [
          {
            "platform": "x",
            "handle": "modash_io",
            "matched": true,
            "followers": 4820
          }
        ],
        "decision_makers": 2,
        "readiness": 95,
        "flags": []
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/leads/linkedin/companies/{id}leads:read

One company with socials, contacts and decision makers

The full record for a single company, including the people we can name there with their titles, seniority and how we know about them. Same shape as a list row plus the people array, so a detail view can never disagree with the list it came from.

Operation id leads.linkedin.companies.get - requires leads:read(sensitive scope, granted only on request)

  • -Bulk lead data carries compliance obligations for the caller. These rows are personal data about identifiable people, collected from public sources and held by us under a legitimate-interest basis for our own outreach. Receiving them through this API does not transfer that basis: you need your own lawful ground, you must be able to identify the source and date for anything you store, and you must be able to honour an erasure or objection request against your copy. Suppression on our side does not delete yours.
  • -No address in this API is generated, permuted or pattern-built. best_email is the highest-confidence address the crawler read off a real page; a company with none returns null rather than a guess.
  • -People whose only link to a company is being named inside a post are excluded everywhere. That relation is not evidence of employment, and a lead list that includes it will eventually mail a rival executive about their competitor's product.
  • -The people array is capped at 25 rows, most senior first, so a large employer does not ship its whole staff list.
  • -relation says how we know: employee (LinkedIn staff module or a website team page) or author (posted on the company feed, usually staff and sometimes a reshare). source says where the row came from.
  • -Individual contact rows are not served. One best address per company is, together with the counts behind it.

Parameters

NameInTypeDescription
idreqpathstringCompany slug, the last path segment of its linkedin.com/company/ URL.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/leads/linkedin/companies/modash" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "company": {
      "id": "modash",
      "name": "Modash",
      "industry": "Software Development",
      "size_band": "51-200 employees",
      "followers": 31146,
      "hq": {
        "location": "Tallinn, Estonia",
        "country_code": "EE"
      },
      "contact": {
        "best_email": "[email protected]",
        "best_email_is_role": true,
        "email_count": 3
      },
      "socials": [
        {
          "platform": "x",
          "handle": "modash_io",
          "matched": true,
          "followers": 4820
        }
      ],
      "people": [
        {
          "name": "Avery Lindqvist",
          "title": "Co-Founder & CEO",
          "seniority": "founder",
          "seniority_label": "Founder",
          "relation": "employee",
          "source": "website",
          "li_slug": "averylindqvist",
          "decision_maker": true
        }
      ],
      "decision_makers": 2,
      "readiness": 95,
      "flags": []
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}
GET/api/v2/leads/linkedin/peopleleads:read

Named people and decision makers

The people layer on its own, filterable by company, seniority, job title and the company's country or industry. Each row carries the company it belongs to, so a decision-maker search returns a usable lead rather than a name that needs a second lookup.

Operation id leads.linkedin.people.list - requires leads:read(sensitive scope, granted only on request)

  • -Bulk lead data carries compliance obligations for the caller. These rows are personal data about identifiable people, collected from public sources and held by us under a legitimate-interest basis for our own outreach. Receiving them through this API does not transfer that basis: you need your own lawful ground, you must be able to identify the source and date for anything you store, and you must be able to honour an erasure or objection request against your copy. Suppression on our side does not delete yours.
  • -People whose only link to a company is being named inside a post are excluded everywhere. That relation is not evidence of employment, and a lead list that includes it will eventually mail a rival executive about their competitor's product.
  • -No email is attached to a person row. Contact addresses are held per company and served on the company endpoints, which keeps a title lookup from doubling as a contact export.
  • -Title fill is uneven: LinkedIn's employee module often gives a headline and no explicit title, so title comes mostly from company websites. Match on both, which is what the title filter does.
  • -Rows are returned in stable discovery order (internal id ascending), not by seniority. Use seniority or decision_makers to isolate the rows you want rather than relying on ordering.
  • -location, education and profile photos are stored but deliberately not served.

Parameters

NameInTypeDescription
companyquerystringCompany slug.
seniorityquerystringExact seniority band.One of: founder, c_level, vp, director, manager, staff, unknown
decision_makersquerybooleanShortcut for founder, c_level, vp and director together. Overrides seniority when both are given.
titlequerystringCase-insensitive substring match against job title and LinkedIn headline.
countryquerystringISO 3166-1 alpha-2 of the person's company.
industryquerystringIndustry of the person's company.
limitqueryintegerRows per page, 1-200.Default: 50
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/leads/linkedin/people?company=modash&seniority=founder&title=head%20of%20growth" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "people": [
      {
        "id": "48211",
        "name": "Avery Lindqvist",
        "title": "Co-Founder & CEO",
        "headline": null,
        "seniority": "founder",
        "seniority_label": "Founder",
        "decision_maker": true,
        "relation": "employee",
        "source": "website",
        "li_slug": "averylindqvist",
        "linkedin_url": "https://www.linkedin.com/in/averylindqvist",
        "company": {
          "id": "modash",
          "name": "Modash",
          "industry": "Software Development",
          "country_code": "EE",
          "website_domain": "modash.io"
        }
      }
    ]
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}
GET/api/v2/leads/x/followersleads:read

Follower-graph export for one X account

The accounts we know to follow a given handle, filterable and sortable, with an optional language and country breakdown of the whole known set. Two different numbers are always returned side by side: what X reports on the profile, and how many of those followers our catalog actually holds as edges.

Operation id leads.x.followers - requires leads:read(sensitive scope, granted only on request)

  • -Bulk lead data carries compliance obligations for the caller. These rows are personal data about identifiable people, collected from public sources and held by us under a legitimate-interest basis for our own outreach. Receiving them through this API does not transfer that basis: you need your own lawful ground, you must be able to identify the source and date for anything you store, and you must be able to honour an erasure or objection request against your copy. Suppression on our side does not delete yours.
  • -followers_known is what WE hold, not what X reports. Every account the crawler expands contributes its following list, and read in reverse that is a follower index - so it only ever covers accounts already in our catalog. Measured: 37,812 of @elonmusk's 241M, 1,725 of @RTErdogan's 19.8M.
  • -The edge scan is materialized at the first 60,000 edges per account before filtering and sorting. Above that the ranking is the strongest among the first 60,000 found, not the true global top. The unbounded version of this query took 87-107 seconds and is why the cap exists.
  • -Pages are capped at 200 rows and the export is depth-capped at 10000 rows per handle and filter combination.
  • -facets describe the whole known set rather than the filtered page, so changing a filter narrows the list without changing the composition behind it.
  • -Bios are returned verbatim as the crawler read them. If yours is a surface where a bio could be mined for contact details, strip it on your side.

Parameters

NameInTypeDescription
handlereqquerystringX handle without the @.
sortquerystringfollowers ranks by the follower's own audience, influence by citation score, recent by when the edge was last confirmed.One of: followers, influence, recentDefault: followers
min_followersqueryintegerOnly followers with at least this many followers of their own.
languagequerystringFollower's detected language code.
countryquerystringFollower's resolved ISO 3166-1 alpha-2 country.
verifiedquerybooleanOnly legacy-verified or blue-verified followers.
qquerystringSubstring match on the follower's handle or display name.
facetsquerybooleanInclude the language and country composition of the whole known set. Computed over the known set, not over the filtered page.
limitqueryintegerRows per page, 1-200. Capped lower than other list endpoints because this is an export.Default: 100
cursorquerystringOpaque cursor from meta.page.next_cursor.

Example request

curl
curl -sS "https://api.playersells.com/v2/api/v2/leads/x/followers?handle=elonmusk&sort=followers&min_followers=10000&language=en" \
  -H "Authorization: Bearer $PLAYERSELLS_API_KEY"
json
{
  "data": {
    "target": {
      "user_id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk",
      "followers_reported": 241382119,
      "followers_known": 37812,
      "known_share_pct": 0.0157
    },
    "followers": [
      {
        "user_id": "1234567890",
        "username": "example",
        "name": "Example Account",
        "followers": 812004,
        "following": 1204,
        "tweets": 44120,
        "is_blue_verified": true,
        "language": "en",
        "country": "US",
        "edge_seen": "2026-08-18T11:02:41.000Z",
        "profile_url": "https://x.com/example"
      }
    ],
    "facets": {
      "languages": [
        {
          "value": "en",
          "count": 21904
        }
      ],
      "countries": [
        {
          "value": "US",
          "count": 9442
        }
      ]
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42,
    "page": {
      "limit": 50,
      "next_cursor": "eyJrIjoiMTczNDU2IiwiZCI6ImEifQ",
      "count": 50
    }
  }
}