DEV Community

Cover image for How I Export 700+ Skool Members to CSV with One API Call (Email, Tier, LTV, Survey)
Cristian Tala
Cristian Tala

Posted on • Originally published at skool-api.cristiantala.com

How I Export 700+ Skool Members to CSV with One API Call (Email, Tier, LTV, Survey)

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" }
  }'
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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"]
  }
}
Enter fullscreen mode Exit fullscreen mode
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 } }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)