API
Read-only JSON (and CSV for exports). Optional X-Api-Key lifts per-IP rate limits and unlocks larger page sizes. CORS open. All responses cache-friendly.
Base URL: https://tgadsspy.com
OpenAPI 3.1 spec: /api/v1/openapi.json — feed it to openapi-typescript, openapi-generator-cli or Postman/Insomnia for a typed client in any language.
Pagination: all list endpoints accept limit and offset. The response meta block carries the running total.
Subscribe to alerts via our bot: @tgadsspybot — use /subscribe crypto or any keyword.
All endpoints work without an API key — you get the ANON tier. Pass X-Api-Key: <your-key> to unlock FREE or PRO limits. Key management lives on /account/api-keys.
| Plan | Rate limit | Max limit | Max offset | Access |
|---|---|---|---|---|
| ANON | 60 req / min (burst) · 1 / s sustained | 50 | 1 000 | no key required |
| FREE | 600 req / min · 10 / s sustained | 100 | 10 000 | free signup |
| PRO | 6 000 req / min · 100 / s sustained | 500 | 100 000 | paid plan |
Send the key in the X-Api-Key header:
curl -H 'X-Api-Key: tgak_••••••••••••••••' \
'https://tgadsspy.com/api/v1/ads?limit=100'Every response carries plan context so you can monitor usage client-side:
| Header | Meaning |
|---|---|
X-Plan | Tier resolved from your key (or ANON). |
X-RateLimit-Limit | Bucket capacity for your tier. |
X-RateLimit-Remaining | Tokens left after this request. |
X-RateLimit-Reset | Seconds until the bucket fully refills. |
X-Request-Id | Trace id for support. Echoed back if you send it in the request, otherwise generated server-side. Include it when reporting issues. |
X-Response-Time-Ms | Server-side latency in milliseconds (gate entry → response). Useful for client-side performance dashboards. |
When the bucket is empty we return HTTP 429. The body includes an upgradeHint pointing at the next tier:
{
"error": "rate_limited",
"plan": "ANON",
"retryAfter": 12,
"upgradeHint": "Get a free API key for 10× higher limits."
}Respect the Retry-After header. If you hit limits repeatedly, upgrade at /account/api-keys.
/rss.xml— RSS feed — latest 50Global RSS feed of the 50 most recent creatives. Also per-niche: /niches/<slug>/rss.xml
curl https://tgadsspy.com/rss.xmlconst r = await fetch('https://tgadsspy.com/rss.xml')
const xml = await r.text()
console.log(xml)import requests
r = requests.get('https://tgadsspy.com/rss.xml')
print(r.text)<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Telegram Ads Spy — latest creatives</title>
<item>
<title>...</title>
<link>https://tgadsspy.com/ads/...</link>
<pubDate>...</pubDate>
<category>crypto</category>
...
</item>
</channel>
</rss>/feed.json— JSON Feed v1.1 — modern aggregator formatSame content stream as /rss.xml but in JSON Feed v1.1 spec (jsonfeed.org). Modern aggregators — Feedbin, Inoreader, NewsBlur, Reeder, Readwise — parse JSON natively. Each item has structured tags array (niche, geo:RU, lang:en, source:TON, cashier flag) для aggregator-side filtering. CORS *. Cache 5 min. CC-BY-4.0.
curl https://tgadsspy.com/feed.jsonconst r = await fetch('https://tgadsspy.com/feed.json')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/feed.json')
print(r.json()){
"version": "https://jsonfeed.org/version/1.1",
"title": "Telegram Ads Spy — latest Telegram ads",
"home_page_url": "https://tgadsspy.com/",
"feed_url": "https://tgadsspy.com/feed.json",
"items": [
{
"id": "https://tgadsspy.com/ads/abc123",
"url": "https://tgadsspy.com/ads/abc123",
"title": "...",
"content_text": "...",
"date_published": "2026-05-06T...",
"tags": ["crypto", "geo:RU", "lang:ru", "source:TON", "cashier"],
"_tgadsspy": { "advertiser": "...", "cta_url": "...", "is_cashier": true }
}
],
"_tgadsspy": { "license": "CC BY 4.0", "rss_alternate": "https://tgadsspy.com/rss.xml" }
}/api/healthz— Health checkCheap liveness probe. Useful for uptime monitors.
curl https://tgadsspy.com/api/healthzconst r = await fetch('https://tgadsspy.com/api/healthz')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/healthz')
print(r.json()){"ok":true,"service":"tgadsspy","version":"0.0.1","ts":"..."}/api/product— Product descriptor (machine-readable)Single-call Telegram Ads Spy descriptor for AI clients and llmstxt.org consumers. Returns live stats (creatives/channels/cashier/mini-apps + glossary count), features narrative, ecosystem block (sister-products with Wall live_stats lazy-block), and data_feeds map. CORS *. Cache 5 min.
curl https://tgadsspy.com/api/productconst r = await fetch('https://tgadsspy.com/api/product')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/product')
print(r.json()){
"name": "tgadsspy",
"tagline": "Telegram Ads Intelligence — public archive of all sponsored ads",
"stats": { "creatives": 36742, "channels": 2046033, "advertisers": 14908, "cashier_creatives": 412, "mini_app_bots": 18247, "..." },
"features": { "creative_archive": "...", "cashier_vertical": "...", "mini_apps": "...", "programmatic_seo": { "glossary_terms": 74, "..." } },
"ecosystem": { "company": "G.media", "sister_products": [{ "name": "Wall", "live_stats": {...}, "..." }] },
"data_feeds": { "ecosystem_api": "https://tgadsspy.com/api/v1/ecosystem", "..." },
"meta": { "schema_version": "1.1", "..." }
}/api/v1/ecosystem— G.Media ecosystem feed (combined)Single envelope returning the entire G.Media product family — replaces two-fetch pattern (/api/product + wall.tg/api/product) for cross-product consumers. Includes legal entity, products (Telegram Ads Spy + Wall) with localised pitches, bots (flagship/assistant/ai/utility), TLD-family domains, live Telegram Ads Spy stats, Wall daily snapshot (cron-mirrored, fallback to WALL_PRODUCT_FACTS if not yet collected). CORS *. Cache 5 min.
curl https://tgadsspy.com/api/v1/ecosystemconst r = await fetch('https://tgadsspy.com/api/v1/ecosystem')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/ecosystem')
print(r.json()){
"ecosystem": { "name": "G.Media", "founder": {...}, "hq": {...} },
"products": [{ "id": "tgadsspy", "pitch": {"en":"...","ru":"..."} }, { "id": "wall", "..." }],
"bots": [{ "handle": "gidbot", "category": "flagship", "..." }, "..."],
"domains": [{ "url": "https://g.media", "canonical": true }, "..."],
"live": {
"tgadsspy": { "stats": { "creatives": 36742, "cashier_creatives": 412, "mini_app_bots": 18247, "..." } },
"wall": { "stats": { "users_total": 70663, "branches_count": 18, "..." } }
},
"data_feeds": { "tgadsspy_*": "...", "wall_*": "..." },
"meta": { "schema_version": "1.0", "license": "CC BY 4.0" }
}/api/v1/glossary— Glossary feed (all terms, EN+RU)Single-call public JSON feed of every Telegram-ads glossary term — 74+ entries with EN+RU localised copy, category and related-term cross-links. Filterable by ?category=ad-types|metrics|technical|... (validated, returns 400 on unknown) or ?slug=a,b,c (regex-validated). Replaces crawling /info/<slug> pages individually. Pure import from lib/glossary.ts, no DB hit. CORS *. Cache 24h.
| Param | Type | Description |
|---|---|---|
category | string | platform | ad-types | metrics | actors | strategies | compliance | technical |
slug | comma-separated | Filter to specific entries. Example: ?slug=telegram-stars,cashier,xtr |
curl "https://tgadsspy.com/api/v1/glossary?category=ad-types"const r = await fetch('https://tgadsspy.com/api/v1/glossary?category=ad-types')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/glossary?category=ad-types')
print(r.json()){
"data": [
{
"slug": "telegram-stars",
"category": "platform",
"url": "https://tgadsspy.com/info/telegram-stars",
"en": { "term": "Telegram Stars (XTR)", "definition": "..." },
"ru": { "term": "Telegram Stars (XTR)", "definition": "..." },
"related": [
{ "slug": "cashier", "url": "https://tgadsspy.com/info/cashier" }
]
}
],
"meta": {
"total": 74,
"categories": ["platform","ad-types","metrics","actors","strategies","compliance","technical"],
"by_category": { "platform": 5, "ad-types": 8, "..." },
"license": "CC BY 4.0",
"source": "lib/glossary.ts"
}
}/api/v1/glossary.csv— Glossary feed — CSV mirrorSame dataset as /api/v1/glossary but in flat 8-column CSV format — slug, category, url, term_en, definition_en, term_ru, definition_ru, related_slugs (pipe-separated). Researcher-friendly for spreadsheet analysis and academic citations. Same filters as JSON. OWASP formula-injection escaped. CORS *. Cache 24h.
| Param | Type | Description |
|---|---|---|
category | string | platform | ad-types | metrics | actors | strategies | compliance | technical |
slug | comma-separated | Filter to specific entries |
curl "https://tgadsspy.com/api/v1/glossary.csv?category=ad-types" > glossary-ad-types.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/glossary.csv?category=ad-types')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/glossary.csv?category=ad-types')
with open('ads.csv', 'wb') as f:
f.write(r.content)slug,category,url,term_en,definition_en,term_ru,definition_ru,related_slugs
telegram-stars,platform,https://tgadsspy.com/info/telegram-stars,Telegram Stars (XTR),"Telegram's native in-app currency...",Telegram Stars (XTR),"Внутренняя валюта Telegram...",cashier|sponsored-invoice|mini-app|sponsored-message
cashier,ad-types,https://tgadsspy.com/info/cashier,Cashier creative,"A sponsored message that triggers a payment flow...",Cashier-креатив,"Спонсорское сообщение, запускающее платёжный flow...",telegram-stars|sponsored-invoice|premium-giveaway|main-app
.../api/v1/stats— Aggregate statsGlobal counts (creatives, channels, advertisers, impressions) plus today-only deltas and payment-source breakdown. Cached 60s.
curl https://tgadsspy.com/api/v1/statsconst r = await fetch('https://tgadsspy.com/api/v1/stats')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/stats')
print(r.json()){
"data": {
"creatives": 1234,
"channels": 156,
"advertisers": 89,
"impressions": 4321,
"paymentBreakdown": { "EUR_ADS_CABINET": 1100, "TON": 134 },
"today": {
"creatives": 48,
"impressions": 92,
"advertisers": 5,
"vsYesterdayPct": 12
},
"ts": "2026-04-19T..."
}
}/api/v1/miniapps— List Telegram mini-appsLeaderboard of bots running main-app mini-apps. Sortable by active users, Telegram official rank, Stars rating level, or D7 trending growth. Filter by niche or sponsored-enabled.
| Param | Type | Description |
|---|---|---|
sort | mau|stars|tgrank|trending | mau = botActiveUsers DESC (default). stars = botStarsRatingLevel DESC. tgrank = popularBotsRank ASC NULLS LAST (Telegram curation). trending = D7 growth % via BotMauSnapshot CTE. |
niche | string | Exact niche slug from INDEXABLE list (crypto, gambling, finance, games, trading, …). Validated server-side, unknown slugs ignored. |
sponsored | 1|true|0|false | When 1/true, return only mini-apps with active Telegram Ads (botSponsoredEnabled=true). |
limit | int 1–500 | Default 50. Max depends on plan: ANON 50, FREE 100, PRO 500. |
offset | int | For pagination. Default 0. |
curl "https://tgadsspy.com/api/v1/miniapps?sort=trending&niche=crypto&limit=20"const r = await fetch('https://tgadsspy.com/api/v1/miniapps?sort=trending&niche=crypto&limit=20')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/miniapps?sort=trending&niche=crypto&limit=20')
print(r.json()){
"data": [
{
"username": "minigame_bot",
"title": "Mini Game",
"avatarUrl": "/m/ab/abc123.jpg",
"botActiveUsers": 1245000,
"botStarsRatingLevel": 12,
"botSponsoredEnabled": true,
"lastBotEnrichmentAt": "2026-05-05T...",
"popularBotsRank": 7,
"botNiche": "games",
"growthPct": 18.4
}
],
"meta": { "total": 132, "limit": 20, "offset": 0, "plan": "FREE", "sort": "trending" }
}/api/v1/miniapps.csv— Mini-app leaderboard — CSV mirror10-column CSV mirror of /api/v1/miniapps for spreadsheet/research workflows. Same filters and sort options (mau/stars/tgrank — no trending CTE for CSV). Requires X-Api-Key — ANON blocked (matches /api/v1/cashier-creatives.csv pattern). OWASP formula-injection escaped. Plan-tier limit cap: FREE 100, PRO 500.
| Param | Type | Description |
|---|---|---|
sort | mau|stars|tgrank | mau = botActiveUsers DESC (default). stars = botStarsRatingLevel DESC. tgrank = popularBotsRank ASC NULLS LAST. |
niche | string | Exact niche slug from INDEXABLE list. |
sponsored | 1|true|0|false | Return only sponsored-enabled mini-apps. |
stars10 | 1|true|0|false | Filter botStarsRatingLevel >= 10. |
limit | int 1–500 | Default 500. Plan caps: FREE 100, PRO 500. |
curl -H "X-Api-Key: $KEY" "https://tgadsspy.com/api/v1/miniapps.csv?sort=stars&niche=crypto" > miniapps-crypto.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/miniapps.csv?sort=stars&niche=crypto')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/miniapps.csv?sort=stars&niche=crypto')
with open('ads.csv', 'wb') as f:
f.write(r.content)username,title,niche,mau,stars_level,popular_bots_rank,sponsored_enabled,avatar_url,last_enriched_at,profile_url
notcoin_bot,Notcoin,crypto,42000000,23,1,true,https://tgadsspy.com/m/ab/abc.jpg,2026-05-06T...,https://tgadsspy.com/miniapps/notcoin_bot
catizen_bot,Catizen,crypto,18500000,18,3,true,...,...,https://tgadsspy.com/miniapps/catizen_bot
.../api/v1/miniapps/<username>— Mini-app detail with time-seriesSingle bot detail: full cashier-signals + last 30 BotMauSnapshot points (takenAt + activeUsers + starsLevel) для researcher sparkline / growth analysis. 404 если bot не enriched (botHasMainApp != true) или username не существует.
| Param | Type | Description |
|---|---|---|
username | string | Bot username (path param). Lowercased, validated against `^[a-z0-9_]{3,32}$`. |
curl "https://tgadsspy.com/api/v1/miniapps/notcoin_bot"const r = await fetch('https://tgadsspy.com/api/v1/miniapps/notcoin_bot')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/miniapps/notcoin_bot')
print(r.json()){
"data": {
"username": "notcoin_bot",
"title": "Notcoin",
"description": "Tap-to-earn TON mini-app",
"avatarUrl": "/m/cd/cd456.jpg",
"botActiveUsers": 42000000,
"botStarsRatingLevel": 23,
"botStarsRatingStars": 1240000,
"botSponsoredEnabled": true,
"popularBotsRank": 1,
"popularBotsRankedAt": "2026-05-05T...",
"botNiche": "crypto",
"lastBotEnrichmentAt": "2026-05-05T...",
"timeseries": [
{ "takenAt": "2026-04-06T...", "activeUsers": 38500000, "starsLevel": 21 },
{ "takenAt": "2026-04-07T...", "activeUsers": 39100000, "starsLevel": 21 }
]
},
"meta": { "plan": "FREE" }
}/api/v1/ads— List creativesPaginated feed of ad creatives. All filters compose; default sort is newest first.
| Param | Type | Description |
|---|---|---|
q | string | Case-insensitive substring match on text or title. |
advertiser | string | Filter by advertiser slug (see /api/v1/advertisers). Returns empty array for unknown slug. |
paymentSource | TON|EUR_ADS_CABINET|UNKNOWN | Exact enum. |
source | ton|eur|all | Shorter alias for paymentSource. |
dest | all|telegram|external | Filter by CTA destination: telegram (t.me links) or external (web URLs). |
niche | string | Exact niche slug (crypto, trading, vpn, …). Granular taxonomy. |
category | finance|gambling|tech|content|commerce|life | Coarse super-group filter — expands to all niches inside. Mutually exclusive with niche (niche wins if both passed). |
geo | string | ISO country code, auto-uppercased. |
lang | string | Creative language hint. |
sort | new|reach | Sort order: new = firstSeenAt desc (default), reach = reachEstimate desc. |
days | int 1–365 | Limit to creatives first seen in the last N days. |
since | ISO timestamp | Creatives first seen after this timestamp (ISO 8601). Used by live polling. |
limit | int 1–500 | Default 50. Max depends on plan: ANON 50, FREE 100, PRO 500. |
offset | int | For pagination. Default 0. |
curl "https://tgadsspy.com/api/v1/ads?niche=crypto&source=eur&limit=20"const r = await fetch('https://tgadsspy.com/api/v1/ads?niche=crypto&source=eur&limit=20')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/ads?niche=crypto&source=eur&limit=20')
print(r.json()){
"data": [
{
"id": "c...",
"title": "P's Trading Journal",
"text": "Private Crypto trading journal...",
"paymentSource": "EUR_ADS_CABINET",
"niche": "crypto",
"category": "finance",
"geo": null,
"lang": null,
"firstSeenAt": "2026-04-18T...",
"lastSeenAt": "2026-04-18T...",
"placementCount": 0,
"impressionCount": 1,
"reachEstimate": "0",
"advertiserId": "...",
"advertiserSlug": "crypto-signals",
"advertiserName": "@cryptosignals",
"mediaUrl": "https://cdn4.telesco.pe/...jpg",
"ctaButtonText": "VIEW BOT",
"ctaUrl": "https://t.me/...",
"targetAvatarUrl": null,
"targetTitle": null
}
],
"meta": { "total": 31, "limit": 20, "offset": 0, "plan": "ANON" }
}/api/v1/ads.csv— Export creatives as CSVSame filters as /api/v1/ads. Returns text/csv with 20 columns (including category between paymentSource and niche; minDisplaySec/maxDisplaySec at the end), up to 500 rows. Attachment download.
| Param | Type | Description |
|---|---|---|
q | string | Same as /api/v1/ads. |
advertiser | string | Filter by advertiser slug. Same as /api/v1/ads. |
source | ton|eur | Same as /api/v1/ads. |
niche | string | Same as /api/v1/ads. |
category | finance|gambling|tech|content|commerce|life | Coarse super-group. Mutually exclusive with niche. |
geo | string | Same as /api/v1/ads. |
limit | int 1–500 | Default 500. |
curl -O "https://tgadsspy.com/api/v1/ads.csv?niche=crypto&limit=100"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/ads.csv?niche=crypto&limit=100')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/ads.csv?niche=crypto&limit=100')
with open('ads.csv', 'wb') as f:
f.write(r.content)id,title,text,paymentSource,category,niche,geo,lang,ctaButtonText,ctaUrl,mediaUrl,advertiser,advertiserSlug,placementCount,impressionCount,reachEstimate,firstSeenAt,lastSeenAt,minDisplaySec,maxDisplaySec
c...,P's Trading Journal,...,EUR_ADS_CABINET,finance,crypto,,,VIEW BOT,https://t.me/...,https://cdn4...jpg,,,0,1,0,2026-04-18T...,2026-04-18T...,5,30/api/v1/cashier-creatives— List cashier creatives (Stars / giveaway)Pre-filtered feed of cashier creatives — Telegram Stars инвойсы (XTR + classic currencies) и Premium giveaway / giveaway_results. Symmetric pair to /api/v1/miniapps (Channel-side cashier surface). Default sort newest first; meta.byType returns invoice/giveaway counts under the active filter.
| Param | Type | Description |
|---|---|---|
type | invoice|giveaway | Discriminator. Omit for both. invoice = OR(invoiceCurrency NOT NULL, mediaType=invoice). giveaway = OR(giveawayPrize NOT NULL, mediaType IN giveaway/giveaway_results). |
currency | string | 3–4 uppercase letters. XTR = Telegram Stars, иначе ISO 4217 (USD, EUR, RUB, …). Validates server-side. |
advertiser | string | Filter by advertiser slug (see /api/v1/advertisers). Returns empty array for unknown slug. |
niche | string | Niche slug (crypto, gambling, finance, games, trading, …). |
geo | string | Advertiser-intent geo (ISO-3166 alpha-2). |
serv | string | ARCHIVE — SponsoredImpression.region. Not where the ad was seen: it stores the region we requested from our pool of real Telegram accounts, and the pool silently substitutes an account from another country when it has none in the one requested. Per-region requests ended 2026-06-03, so this matches historical impressions only. For advertiser targeting use `geo`. |
lang | string | Creative language (en, ru, …). |
sort | new|reach | new = firstSeenAt DESC (default). reach = reachEstimate DESC. |
days | int 1–365 | Recent window in days. ANON clamped to ≤7. |
since | ISO timestamp | Cursor for live polling. ANON clamped to last 24h. |
limit | int 1–500 | Default 50. Max depends on plan: ANON 50, FREE 100, PRO 500. |
offset | int | For pagination. Default 0. |
curl "https://tgadsspy.com/api/v1/cashier-creatives?type=invoice¤cy=XTR&limit=20"const r = await fetch('https://tgadsspy.com/api/v1/cashier-creatives?type=invoice¤cy=XTR&limit=20')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/cashier-creatives?type=invoice¤cy=XTR&limit=20')
print(r.json()){
"data": [
{
"id": "c...",
"title": "Premium Stars Pack",
"text": "Get 1000 stars instantly",
"paymentSource": "TON",
"niche": "crypto",
"geo": null,
"lang": "en",
"isCashierCreative": true,
"invoiceCurrency": "XTR",
"invoiceTotalAmount": "1000",
"invoiceTitle": "Stars Pack",
"giveawayPrize": null,
"giveawayMonths": null,
"ctaButtonText": "BUY",
"ctaUrl": "https://t.me/example_bot?start=stars",
"ctaTargetUsername": "example_bot",
"advertiserSlug": "stars-shop",
"advertiserName": "Stars Shop",
"reachEstimate": "12500",
"firstSeenAt": "2026-05-04T...",
"lastSeenAt": "2026-05-05T..."
}
],
"meta": {
"total": 47,
"byType": { "invoice": 32, "giveaway": 15 },
"limit": 20,
"offset": 0,
"plan": "FREE"
}
}/api/v1/cashier-creatives.csv— Export cashier creatives as CSVSame filters as /api/v1/cashier-creatives. Returns text/csv with 27 columns (extends ads.csv с invoice* + giveaway* + ctaTargetUsername полями), up to 500 rows. Attachment download. Requires X-Api-Key (FREE+ plan, anon blocked).
| Param | Type | Description |
|---|---|---|
type | invoice|giveaway | Same as /api/v1/cashier-creatives. |
currency | string | XTR or ISO 4217. Same as /api/v1/cashier-creatives. |
advertiser | string | Filter by advertiser slug. |
niche | string | Niche slug. |
geo | string | Advertiser-intent geo. |
serv | string | ARCHIVE — SponsoredImpression.region. Not where the ad was seen: it stores the region we requested from our pool of real Telegram accounts, and the pool silently substitutes an account from another country when it has none in the one requested. Per-region requests ended 2026-06-03, so this matches historical impressions only. For advertiser targeting use `geo`. |
lang | string | Creative language. |
limit | int 1–500 | Default 500. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/cashier-creatives.csv?type=invoice¤cy=XTR&limit=100"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/cashier-creatives.csv?type=invoice¤cy=XTR&limit=100')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/cashier-creatives.csv?type=invoice¤cy=XTR&limit=100')
with open('ads.csv', 'wb') as f:
f.write(r.content)id,title,text,paymentSource,niche,geo,lang,isCashierCreative,invoiceCurrency,invoiceTotalAmount,invoiceTitle,giveawayPrize,giveawayMonths,giveawayWinnersCount,giveawayUntilDate,giveawayCountriesIso2,ctaButtonText,ctaUrl,ctaTargetUsername,mediaUrl,advertiser,advertiserSlug,placementCount,impressionCount,reachEstimate,firstSeenAt,lastSeenAt
c...,Premium Stars Pack,Get 1000 stars,TON,crypto,,en,true,XTR,1000,Stars Pack,,,,,,BUY,https://t.me/example_bot,example_bot,,,,0,1,12500,2026-05-04T...,2026-05-05T.../api/v1/channels— List channelsTelegram channels we track, sorted by member count desc.
| Param | Type | Description |
|---|---|---|
q | string | Case-insensitive substring match on username or title. |
lang | string | Channel language filter. |
category | string | Category filter (news, crypto, …). |
minMembers | int | Only channels with members ≥ this value. |
sponsored | eligible | Only channels eligible for Telegram sponsored messages. |
limit | int 1–500 | Default 50. Max depends on plan: ANON 50, FREE 100, PRO 500. |
offset | int | Default 0. |
curl "https://tgadsspy.com/api/v1/channels?sponsored=eligible&minMembers=100000"const r = await fetch('https://tgadsspy.com/api/v1/channels?sponsored=eligible&minMembers=100000')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/channels?sponsored=eligible&minMembers=100000')
print(r.json()){
"data": [
{
"id": "...",
"username": "durov",
"title": "Pavel Durov",
"lang": "en",
"category": "personal",
"members": 10248881,
"avgViews": 819910,
"estimatedCpm": null,
"isVerified": true,
"isSponsoredEligible": true,
"lastSnapshotAt": "2026-04-19T..."
}
],
"meta": { "total": 108, "limit": 50, "offset": 0 }
}/api/v1/advertisers— List advertisersAdvertisers we identified from CTA URLs (domains + Telegram handles). Default sort: creative count desc.
| Param | Type | Description |
|---|---|---|
q | string | Case-insensitive match on name, slug, or domain. |
domain | string | Partial domain match (case-insensitive). |
tgUsername | string | Exact Telegram username match (case-insensitive). Use without @ prefix. |
sort | popular|new|recent | Sort: popular = most creatives (default), new = firstSeenAt desc, recent = lastSeenAt desc. |
limit | int 1–500 | Default 50. Max depends on plan: ANON 50, FREE 100, PRO 500. |
offset | int | Default 0. |
curl "https://tgadsspy.com/api/v1/advertisers?q=crypto&limit=20"const r = await fetch('https://tgadsspy.com/api/v1/advertisers?q=crypto&limit=20')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/advertisers?q=crypto&limit=20')
print(r.json()){
"data": [
{
"id": "...",
"slug": "tg-mrp_tradingbot",
"name": "@MrP_Tradingbot",
"domain": null,
"tgUsername": "mrp_tradingbot",
"creativeCount": 1,
"firstSeenAt": "2026-04-18T...",
"lastSeenAt": "2026-04-18T..."
}
],
"meta": { "total": 10, "limit": 20, "offset": 0 }
}/api/v1/advertisers.csv— Advertiser index — CSV mirror9-column CSV mirror of /api/v1/advertisers for competitive intelligence workflows. Sortable by popular/new/recent. Filterable by q/domain/niche. Requires X-Api-Key — ANON blocked (matches /api/v1/cashier-creatives.csv pattern). OWASP escape. Plan-tier limit cap: FREE 100, PRO 500.
| Param | Type | Description |
|---|---|---|
q | string | Case-insensitive search (name/slug/domain). |
domain | string | Partial domain filter. |
niche | string | Exact primary niche slug. |
sort | popular|new|recent | Default popular. |
limit | int 1–500 | Default 500. Plan caps: FREE 100, PRO 500. |
curl -H "X-Api-Key: $KEY" "https://tgadsspy.com/api/v1/advertisers.csv?niche=crypto&sort=popular" > advertisers-crypto.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/advertisers.csv?niche=crypto&sort=popular')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/advertisers.csv?niche=crypto&sort=popular')
with open('ads.csv', 'wb') as f:
f.write(r.content)slug,name,domain,tg_username,primary_niche,creative_count,first_seen_at,last_seen_at,profile_url
ftmo,FTMO,ftmo.com,ftmo,prop-firm,1240,2026-01-15T...,2026-05-06T...,https://tgadsspy.com/advertisers/ftmo
binance,Binance,binance.com,binance,crypto,890,2026-02-10T...,2026-05-06T...,https://tgadsspy.com/advertisers/binance
.../api/v1/advertisers/:slug— Get advertiser by slugLook up a single advertiser by their unique slug. Returns 404 for unknown slugs. Includes: primary niche, honeypot flag (anti-scrape Phase B decoy detection), discovery aggregates (channels promoting CTAs, t.me-post mention count, discovery sources), и domains[] array (all known domain mappings — main + .ru + .com + landing-pages).
| Param | Type | Description |
|---|
curl "https://tgadsspy.com/api/v1/advertisers/tg-mrp_tradingbot"const r = await fetch('https://tgadsspy.com/api/v1/advertisers/tg-mrp_tradingbot')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/advertisers/tg-mrp_tradingbot')
print(r.json()){
"data": {
"id": "...",
"slug": "tg-mrp_tradingbot",
"name": "@MrP_Tradingbot",
"domain": null,
"tgUsername": "mrp_tradingbot",
"creativeCount": 1,
"firstSeenAt": "2026-04-18T...",
"lastSeenAt": "2026-04-18T...",
"primaryNiche": "trading",
"isHoneypot": false,
"channelsPromotedIn": 12,
"tmePostsCount": 47,
"discoverySources": ["tme-scraper", "gramesh-cta"],
"domains": [
{
"domain": "mrp-trading.com",
"rootDomain": "mrp-trading.com",
"firstSeenAt": "2026-04-18T...",
"lastSeenAt": "2026-05-17T...",
"postsCount": 38,
"channelsCount": 9,
"source": "tme-scraper"
}
]
}
}/api/v1/channels/:username/aliases— Fragment NFT-username aliases для каналаTelegram через Fragment marketplace (TON) разрешает на одном entity несколько usernames (multinames). Owner может attach/detach/sell aliases. Этот endpoint возвращает все известные variants — включая detached (с detachedAt set) для historical record. Resolves username через canonical Channel ИЛИ alias mapping. Use cases: brand monitoring, anti-squatting detection, Fragment marketplace research.
| Param | Type | Description |
|---|
curl "https://tgadsspy.com/api/v1/channels/durov/aliases"const r = await fetch('https://tgadsspy.com/api/v1/channels/durov/aliases')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/channels/durov/aliases')
print(r.json()){
"data": [
{
"alias": "durov",
"source": "user-submit",
"firstSeenAt": "2026-01-15T...",
"lastSeenAt": "2026-05-17T...",
"detachedAt": null
},
{
"alias": "old_durov",
"source": "gramesh-multiname",
"firstSeenAt": "2025-08-01T...",
"lastSeenAt": "2026-01-15T...",
"detachedAt": "2026-01-15T..."
}
],
"meta": { "canonical": "durov", "total": 2, "plan": "FREE" }
}/api/v1/niches— List niches with countsAll known niches (crypto, trading, vpn, gambling, tech, news, retail, finance, gaming, education, bots) with current counts and SEO-friendly metadata. Useful for building navigation or a niche directory.
curl https://tgadsspy.com/api/v1/nichesconst r = await fetch('https://tgadsspy.com/api/v1/niches')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/niches')
print(r.json()){
"data": [
{
"slug": "crypto",
"title": "Crypto ads on Telegram",
"headline": "Crypto, Web3 and token ads",
"description": "Every Bitcoin, Ethereum, TON...",
"count": 12,
"indexable": true,
"url": "https://tgadsspy.com/niches/crypto",
"feed": "https://tgadsspy.com/niches/crypto/rss.xml"
}
],
"meta": { "total": 11, "locale": "en" }
}/api/v1/categories— List categories (coarse taxonomy) with niches insideCoarse-grained taxonomy: 6 super-groups (Finance, Gambling, Tech, Content, Commerce, Lifestyle). Each item includes per-group total + niches[] with counts. Companion to /api/v1/niches at one level up. Use ?category=<group> as a filter on /api/v1/ads or /api/v1/cashier-creatives to expand to all niches inside.
curl https://tgadsspy.com/api/v1/categoriesconst r = await fetch('https://tgadsspy.com/api/v1/categories')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/categories')
print(r.json()){
"data": [
{
"slug": "finance",
"label": "Finance & Crypto",
"total": 1240,
"nicheCount": 4,
"niches": [
{ "slug": "crypto", "count": 850 },
{ "slug": "trading", "count": 220 },
{ "slug": "finance", "count": 130 },
{ "slug": "prop-firm", "count": 40 }
],
"url": "https://tgadsspy.com/categories/finance"
}
],
"meta": { "total": 6, "locale": "en" }
}/api/v1/categories.csv— Categories taxonomy as flat CSVFlat denormalized CSV — one row per (category, niche) pair with niche_count + category_total. Suitable for Excel pivots and pandas import without JSON nesting.
curl https://tgadsspy.com/api/v1/categories.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/categories.csv')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/categories.csv')
with open('ads.csv', 'wb') as f:
f.write(r.content)category_slug,category_label,niche_slug,niche_count,category_total
finance,Finance & Crypto,crypto,850,1240
finance,Finance & Crypto,trading,220,1240
finance,Finance & Crypto,finance,130,1240
finance,Finance & Crypto,prop-firm,40,1240
.../api/v1/niches.csv— Niche distribution — CSV mirror8-column CSV mirror of /api/v1/niches for spreadsheet/research workflows. Adds advertiser_count column для diversity analysis (количество distinct advertisers per niche). Researcher-friendly for ad-market vertical evolution studies. Public, no auth, 60s cache.
curl "https://tgadsspy.com/api/v1/niches.csv" > niches.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/niches.csv')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/niches.csv')
with open('ads.csv', 'wb') as f:
f.write(r.content)slug,title,headline,count,advertiser_count,indexable,url,feed_url
crypto,Crypto ads on Telegram,"Crypto, Web3 and token ads",12450,1248,true,https://tgadsspy.com/niches/crypto,https://tgadsspy.com/niches/crypto/rss.xml
trading,Trading signals,"Trading signal services",8900,890,true,https://tgadsspy.com/niches/trading,https://tgadsspy.com/niches/trading/rss.xml
.../api/v1/geos— List target countries with countsCountries the creatives TARGET, classified from CTA URL TLD, script and language hints. Sorted by confidentCount descending. This endpoint carries no measurement of where an ad was seen. The old `count` field (audience-delivery) was removed on 2026-07-28 — it was derived from SponsoredImpression.region, which stored the region we requested from our pool of real Telegram accounts rather than any place of delivery, and has not been written since 2026-06-03; meta.retired describes what it used to mean.
curl https://tgadsspy.com/api/v1/geosconst r = await fetch('https://tgadsspy.com/api/v1/geos')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/geos')
print(r.json()){
"data": [
{ "code": "RU", "intentCount": 12450, "confidentCount": 8900 },
{ "code": "IT", "intentCount": 1240, "confidentCount": 810 }
],
"meta": {
"total": 2,
"metric": "advertiser-intent",
"retired": { "field": "count", "reason": "SponsoredImpression.region is no longer written — per-region requests ended 2026-06-03. The column never recorded where an ad was seen ..." }
}
}/api/v1/geos.csv— Geo distribution — CSV mirror5-column CSV mirror of /api/v1/geos (code, name, confident_creatives, geo_tagged_creatives, profile_url). Both columns are advertiser intent, at two confidence levels: confident_creatives = target country confirmed by the creative itself (landing ccTLD, script, flag, unambiguous language); geo_tagged_creatives = raw AdCreative.geo group-by, including creatives whose only country signal was the region we requested from our account pool. The former audience-delivery column `count` was removed on 2026-07-28 together with the metric — it read SponsoredImpression.region, which held the region we requested rather than a place of delivery and has been unwritten since 2026-06-03. Headers were renamed rather than reused, so a stale parser breaks loudly instead of silently reading a different number. Public, no auth, 300s cache.
curl "https://tgadsspy.com/api/v1/geos.csv" > geos.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/geos.csv')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/geos.csv')
with open('ads.csv', 'wb') as f:
f.write(r.content)code,name,confident_creatives,geo_tagged_creatives,profile_url
RU,Russia,8900,12450,https://tgadsspy.com/countries/ru
TR,Türkiye,3100,5200,https://tgadsspy.com/countries/tr
IN,India,2400,4100,https://tgadsspy.com/countries/in
.../api/v1/comparisons— List vs-pages (pairs, trios, niche-pairs, geo-pairs)Programmatic access to all Wave-78/79 comparison pages. type=advertiser-pair returns 2-way brand comparisons (e.g. ftmo-vs-myfundedfx); type=advertiser-trio returns 3-way trifecta (top-3 per niche); type=niche-pair returns niche-vs-niche taxonomy pages (e.g. crypto-vs-gambling); type=geo-pair returns country-vs-country market pages (e.g. us-vs-de). Optional filters: ?niche=crypto (restrict by niche), ?advertiser=ftmo (find comparisons containing this advertiser), ?locale=ru (translate niche labels). Backed by 10-min Redis cache (same data as sitemap.xml).
| Param | Type | Description |
|---|---|---|
type | advertiser-pair|advertiser-trio|niche-pair|geo-pair | Comparison kind. Default advertiser-pair. |
limit | int 1–100 | 1-100 entries. Default 50. |
niche | string (niche slug) | For advertiser-pair/trio: filter by primary niche. For niche-pair: pairs containing this niche. |
advertiser | string (advertiser slug) | For advertiser-pair/trio: returns only entries containing this advertiser. |
locale | en|ru|uk|it|es|pt|de|fr|ar|id | Locale for label rendering. Default en. |
curl "https://tgadsspy.com/api/v1/comparisons?type=advertiser-pair&niche=prop-firm&advertiser=ftmo&limit=5"const r = await fetch('https://tgadsspy.com/api/v1/comparisons?type=advertiser-pair&niche=prop-firm&advertiser=ftmo&limit=5')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/comparisons?type=advertiser-pair&niche=prop-firm&advertiser=ftmo&limit=5')
print(r.json()){
"data": [
{
"type": "advertiser-pair",
"canonical": "ftmo-vs-myfundedfx",
"members": [
{ "slug": "ftmo", "name": "FTMO" },
{ "slug": "myfundedfx", "name": "MyFundedFX" }
],
"niche": "prop-firm",
"url": "https://tgadsspy.com/advertisers/vs/ftmo-vs-myfundedfx"
}
],
"meta": {
"type": "advertiser-pair",
"total": 5,
"limit": 5,
"locale": "en",
"filters": { "niche": "prop-firm", "advertiser": "ftmo" },
"plan": "FREE"
}
}/api/v1/comparisons/stats— Vs-vertical meta-stats (totals + top entities)Single endpoint для dashboard headline tiles. Returns total comparison count per kind, top-10 advertisers by appearance в pairs/trios, top-10 niches by combined appearance, top-10 countries by geo-pair appearance. Cheap (reads cached pools — 1-2 batch Advertiser query). 5-min cache.
curl https://tgadsspy.com/api/v1/comparisons/statsconst r = await fetch('https://tgadsspy.com/api/v1/comparisons/stats')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/comparisons/stats')
print(r.json()){
"data": {
"totals": {
"advertiser-pair": 100,
"advertiser-trio": 28,
"niche-pair": 47,
"geo-pair": 35,
"all": 210
},
"topAdvertisers": [
{ "slug": "ftmo", "name": "FTMO", "pairCount": 8, "trioCount": 3, "total": 11 },
...
],
"topNiches": [
{ "slug": "prop-firm", "label": "Prop firm", "nichePairCount": 4, "advertiserContextCount": 24, "total": 28 },
...
],
"topCountries": [
{ "iso": "US", "name": "United States", "pairCount": 14 },
...
],
"generatedAt": "2026-05-18T..."
},
"meta": { "plan": "FREE" }
}/api/v1/comparisons/:type/:canonical— Vs-page deep metrics (full payload)Companion к list endpoint — returns same data структура что vs-page renders (advertiser/niche/country metadata + creative counts + 90d advertiser counts + overlap arrays). 404 если pair fails filter rules (same gate as page). 5-min cache.
| Param | Type | Description |
|---|---|---|
type | advertiser-pair|advertiser-trio|niche-pair|geo-pair | Path segment, required. |
canonical | string | Lex-sorted slug. Non-canonical orderings return 404 (no implicit redirect on API). |
curl https://tgadsspy.com/api/v1/comparisons/advertiser-pair/ftmo-vs-myfundedfxconst r = await fetch('https://tgadsspy.com/api/v1/comparisons/advertiser-pair/ftmo-vs-myfundedfx')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/comparisons/advertiser-pair/ftmo-vs-myfundedfx')
print(r.json()){
"data": {
"type": "advertiser-pair",
"canonical": "ftmo-vs-myfundedfx",
"url": "https://tgadsspy.com/advertisers/vs/ftmo-vs-myfundedfx",
"advertisers": [
{
"slug": "ftmo",
"name": "FTMO",
"domain": "ftmo.com",
"creativeCount": 248,
"firstSeenAt": "2024-09-15T...",
"lastSeenAt": "2026-05-17T...",
"topNiche": "prop-firm"
},
{ "slug": "myfundedfx", "name": "MyFundedFX", ... }
],
"niche": "prop-firm"
},
"meta": { "type": "advertiser-pair", "canonical": "ftmo-vs-myfundedfx", "plan": "FREE" }
}/api/v1/comparisons.csv— Vs-pages — CSV mirror (researcher-friendly)10-column CSV mirror of /api/v1/comparisons. Default type=all combines all 4 kinds в один dataset (researcher cross-cut analysis). Same ?niche= и ?advertiser= filters как JSON sister (filter applied post-fetch с pool widening). Empty member_3_* для pair/niche-pair/geo-pair (только trio has 3 members). OWASP formula-injection escaped. Public, no auth, 60s cache.
| Param | Type | Description |
|---|---|---|
type | advertiser-pair|advertiser-trio|niche-pair|geo-pair|all | Default all (combines all 4 kinds). |
limit | int 1–100 | Per-kind cap. Default 50. |
niche | string | For advertiser-*: Advertiser.primaryNiche filter. For niche-pair: pairs containing this slug. |
advertiser | string | For advertiser-pair/trio: returns rows containing this slug. |
curl "https://tgadsspy.com/api/v1/comparisons.csv?type=all&limit=50" > comparisons.csvimport { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/comparisons.csv?type=all&limit=50')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/comparisons.csv?type=all&limit=50')
with open('ads.csv', 'wb') as f:
f.write(r.content)type,canonical,member_1_slug,member_1_name,member_2_slug,member_2_name,member_3_slug,member_3_name,niche,url
advertiser-pair,ftmo-vs-myfundedfx,ftmo,FTMO,myfundedfx,MyFundedFX,,,prop-firm,https://tgadsspy.com/advertisers/vs/ftmo-vs-myfundedfx
advertiser-trio,ftmo-vs-fundednext-vs-myfundedfx,ftmo,FTMO,fundednext,FundedNext,myfundedfx,MyFundedFX,prop-firm,https://tgadsspy.com/advertisers/vs/ftmo-vs-fundednext-vs-myfundedfx
niche-pair,crypto-vs-gambling,crypto,Crypto,gambling,Gambling,,,,https://tgadsspy.com/niches/vs/crypto-vs-gambling
geo-pair,de-vs-us,de,Germany,us,United States,,,,https://tgadsspy.com/countries/vs/de-vs-us
.../api/v1/trends— Time series — creatives per dayNew creatives counted per UTC day, pivoted on a dimension. Useful for charts of niche growth, category mix, payment-source split or geo breakdown over time. dim=category aggregates niches into 6 super-groups (Finance/Gambling/Tech/Content/Commerce/Lifestyle).
| Param | Type | Description |
|---|---|---|
dim | niche|category|source|geo | What to group by. Default niche. category aggregates niches into 6 super-groups. |
days | int 1–90 | Lookback window. Default 30. |
curl "https://tgadsspy.com/api/v1/trends?dim=niche&days=30"const r = await fetch('https://tgadsspy.com/api/v1/trends?dim=niche&days=30')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/trends?dim=niche&days=30')
print(r.json()){
"data": [
{ "date": "2026-04-19", "series": { "crypto": 8, "trading": 4, "vpn": 2 } },
{ "date": "2026-04-20", "series": { "crypto": 10, "trading": 3 } }
],
"meta": {
"dim": "niche",
"days": 30,
"since": "2026-03-20T00:00:00Z",
"keys": ["crypto","trading","vpn"],
"totals": { "crypto": 18, "trading": 7, "vpn": 2 }
}
}/api/v1/analytics— Analytics catalog (machine-readable)Index endpoint listing all analytics surfaces with path/method/summary/params/csvMirror/plans/status. Useful for SDK / dashboard builders that auto-discover capabilities. No auth required.
| Param | Type | Description |
|---|
curl "https://tgadsspy.com/api/v1/analytics" | jq '.endpoints[] | select(.status == "live") | .path'const r = await fetch('https://tgadsspy.com/api/v1/analytics')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics')
print(r.json()){
"endpoints": [
{
"path": "/api/v1/analytics/share-of-voice",
"method": "GET",
"summary": "...",
"params": ["niche", "geo", "top", "from", "to"],
"csvMirror": "/api/v1/analytics/share-of-voice.csv",
"plans": ["FREE", "PRO", "ENTERPRISE"],
"status": "live"
},
...
],
"meta": {
"version": "phase-2",
"totalEndpoints": 15,
"liveEndpoints": 12,
"stubEndpoints": 3,
"csvMirrors": 12,
"specUrl": "/api/v1/openapi.json",
"docsUrl": "/api-docs"
}
}/api/v1/feeds— Feeds catalog (single discovery anchor)Machine-readable catalog of all 22+ data feeds and datasets. Each entry has structured metadata: format (json/csv/xml/feed+json/rss+xml/opensearch/text), plan (ANON/FREE/PRO/ENTERPRISE), cache_ttl_sec, auth_required, license, tags array, schema_url. SDK builders use this as single discovery anchor вместо scraping data_feeds blocks across /api/product and /api/v1/ecosystem. Filterable.
| Param | Type | Description |
|---|---|---|
format | string | json | csv | xml | feed+json | rss+xml | opensearch | text |
plan | string | ANON | FREE | PRO | ENTERPRISE |
tag | string | cashier, miniapps, glossary, ecosystem, researcher, sdk, ai-crawler, … |
curl "https://tgadsspy.com/api/v1/feeds?format=csv&plan=FREE" | jq '.data[].url'const r = await fetch('https://tgadsspy.com/api/v1/feeds?format=csv&plan=FREE')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/feeds?format=csv&plan=FREE')
print(r.json()){
"data": [
{
"id": "api-v1-cashier-creatives-csv",
"name": "Cashier Creatives — CSV",
"url": "https://tgadsspy.com/api/v1/cashier-creatives.csv",
"format": "csv",
"description": "27-column CSV export...",
"plan": "FREE",
"cache_ttl_sec": 60,
"auth_required": true,
"license": "CC-BY-4.0",
"tags": ["cashier", "creatives", "csv", "researcher"]
}
],
"meta": {
"total": 8,
"total_unfiltered": 22,
"by_format": { "csv": 4, "json": 14, "..." },
"by_plan": { "ANON": 17, "FREE": 5 },
"by_tag": { "cashier": 4, "researcher": 4, "..." },
"filters_applied": { "format": "csv", "plan": "FREE", "tag": null }
}
}/api/v1/analytics/* (Phase 2)— Analytics endpoints — Phase 2 (10 endpoints, 7 with real compute)Pro+ analytics surface. Phase 1 endpoints (real compute): /share-of-voice (SoV by advertiser in niche), /creative-fatigue (peak detection + fatigueIndex per creative), /frequency-distribution (imps/unique-channel histogram + p50/p90), /competitor-overlap (Jaccard half-matrix for 2-5 advertisers across channels/niches/creatives), /spend-estimate (groupBy advertiser/niche/geo with confidence band). Phase 2 endpoints (real compute): /geo-snapshot (top advertisers/niches per geo + growthVsPrior), /niche-leaderboard (rank by impressions/newCreatives/sov/spend with delta), /advertiser-timeline (per-period creatives+impressions, day/week/month granularity), /niche-pricing (proxy CPM percentiles 25/50/75), /advertiser-creatives (cursor pagination; the `region` filter and the top-regions field were retired 2026-07-28 — both read SponsoredImpression.region, unwritten since 2026-06-03), /channel-ad-load (channel-owner perspective: total/uniqueAdvertisers/daily/estCpmRange), /cta-domains (top CTA hosts per advertiser/niche with isAffiliate/isShortener heuristics). Stubs (require async-job infra): /exports POST + /exports/<jobId> GET (sync CSV mirrors below cover typical use), /diff-webhook POST (subscribe to advertiser change events). Plan-gating: ANON 401, FREE narrow window/top, PRO/ENTERPRISE full access.
| Param | Type | Description |
|---|---|---|
(varies per endpoint) | see spec | Each endpoint has its own zod schema. Common parameters: from/to (ISO-8601 date, default last 30d / 90d), niche, geo (alpha-2 ISO), advertiser (slug). See outputs/specs/analytics-phase-2-spec.md in repo. |
curl -H "X-Api-Key: $KEY" "https://tgadsspy.com/api/v1/analytics/share-of-voice?niche=crypto&top=5"const r = await fetch('https://tgadsspy.com/api/v1/analytics/share-of-voice?niche=crypto&top=5')
const data = await r.json()
export const dynamic = 'force-dynamic'
console.log(data)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/share-of-voice?niche=crypto&top=5')
print(r.json()){
"data": { "totalImpressions": 1240000, "top": [...], "otherSov": 0.18 },
"meta": { "plan": "PRO" }
}/api/v1/analytics/advertiser-creatives.csv— Export advertiser creatives as CSV (Pro+)Same filters as JSON sibling but flat (no cursor) — up to 500 rows in one shot. 21 columns including ctaHost (for affiliate/shortener analysis), reachEstimate, spendEstimate, daysAlive, permalink. Attachment download. Pro+ only (FREE blocked).
| Param | Type | Description |
|---|---|---|
advertiser | string | Required. Advertiser slug. |
region | (retired) | RETIRED 2026-07-28 — returns 400. Filtered on SponsoredImpression.region, which stored the region we requested from our account pool (never where an ad was shown) and has been unwritten since 2026-06-03; the endpoint rejects the parameter instead of silently returning an unfiltered list. This endpoint has no country filter; advertiser-target filtering lives on /niche-leaderboard and /geo-snapshot. |
niche | string | Filter by creative niche. |
from | ISO-8601 | Default 90 days ago. |
to | ISO-8601 | Default now. |
limit | int 1–500 | Default 500. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/advertiser-creatives.csv?advertiser=tg-mrp_tradingbot&limit=200"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/advertiser-creatives.csv?advertiser=tg-mrp_tradingbot&limit=200')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/advertiser-creatives.csv?advertiser=tg-mrp_tradingbot&limit=200')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary; see Content-Disposition header for filename)/api/v1/analytics/niche-leaderboard.csv— Export niche leaderboard as CSVTop advertisers in a niche with rank, metric, delta vs prior window, newCreatives. 8 columns. FREE: only impressions/newCreatives ranking; spend/sov ranking is Pro+.
| Param | Type | Description |
|---|---|---|
niche | string | Required. Niche slug. |
geo | ISO-3166-1 alpha-2 | Optional geo filter. |
rankBy | impressions|sov|newCreatives|spend | Default impressions. spend/sov require Pro+. |
top | int 1–50 | Default 20. FREE caps at 20. |
from | ISO-8601 | Default 30 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/niche-leaderboard.csv?niche=crypto&rankBy=impressions&top=20"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/niche-leaderboard.csv?niche=crypto&rankBy=impressions&top=20')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/niche-leaderboard.csv?niche=crypto&rankBy=impressions&top=20')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/spend-estimate.csv— Export spend estimates as CSVGroupBy advertiser/niche/geo with USD ranges and confidence band. 7 columns. FREE caps window 7d / limit 10. Estimate based on observed impressions × niche-level CPM ±25% confidence.
| Param | Type | Description |
|---|---|---|
groupBy | advertiser|niche|geo | Required. Aggregation key. |
from | ISO-8601 | Required. Window start. |
to | ISO-8601 | Required. Window end. Hard cap 365d. |
niche | string | Filter to a single niche. |
geo | ISO-3166-1 alpha-2 | Filter to a single geo. |
limit | int 1–200 | Default 50. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/spend-estimate.csv?groupBy=advertiser&niche=crypto&from=2026-04-01&to=2026-04-30"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/spend-estimate.csv?groupBy=advertiser&niche=crypto&from=2026-04-01&to=2026-04-30')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/spend-estimate.csv?groupBy=advertiser&niche=crypto&from=2026-04-01&to=2026-04-30')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/geo-snapshot.csv— Export geo snapshot as CSVLong format: top advertisers + top niches in one CSV. 5 columns (kind=advertiser|niche, rank, slug, name, impressions). Filter by 30-day default window.
| Param | Type | Description |
|---|---|---|
geo | ISO-3166-1 alpha-2 | Required. Geo code (e.g. US, BR, KZ). |
from | ISO-8601 | Default 30 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/geo-snapshot.csv?geo=KZ"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/geo-snapshot.csv?geo=KZ')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/geo-snapshot.csv?geo=KZ')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary; first half rows kind=advertiser, second half kind=niche)/api/v1/analytics/advertiser-timeline.csv— Export advertiser timeline as CSVPer-period creatives + impressions for one advertiser. 3 columns (period, creatives, impressions). Granularity day | week | month — period format ISO-date / YYYY-WW / YYYY-MM accordingly.
| Param | Type | Description |
|---|---|---|
advertiser | string | Required. Advertiser slug. |
granularity | day|week|month | Default day. |
from | ISO-8601 | Default 90 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/advertiser-timeline.csv?advertiser=tg-mrp_tradingbot&granularity=week"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/advertiser-timeline.csv?advertiser=tg-mrp_tradingbot&granularity=week')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/advertiser-timeline.csv?advertiser=tg-mrp_tradingbot&granularity=week')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/niche-pricing.csv— Export niche pricing percentiles as CSVProxy CPM percentiles 25/50/75 per niche × geo, with observed-creatives count and confidence band. 7 columns. Method = observed impressions ÷ channel-members × 1000.
| Param | Type | Description |
|---|---|---|
niche | string | Optional niche filter (omit for all). |
geo | ISO-3166-1 alpha-2 | Optional geo filter. |
top | int 1–50 | Default 20. |
from | ISO-8601 | Default 7 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/niche-pricing.csv?niche=crypto"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/niche-pricing.csv?niche=crypto')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/niche-pricing.csv?niche=crypto')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/channel-ad-load.csv— Export channel ad-load as CSVLong format: top advertisers (kind=advertiser) + daily series (kind=daily) for one channel. 4 columns (kind, key, name, creatives). Channel-owner perspective: how much ad-load you carry.
| Param | Type | Description |
|---|---|---|
channelUsername | string | Required. Channel username (no @). |
from | ISO-8601 | Default 30 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/channel-ad-load.csv?channelUsername=durov"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/channel-ad-load.csv?channelUsername=durov')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/channel-ad-load.csv?channelUsername=durov')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/cta-domains.csv— Export CTA-domain distribution as CSVTop CTA hosts per advertiser or niche, with isAffiliate / isShortener heuristic flags. 6 columns. Either advertiser OR niche is required.
| Param | Type | Description |
|---|---|---|
advertiser | string | Advertiser slug (either this or niche required). |
niche | string | Niche slug (either this or advertiser required). |
top | int 1–50 | Default 20. |
from | ISO-8601 | Default 30 days ago. |
to | ISO-8601 | Default now. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/cta-domains.csv?advertiser=tg-mrp_tradingbot"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/cta-domains.csv?advertiser=tg-mrp_tradingbot')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/cta-domains.csv?advertiser=tg-mrp_tradingbot')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/creative-fatigue.csv— Export creative-fatigue summary as CSVPer-creative summary (full daily series omitted — use JSON endpoint for that). 8 columns (creativeId, title, firstSeenAt, daysAlive, peakDay, fatigueIndex, status, permalink). Status enum: ramping | peak | fatigued | paused.
| Param | Type | Description |
|---|---|---|
advertiser | string | Filter by advertiser slug. |
niche | string | Filter by niche slug. |
creativeId | string | Single creative ID. |
from | ISO-8601 | Window start. |
to | ISO-8601 | Window end. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/creative-fatigue.csv?advertiser=tg-mrp_tradingbot"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/creative-fatigue.csv?advertiser=tg-mrp_tradingbot')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/creative-fatigue.csv?advertiser=tg-mrp_tradingbot')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)/api/v1/analytics/frequency-distribution.csv— Export frequency-distribution histogram as CSVImps/unique-channel histogram with summary stats. 3 columns (bucketLow, bucketHigh, creatives). Trailing rows: bucketLow=-1 → total, -2 → median, -3 → p90 (sentinel encoding).
| Param | Type | Description |
|---|---|---|
advertiser | string | Filter by advertiser slug. |
niche | string | Filter by niche slug. |
bins | int 5–20 | Histogram bin count. Default 10. |
from | ISO-8601 | Window start. |
to | ISO-8601 | Window end. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/frequency-distribution.csv?niche=crypto&bins=15"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/frequency-distribution.csv?niche=crypto&bins=15')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/frequency-distribution.csv?niche=crypto&bins=15')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary; first line is # comment documenting sentinels)/api/v1/analytics/competitor-overlap.csv— Export competitor-overlap matrix as CSVLong format: advertiser set sizes (kind=set) + Jaccard pair matrix (kind=pair). 8 columns (kind, a, b, jaccard, intersection, union, setSize, name). 2-5 advertisers, metric = channels | niches | creatives.
| Param | Type | Description |
|---|---|---|
advertisers | csv-list 2-5 | Required. Comma-separated advertiser slugs. |
metric | channels|niches|creatives | Default channels. |
from | ISO-8601 | Window start. |
to | ISO-8601 | Window end. |
curl -H "X-Api-Key: $KEY" -O "https://tgadsspy.com/api/v1/analytics/competitor-overlap.csv?advertisers=acme,corp,foo&metric=niches"import { writeFile } from 'node:fs/promises'
const r = await fetch('https://tgadsspy.com/api/v1/analytics/competitor-overlap.csv?advertisers=acme,corp,foo&metric=niches')
const csv = await r.text()
await writeFile('ads.csv', csv)import requests
r = requests.get('https://tgadsspy.com/api/v1/analytics/competitor-overlap.csv?advertisers=acme,corp,foo&metric=niches')
with open('ads.csv', 'wb') as f:
f.write(r.content)(text/csv binary)alert.fired (Business only).GET /api/v1/creatives/:id and related lookup endpoints.Want a feature prioritised? contact us.