# Discord Server Stats Scraper (`xtracto/discord-server-stats`) Actor

Get public Discord server metadata from invite links: member and online counts, description, boost tier, features, verification level, icon and banner URLs, and the channel list.

- **URL**: https://apify.com/xtracto/discord-server-stats.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.67 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Discord Server Stats — Member Counts, Boosts & Channels from Invite Links

Turn a list of Discord invite links into structured server data: member and online counts,
description, boost tier, feature flags, verification level, icon/banner URLs and — when the owner
has the widget enabled — the channel list and live presence count.
HTTP-only, no bot token, no account, no browser.

### What this actor does *not* do

**It does not read messages.** Discord message history requires a logged-in session and a
WebSocket gateway connection; there is no anonymous HTTP surface for it. This actor covers server
*metadata* only. If you need messages, you need a bot token and the Gateway API — a different tool.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `invites` | array | *required* | `https://discord.gg/python`, `discord.gg/python` or bare `python` — all three parse |
| `fetchWidget` | boolean | `true` | Also call the widget endpoint for the channel list and live presence |
| `includeOnlineMembers` | boolean | `false` | Add usernames of up to 100 online members (widget servers only) |
| `maxItems` | integer | *all* | Stop after this many invites |

### Output

```jsonc
{
  "_input": "https://discord.gg/python",
  "_source": "S1-invite-api",
  "_scrapedAt": "2026-09-09T09:12:04Z",

  "inviteCode": "python",
  "inviteUrl": "https://discord.gg/python",
  "guildId": "267624335836053506",
  "name": "Python",
  "description": "We're a large community focused around the Python programming language…",

  "memberCount": 430790,
  "onlineCount": 29564,
  "boostCount": 30, "boostTier": 3,
  "verificationLevel": 2,
  "vanityUrlCode": "python",
  "features": ["ANIMATED_BANNER", "COMMUNITY", "DISCOVERABLE", …],  // 49 flags
  "nsfw": false, "nsfwLevel": 0,

  "iconUrl": "https://cdn.discordapp.com/icons/267624335836053506/a_421bf….gif",
  "bannerUrl": "…", "splashUrl": "…",

  "inviteChannelId": "…", "inviteChannelName": "python-discussion",
  "inviteExpiresAt": null,          // null = permanent invite

  "widgetEnabled": true,            // ← gates the four fields below; null = fetchWidget was off
  "channelCount": 6,
  "channels": [{ "id": "…", "name": "Code/Help", "position": 1 }, …],
  "widgetPresenceCount": 29648,
  "instantInvite": "https://discord.com/invite/x298zHMz"
}
```

### Three things worth knowing

**`widgetEnabled` tells you which rows are complete.** The channel list comes from a second
endpoint that server owners can switch off, and most do — in testing 1 of 4 large servers had it
on. A disabled widget answers `403`, which this actor treats as a configuration fact, not a block:
you get the row with `widgetEnabled: false` and the four widget fields absent, never a silent
failure. Filter on that flag rather than assuming a missing `channels` means something went wrong.

**Icons are hashes upstream, URLs here.** Discord returns `a_421bfdb0…`; the leading `a_` means
animated, which changes the extension from `.png` to `.gif`. Building the CDN URL with the wrong
extension yields a 404 image, so that is resolved for you in `iconUrl`/`bannerUrl`/`splashUrl`.

**Online member usernames are opt-in.** `includeOnlineMembers` is `false` by default. Those are
real people's usernames and presence status; the counts (`onlineCount`, `widgetPresenceCount`)
answer most questions without publishing them, so turn it on only if you actually need the list.

### Errors

Every input produces exactly one row — a failure never disappears from the dataset, so a join
against your input list stays aligned. Failures carry `_error`:

| `_error` | Meaning |
|---|---|
| `invalid_input` | Not a Discord invite code or a discord.gg / discord.com invite URL |
| `invite_not_found` | Discord returned 404 — the invite is invalid, expired, or was revoked |
| `blocked` | Every TLS profile was refused; `_errorDetail` carries the last status |
| `unexpected_shape` | 200 JSON, but no `guild` object — the API contract changed |

If *every* input fails, the run itself fails rather than reporting success over an empty dataset.

### How it works

Two documented public endpoints, `curl_cffi` with a Chrome TLS fingerprint and a
`chrome131`/`safari180` fallback ladder:

1. `GET /api/v10/invites/{code}?with_counts=true&with_expiration=true` — the guild record.
2. `GET /api/v10/guilds/{guild_id}/widget.json` — channels and presence, when enabled.

Rate limiting is honoured, not guessed: a `429` carries `retry_after` in seconds and the actor
sleeps exactly that long. Transport errors and unexpected statuses back off exponentially across
the profile ladder.

# Actor input Schema

## `invites` (type: `array`):

Discord invites to look up. Accepts a full URL (https://discord.gg/python), a short form (discord.gg/python), or a bare code (python).

## `fetchWidget` (type: `boolean`):

Also call the server widget endpoint to get the channel list and live presence count. Only works when the server owner has enabled the widget; otherwise these fields stay empty.

## `includeOnlineMembers` (type: `boolean`):

Add the usernames of up to 100 currently-online members (widget-enabled servers only). These are personal usernames, so leave this off unless you actually need them.

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

Stop after this many invites. Leave empty to process the whole list.

## `proxyConfiguration` (type: `object`):

Discord rate-limits per IP. Datacenter proxies are enough for normal list sizes.

## Actor input object example

```json
{
  "invites": [
    "https://discord.gg/python",
    "https://discord.gg/reactiflux"
  ],
  "fetchWidget": true,
  "includeOnlineMembers": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset items shown in the 'Servers' view.

## `items` (type: `string`):

Every record this run produced, with all fields, as JSON.

# 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 = {
    "invites": [
        "https://discord.gg/python",
        "https://discord.gg/reactiflux"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/discord-server-stats").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 = { "invites": [
        "https://discord.gg/python",
        "https://discord.gg/reactiflux",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("xtracto/discord-server-stats").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 '{
  "invites": [
    "https://discord.gg/python",
    "https://discord.gg/reactiflux"
  ]
}' |
apify call xtracto/discord-server-stats --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,xtracto/discord-server-stats"
        }
    }
}
```

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/CE9jXlr6MKCU7ZEsJ/builds/kyVMDxLm2UDCyBbwj/openapi.json
