惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
F
Fortinet All Blogs
腾讯CDC
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
WordPress大学
WordPress大学
雷峰网
雷峰网
小众软件
小众软件
D
DataBreaches.Net
V
Visual Studio Blog
博客园 - Franky
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
博客园 - 聂微东
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
云风的 BLOG
云风的 BLOG

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I Export 700+ Skool Members to CSV with One API Call ...
Cristian Tala · 2026-05-29 · via DEV Community

If you run a community on Skool, you've probably noticed something annoying about the member list: the email field is blank for most members. Not your bug. Skool's server-rendered member list returns email: "" for everyone except the authenticated user. Even community owners get masked emails for third parties when calling GET /users/{id}.

The admin panel has an "Export" button that does give you everything — emails, tiers, LTV, survey answers — but only if you click it manually. For a CRM sync pipeline, that's not enough.

This post shows how to call that same export programmatically via the Skool All-in-One API actor on Apify. One HTTP request, full member roster as JSON, ready to pipe into NocoDB / Airtable / Postgres / anywhere.

It's the foundation of the CRM single-source-of-truth pipeline I run for Cágala, Aprende, Repite (700+ members).

What you'll need

  • An Apify account (free tier covers this — the export costs ~$0.05/run)
  • Skool admin/owner cookies for your community
  • A community where you're admin (export is admin-only)

If you've never used the actor before: it wraps Skool's entire REST surface so you don't have to handle cookies, WAF token rotation, weekly buildId changes, or the 3-step async pattern Skool's export uses internally. One JSON POST → parsed response.

The one-liner that returns your full roster

curl -X POST \
  "https://api.apify.com/v2/acts/cristiantala~skool-all-in-one-api/run-sync-get-dataset-items?token=$APIFY_TOKEN&timeout=60&build=latest" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "members:export",
    "cookies": "auth_token=...; client_id=...; aws-waf-token=...",
    "groupSlug": "your-community",
    "params": { "status": "active" }
  }'

Response (one item per member, already parsed — no CSV parsing on your side):

[
  {
    "firstName": "Maria",
    "lastName": "Lopez",
    "email": "maria@example.com",
    "invitedBy": "Cristian Tala",
    "joinedDate": "2026-04-12 18:14:56",
    "survey": [
      { "question": "What's your LinkedIn?", "answer": "linkedin.com/in/marialopez" },
      { "question": "What are you building?", "answer": "SaaS for restaurants" },
      { "question": "How did you find us?", "answer": "Newsletter" }
    ],
    "tier": "standard",
    "ltv": "$0"
  }
]

The actor handles the underlying 3-step flow Skool's admin UI runs when you click Export:

  1. POST /groups/{id}/request-bulk-action?type=bulk-export-csv → returns {token, file_id}
  2. GET /wait?token={token} → polls until completed (2-8 sec typically)
  3. POST /files/{file_id}/download-url → signed CloudFront URL → GET that → CSV body → parse to JSON

You don't see any of that. One call in, parsed JSON out.

Filter what you export

{
  "params": {
    "status": "active",
    "tiers": ["premium", "vip"]
  }
}

Param Values Default
status active / cancelling / churned / banned active
tiers array of your community's tier slugs all tiers

Useful for cohort analysis: "all premium members who joined in the last 30 days" → filter status: active, tiers: ["premium"], then client-side filter by joinedDate.

The data quality reality nobody mentions

Measured against a real production community of 700+ members:

  • ~68% of members have a populated email field. The other ~32% (typically members who joined via the Skool Discovery network — the platform's "browse communities" feature) don't share their email with the community owner. Skool keeps that field empty in the export. This is structural, not a bug.
  • ~88% have a LinkedIn URL in their first survey answer (when the apply form asks for it). Apply-form answers are far more complete than the email field.
  • invitedBy is empty unless the member came through a tracked Skool referral link.

For the ~32% with no email, fall back to the LinkedIn URL from the survey. That's typically enough to identify the member for outreach.

What the export does NOT include

One important gap: the acquisition source (Joined from LinkedIn / Joined from your blog / Joined from Skool network) is NOT in the export. It lives in member.metadata.attrSrcComp on the SSR member object — accessed via a separate call:

{ "action": "members:list", "cookies": "...", "groupSlug": "...", "params": { "all": true } }

members:list returns joinedFrom / joinedVia per member — the missing piece for attribution analytics. Merge both responses by memberId if you need the full picture.

Pipe straight to your CRM

Two production patterns I use:

A. Nightly snapshot → NocoDB. Cron runs the export, diffs against yesterday's, upserts into a personas table keyed by email. New rows get joined_at, missing rows get churned_at. This is the single-source-of-truth for the CRM.

B. On-demand cohort pull. Export tier: ["premium"], filter rows where joinedDate >= 30 days ago, send each to a personalized email sequence in Postmark. The survey array gives the personalization data: their LinkedIn, what they're building, where they heard about you.

# JSON snapshot
curl ... > members-$(date +%F).json

# Or convert to CSV with jq
curl ... | jq -r '
  (.[0] | [.firstName, .lastName, .email, .joinedDate, .tier, .ltv] | @csv),
  (.[]  | [.firstName, .lastName, .email, .joinedDate, .tier, .ltv] | @csv)
' > members-$(date +%F).csv

Gotchas I hit so you don't have to

  • build.stale error = expired cookies, not a bug. Skool's aws-waf-token rotates every ~3.5 days. Re-run auth:login with email+password to get fresh cookies. The actor returns a clear errorCode: BUILDID_STALE with the exact JSON payload you need to re-auth.
  • Don't poll the export every 5 minutes. Designed for daily/weekly snapshots. The CSV is generated on-demand by Skool — calling it on every webhook is wasteful and you'll eventually hit rate limits.
  • Large communities (10K+ members) can take 30+ seconds to generate. Set your HTTP client timeout to at least 90 sec.
  • invitedBy is empty unless the member came through a tracked referral. All blanks doesn't mean broken — just means no member came through your ref link.

Why this actor exists

I run my own Skool community alone — no team, no virtual assistants. Approving members, replying to posts, publishing courses, exporting member data for follow-up. Everything in this actor (and the 20 production recipes in the docs) exists because I needed it to keep up with the daily work as a solo admin.

If you're running a Skool community without a team and the manual admin clicks are eating your day, the actor is built for you.

→ Use the Skool All-in-One API actor on Apify — pay-per-event (~$1.50/mo typical).

→ Full recipe with all the edge cases

New to Skool? Launch your community here — 14-day free trial.