# outseo.lol Scraper - Organic Clicks & Rank Monitoring (`memo23/outseo-scraper`) Actor

outseo.lol organic traffic leaderboard as sortable rows: see real Search Console click volume for the indie SaaS sites ranked on it, never a traffic estimate. Get share of board, clicks per day, and change rows naming who climbed since your last run. Opt-in contact emails.

- **URL**: https://apify.com/memo23/outseo-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** SEO tools, Lead generation, AI
- **Stats:** 16 total users, 16 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## outseo.lol Scraper — Organic Traffic Leaderboard, Verified Clicks & Change Monitoring

Scrape the entire [outseo.lol](https://outseo.lol/) organic traffic leaderboard — every site's rank, domain, verified Google Search Console click count, share of board traffic, average clicks per day and self-written description — then track what moves between runs.

![How the outseo.lol Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-outseo.png)

### Why use this scraper

- **Click counts that are measured, not modelled.** Every figure on this board comes from the site owner's own Google Search Console, connected read-only and published on purpose. This is not a third-party traffic estimate — it is the number Google reports to the owner.
- **The whole board in one request.** The leaderboard is a single server-rendered page. Every ranked site arrives in about a second, with no pagination to walk and no detail pages to fetch.
- **Change monitoring built in.** Search Console totals refresh daily and the board re-ranks with them. Turn monitoring on and each run reports what is new, gone, re-ranked or moving on clicks, with the previous value and the delta on every row. Set `onlyChanges` and a scheduled run bills for the handful of rows that moved instead of the whole board every time.
- **Comparisons the board does not print.** `clicksSharePct` places every site against total board traffic, and `avgClicksPerDay` normalises the 28-day figure into a daily rate.
- **Honest about duplicates.** One domain can hold several verified Search Console properties, and the board ranks each separately. `domainOccurrence` tells you when you are looking at a second property rather than a duplicate row.
- **No proxy cost.** The board answers ordinary requests, so the actor runs direct. You are not paying for residential bandwidth you do not need.
- **Opt-in contact emails.** Every entry on this board *is* a website, so email enrichment scrapes the real site rather than guessing at a company match. Billed only when an email is actually found.

### Overview

outseo.lol is a public leaderboard where rank is earned rather than bought: site owners connect Google Search Console read-only, choose which properties to publish, and the board orders them by verified organic clicks over the last 28 days.

It launched on 23 August 2026. At the time of writing it carries 92 benchmarked websites and 500,788 organic clicks across the board, with the leader alone holding just under half of that and a median site sitting at 41 clicks. That distribution is the interesting part: the board is a live, verified picture of how organic traffic actually concentrates among small SaaS products, using numbers that are normally private.

The site publishes no JSON API — `/api/` is disallowed in robots.txt and answers POST-only — so the actor parses the rendered board directly and checks its own parse against the site's printed total.

### Supported inputs

| Input | What it does |
|---|---|
| *(nothing)* | Run with an empty input and get the entire board. |
| `https://outseo.lol/` | The board. Equivalent to an empty input. |

Only outseo.lol URLs are accepted. Anything else fails immediately with a clear message rather than silently scraping the board instead.

### Use cases

- **Competitive SEO research.** See real organic click volume for products in your category, sourced from their own Search Console rather than a traffic estimator.
- **Benchmark your own traffic.** `clicksSharePct` and `medianOrganicClicks` tell you where a given number actually sits on this board.
- **Track a climber.** Monitor mode reports rank moves and click deltas between runs, so you can see which sites are compounding and which have stalled.
- **Catch new entrants.** A `changeType: "new"` row is a site that just connected Search Console and published its numbers.
- **Find products by traffic band.** Pair `minOrganicClicks` and `maxOrganicClicks` to isolate, say, everything between 1,000 and 10,000 clicks a month.
- **Build a lead list.** The board is a roster of founders shipping products with verified organic traffic, each with a live URL and a self-written positioning line. Turn on `enrichEmails` to attach contact details.

### How it works

1. **Fetch the board.** One request to the leaderboard page. It is server-rendered, so every ranked site is in that single response.
2. **Parse and self-check.** Each card is read for rank, domain, destination, description, favicon and click count, then the recovered count is compared against the total the board prints on itself. A shortfall is logged rather than passed off as a complete board.
3. **Separate the placements.** The page mixes the ranked leaderboard with paid sponsor cards and a random "Lucky duck" spotlight. Only ranked organic entries are emitted by default; the sponsor slot adverts are never emitted as entries, and their asking prices are reported on the board-stats row instead.
4. **Derive the comparisons.** Share of total board clicks and average clicks per day are computed from the board's own window.
5. **Diff against the last run.** With monitoring on, a snapshot in a named key-value store is compared to the current board and change rows are emitted.

A dormant Vercel challenge solver is wired in. outseo.lol serves ordinary responses today, but its sibling board switched Vercel's security checkpoint on days after launch — if this one ever does the same, the actor clears it in-run without a proxy. No browser is launched unless a response actually demands it.

### Input configuration

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | `[]` | Optional. outseo.lol URLs only; the board is one page either way. |
| `minRank` / `maxRank` | integer | `0` | Rank range. `0` means unbounded. |
| `minOrganicClicks` / `maxOrganicClicks` | integer | `0` | Click floor and ceiling. Set `minOrganicClicks: 1` to drop sites that have connected but recorded nothing yet. |
| `keywords` / `excludeKeywords` | array | `[]` | Case-insensitive substring match. Excludes are applied after includes, so an excluded term always wins. |
| `keywordFields` | array | `[]` | Restrict matching to `domain`, `url` or `description`. Empty matches across all three. |
| `domains` | array | `[]` | Domain filter. Subdomains included; a bare TLD such as `.ai` matches every entry on it. |
| `includeSponsored` | boolean | `false` | Emit the board's paid sponsor cards. They carry no rank and no click count. |
| `includeSpotlight` | boolean | `false` | Emit the random "Lucky duck" card. It is drawn from the board itself, so this usually means one duplicated site. |
| `sortBy` | enum | `rank` | `rank`, `clicksDesc`, `clicksAsc`, `domainAsc`. Ranks are never renumbered. |
| `includeBoardStats` | boolean | `true` | Append one unbilled `boardStats` row. |
| `monitorChanges` | boolean | `false` | Diff against the previous run and emit `change` rows. |
| `onlyChanges` | boolean | `false` | Emit only the change rows. Requires `monitorChanges`. |
| `minClickJump` | integer | `1` | A click move smaller than this is not reported as a change. |
| `monitorStoreName` | string | `outseo-monitor` | Named key-value store holding the snapshot. |
| `enrichEmails` | boolean | `false` | Opt-in contact-email enrichment. Billed only when an email is found. |
| `maxItems` | integer | `100000` | Hard cap on rows pushed. Free-tier accounts are capped at 100. |
| `proxy` | object | direct | Off by default. Not needed for this board. |

#### Monitoring: give each schedule its own store

Snapshots must live in a **named** key-value store. A run's default store is created fresh each run, so a snapshot written there would never survive to the next run and monitoring would report "baseline established" forever. The default store name is `outseo-monitor`; give each scheduled monitor its own name so several can track different filters independently.

### Output overview

Three row types share one flat schema, distinguished by `rowType`:

- **`listing`** — one row per board entry. Billed.
- **`change`** — one row per entry that moved since the last run, carrying the previous value and the delta. Billed.
- **`boardStats`** — one row of board-level context per run. Never billed.

### Output samples

**A `listing` row:**

```json
{
  "rowType": "listing",
  "rank": 1,
  "domain": "adaptlypost.com",
  "sourceUrl": "https://adaptlypost.com/",
  "outboundUrl": "https://adaptlypost.com/?ref=outseo.lol",
  "description": "Save hours every week with a social media scheduler that automates posts to Instagram, TikTok, YouTube, X, Facebook, LinkedIn, and more.",
  "faviconUrl": "https://www.google.com/s2/favicons?domain=adaptlypost.com&sz=128",
  "organicClicks": 249744,
  "clicksSharePct": 49.8702,
  "avgClicksPerDay": 8919.43,
  "placement": "organic",
  "isSponsored": false,
  "domainOccurrence": 1,
  "entryKey": "adaptlypost.com|https://adaptlypost.com/",
  "boardUrl": "https://outseo.lol/",
  "clicksWindow": "Last 28 days",
  "scrapedAt": "2026-08-24T21:06:07.342Z"
}
```

**A `change` row:**

```json
{
  "rowType": "change",
  "changeType": "rankUp",
  "rank": 1,
  "domain": "adaptlypost.com",
  "previousRank": 3,
  "rankDelta": 2,
  "organicClicks": 249744,
  "previousOrganicClicks": 249744,
  "clickDelta": 0,
  "previousSnapshotAt": "2026-08-23T00:00:00.000Z",
  "clicksWindow": "Last 28 days",
  "scrapedAt": "2026-08-24T21:07:41.008Z"
}
```

**The `boardStats` row:**

```json
{
  "rowType": "boardStats",
  "visitorCount": 436,
  "websitesBenchmarked": 92,
  "totalOrganicClicks": 500788,
  "rankedListings": 92,
  "topDomain": "adaptlypost.com",
  "topDomainClicks": 249744,
  "medianOrganicClicks": 41,
  "clicksWindow": "Last 28 days",
  "sponsorSlots": [
    { "afterRank": 10, "priceUsd": 199 },
    { "afterRank": 20, "priceUsd": 75 }
  ],
  "boardUrl": "https://outseo.lol/",
  "scrapedAt": "2026-08-24T21:06:07.342Z"
}
```

### Key output fields

| Field | Meaning |
|---|---|
| `rank` | True board position. Filtering never renumbers it. `null` on sponsored and spotlight cards, which the board does not rank. |
| `organicClicks` | Verified Google Search Console clicks over the board's window. |
| `clicksSharePct` | This entry's share of every organic click on the board, to four decimals. The leader holds close to half, so the tail needs the precision. |
| `avgClicksPerDay` | `organicClicks` spread over the board's window. A derived daily rate, not a measured one. |
| `clicksWindow` | The window the click figure covers, read off the board rather than hardcoded. |
| `sourceUrl` | Destination with outseo's `?ref=` tracking parameter stripped. The `http://` vs `https://` and `www.` distinctions are preserved, because that is exactly what separates two Search Console properties on one domain. |
| `placement` | `organic`, `sponsored` or `spotlight`. |
| `domainOccurrence` | `1` the first time a domain appears, `2` for a second property on the same domain, and so on. |
| `entryKey` | The identity monitor mode diffs on. |
| `changeType` | `new`, `gone`, `rankUp`, `rankDown` or `clicksJumped`. |
| `rankDelta` | Positive means the entry moved **up** the board. |

### FAQ

**Where do the click numbers come from?**
From each site owner's own Google Search Console, connected read-only. The owner chooses which properties to publish. These are measured clicks, not the modelled estimates a traffic tool would give you.

**Why does the same domain appear twice?**
Because the board ranks Search Console *properties*, not domains, and one domain can have several — `https://example.com` and `http://www.example.com` are distinct properties with distinct click counts. `domainOccurrence` marks the second and later appearances.

**How big is the board?**
92 benchmarked sites as of 24 August 2026, on a site that launched the day before. It arrives in a single request, so a full run is fast and cheap regardless of how large it grows.

**Do I pay for the boardStats row?**
No. It is one row of context per run and it is not billed.

**Why are sponsored cards off by default?**
They carry no rank and no click count, because the board does not measure them. The empty "your brand here" slot adverts are never emitted at all — they point at a DM link rather than a site — but their asking prices are reported on the `boardStats` row.

**What happens if the board's layout changes?**
The actor compares its parsed count against the total the board prints on itself and logs a warning when they disagree, so a partial parse shows up instead of quietly shipping a short board.

### Support

Found a bug or need a field that is not here? Open an issue on the actor's Issues tab and it will be looked at.

### Additional services

Need a scraper for a site that is not on the Store yet, or a custom data pipeline? Get in touch through the Apify profile.

### Explore more scrapers

- [outbid.lol Scraper](https://apify.com/memo23/outbid-scraper) — the same board's paid-rank sibling: bids, clicks and cost-per-click.
- [Whop Leaderboards Scraper](https://apify.com/memo23/whop-leaderboards-scraper)
- [Uneed.best Scraper](https://apify.com/memo23/uneed-scraper)

### 🤖 For AI agents & LLM apps

This actor is a good fit for agent pipelines: it needs no input to produce useful output, it returns strictly typed rows, and it is cheap and fast enough to call on a schedule.

```json
{
  "minOrganicClicks": 1,
  "sortBy": "clicksDesc",
  "includeBoardStats": true
}
```

Call it via the Apify API, or through the Apify MCP server as `memo23/outseo-scraper`. For a change feed, set `monitorChanges: true` and `onlyChanges: true` and poll on a schedule — a quiet interval returns a handful of rows.

### ⚠️ Disclaimer

This actor collects only data that outseo.lol publishes publicly and serves to any visitor without authentication. The site's `robots.txt` allows crawling of the public board and disallows `/connect` and `/api/`, neither of which this actor touches. It does not bypass access controls, log in, or collect private personal information. Click figures are published by the site owners themselves, who opt in by connecting Search Console and choosing which properties to make public.

You are responsible for how you use the data, including compliance with outseo.lol's terms, applicable data-protection law such as GDPR and CCPA, and anti-spam rules such as CAN-SPAM if you use the optional email-enrichment output. Scrape responsibly and at a reasonable rate.

### SEO keywords

outseo.lol scraper, outseo leaderboard scraper, organic traffic leaderboard, google search console clicks data, verified organic clicks, seo competitive intelligence, organic traffic benchmark, saas traffic data, indie hacker traffic leaderboard, website ranking scraper, organic click tracker, seo rank monitoring, traffic leaderboard api, startup traffic data, search console leaderboard, founder contact emails, apify outseo actor

# Actor input Schema

## `startUrls` (type: `array`):

outseo.lol board URLs. The leaderboard is a single page, so this is optional — leave it empty and the whole board is scraped. Only outseo.lol URLs are accepted; anything else fails fast rather than silently scraping the board instead.

## `minRank` (type: `integer`):

Start from this rank. Pair with 'Maximum rank' to pull a slice such as ranks 20-50 without paying for the rows above it. Empty or 0 = start at rank 1.

## `maxRank` (type: `integer`):

Stop after this rank. Empty or 0 = the entire board. The whole leaderboard arrives in one request either way, so this trims the bill rather than the crawl.

## `minOrganicClicks` (type: `integer`):

Only entries with at least this many verified Search Console clicks over the board's window. Set to 1 to drop the long tail of sites that have connected but recorded nothing yet.

## `maxOrganicClicks` (type: `integer`):

Only entries with at most this many clicks — the 'who is still small' query. Empty or 0 = no ceiling.

## `keywords` (type: `array`):

Keep only entries matching any of these (case-insensitive substring). Matched against the domain, destination URL and description unless `keywordFields` narrows it. Example: `["ai", "video"]` keeps every entry mentioning either. Empty = keep everything. Default `[]`.

## `excludeKeywords` (type: `array`):

Drop entries matching any of these (case-insensitive substring). Applied after `keywords`, so an excluded term always wins. Example: `["casino"]`. Empty = drop nothing. Default `[]`.

## `keywordFields` (type: `array`):

Restrict keyword matching to specific fields. Empty = match across the domain, URL and description at once.

## `domains` (type: `array`):

Only entries on these domains. Subdomains are included automatically, and a bare TLD such as `.ai` matches every entry on it.

## `includeSponsored` (type: `boolean`):

Emit the board's sponsored cards. They carry a name, description and destination but no rank and no click count, since the board does not measure them.

## `includeSpotlight` (type: `boolean`):

Emit the random spotlight card at the top of the page. It is drawn from the board itself, so it is normally already present as a ranked row — turning this on usually means one duplicated site.

## `monitorChanges` (type: `boolean`):

Emit `rowType: "change"` rows for entries that are new, gone, re-ranked, or moving on clicks. Each carries the previous value and the delta. The first run has nothing to compare against, so it stores a baseline and reports zero changes.

## `onlyChanges` (type: `boolean`):

Skip the full board and return the change rows alone. This is the cheap scheduled-run setting: a quiet day costs a handful of rows instead of the entire board. Has no effect unless `monitorChanges` is also true. Example: `monitorChanges: true, onlyChanges: true`. Default false.

## `minClickJump` (type: `integer`):

A click move smaller than this is not a change. Search Console totals drift daily on every active site, so a threshold of 1 makes almost every entry a change row — raise it to see only meaningful movement.

## `monitorStoreName` (type: `string`):

Named key-value store holding the snapshot. Defaults to `outseo-monitor`. It must be a named store — a run's default store is created fresh each run, so a snapshot there would never survive to the next run. Give each scheduled monitor its own name so several can track different filters independently.

## `sortBy` (type: `string`):

Ordering of the entry rows: `rank` (board order), `clicksDesc`, `clicksAsc`, or `domainAsc`. Ranks are unaffected — a row always reports its real board position regardless of sort. Default `rank`.

## `includeBoardStats` (type: `boolean`):

Append one `rowType: "boardStats"` row with live visitors, the board's own benchmarked-site count, total and median organic clicks, the top site, and the asking price of every open sponsor slot. This row is never billed.

## `enrichEmails` (type: `boolean`):

Best-effort: visit each entry's own website and harvest contact emails, phone numbers and social profiles into `contactEmail` and `emailEnrichment`. Every entry on this board is a verified website, so no company-matching guesswork is involved. Billed only for entries where an email is actually found. Adds roughly a second per entry.

## `maxItems` (type: `integer`):

Hard cap on rows pushed to the dataset, counting entry and change rows together. Free-tier Apify accounts are capped at 100.

## `maxConcurrency` (type: `integer`):

Only used by the optional email enrichment — the board itself is a single request. Kept low on purpose so enrichment does not hammer the sites it visits.

## `proxy` (type: `object`):

Runs direct by default, so you pay nothing for proxies. outseo.lol answers ordinary requests today. Should it ever switch on Vercel's security checkpoint the way its sibling board did, the actor clears it in-run on its own — still no proxy required. Enable one only if you want the traffic to originate from a specific network.

## Actor input object example

```json
{
  "startUrls": [
    "https://outseo.lol/"
  ],
  "minRank": 0,
  "maxRank": 50,
  "minOrganicClicks": 0,
  "maxOrganicClicks": 0,
  "keywords": [],
  "excludeKeywords": [],
  "keywordFields": [],
  "domains": [],
  "includeSponsored": false,
  "includeSpotlight": false,
  "monitorChanges": false,
  "onlyChanges": false,
  "minClickJump": 1,
  "monitorStoreName": "",
  "sortBy": "rank",
  "includeBoardStats": true,
  "enrichEmails": false,
  "maxItems": 1000,
  "maxConcurrency": 8,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://outseo.lol/"
    ],
    "maxRank": 50,
    "maxItems": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/outseo-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["https://outseo.lol/"],
    "maxRank": 50,
    "maxItems": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/outseo-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://outseo.lol/"
  ],
  "maxRank": 50,
  "maxItems": 1000
}' |
apify call memo23/outseo-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/outseo-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hiWyv2Hq4IINywpTG/builds/gkv1hOpzNHRoSKV4M/openapi.json
