Skip to the content.

Export Skool members to CSV

The Skool admin panel has an “Export” button that downloads your member roster as CSV — but only if you click it manually. This recipe does the same thing via the API, so you can pipe it into a CRM, an analytics pipeline, or a nightly snapshot job.

The exported CSV is the only reliable source for member emails on Skool. The server-rendered member list returns email:"" (Skool strips it from the SSR payload), and GET /users/{id} only returns the email of the authenticated user — even community owners see masked emails for third parties. The bulk export is the workaround Skool itself uses for the admin UI button.

It’s the exact flow that backs Cágala, Aprende, Repite’s CRM single-source-of-truth pipeline.

Quick reference (TL;DR for agents)

   
Goal Export your Skool member roster (active / cancelling / churned / banned) as CSV
Stack Any HTTP client (curl / Python / n8n) + the Apify-hosted actor
Actions used members:export
Setup time ~3 min
Ongoing cost $0.05 per export run (regardless of community size)
Output CSV: FirstName, LastName, Email, Invited By, JoinedDate, Question1-3, Answer1-3, Price, Recurring Interval, Tier, LTV
Key gotcha Async 3-step flow under the hood. The actor wraps it — you get the CSV string back in one call

Prerequisites

What the export returns

Real shape from a production community (728 members, 28-may-2026):

FirstName,LastName,Email,Invited By,JoinedDate,Question1,Question2,Question3,Answer1,Answer2,Answer3,Price,Recurring Interval,Tier,LTV
Maria,Lopez,maria@example.com,,2026-04-12,What's your LinkedIn?,What are you building?,How did you find us?,linkedin.com/in/marialopez,SaaS for restaurants,Newsletter,0,,standard,0
...

Data quality reality check (measured on a 728-member community):

If you need the acquisition source (Joined from {channel}), that’s NOT in the CSV — it’s in member.metadata.attrSrcComp from the SSR payload. Use members:list for that.

Step 1 — Call the export action

{
  "action": "members:export",
  "cookies": "auth_token=...; client_id=...; aws-waf-token=...",
  "groupSlug": "your-community",
  "params": {
    "status": "active",
    "tiers": ["standard", "premium", "vip"]
  }
}
Param Values Default
status active / cancelling / churned / banned active
tiers array of tier slugs you defined in admin (e.g. ["standard", "premium", "vip"]) all tiers
sortType empty string (sort is Skool-default) ""

Behind the scenes the actor runs the 3-step async flow that 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 in-progresscompleted (typically 2-8 sec)
  3. POST /files/{file_id}/download-url → returns a signed CloudFront URL (expires in ~5 min) → GET that URL = CSV body

You don’t see any of that — the actor returns the CSV string directly in the dataset.

Step 2 — Get the CSV

The synchronous run-sync-get-dataset-items endpoint gives you the CSV in one HTTP call:

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": "'"$SKOOL_COOKIES"'",
    "groupSlug": "your-community",
    "params": { "status": "active" }
  }'

Response is a JSON array with one item, where csv contains the full CSV body:

[
  {
    "success": true,
    "csv": "FirstName,LastName,Email,...\nMaria,Lopez,maria@example.com,...\n...",
    "rowCount": 728
  }
]

Pipe it straight to a file:

curl ... | jq -r '.[0].csv' > members-$(date +%F).csv

Step 3 — Pipe into your CRM / analytics

The two patterns we use in production:

A. Nightly snapshot → NocoDB. A cron job fetches the export, diffs against yesterday’s, and upserts into crm.personas keyed by Email. New rows get a joined_at timestamp, missing rows get churned_at. This is the CRM single-source-of-truth flow.

B. On-demand pull for a specific cohort. Export tier=premium, filter by JoinedDate >= 30 days ago, send each row to a personalized email sequence in Listmonk / Postmark. The Answer1-3 columns give you the personalization data (their LinkedIn, what they’re building, where they heard about you).

Production gotchas

Pairing with members:list for the full picture

The CSV doesn’t include the acquisition source (Joined from LinkedIn, Joined from your blog). To enrich your CRM with that, also run members:list and merge by memberId:

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

members:list returns joinedFrom / joinedVia per member — the missing piece for attribution analytics.

See also


Use this in production — no setup

The hardest part of building Skool automation isn’t the API logic — it’s the auth (cookies expire every ~3.5 days, WAF token rotation, weekly Skool buildId changes). The Skool All-in-One API actor on Apify handles all of that.

→ Open the actor on Apify

New to Skool? Launch your community here — 14-day free trial. Need an n8n instance? Get started free — the workflow tool we use throughout these recipes.