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 JSON array — one item per member with firstName, lastName, email, invitedBy, joinedDate, survey[], tier, ltv (the actor parses the underlying CSV for you)
Key gotcha Async 3-step flow under the hood. The actor wraps it — you get parsed member items back in one call

Prerequisites

What the export returns

The actor wraps Skool’s 3-step async export flow and returns parsed JSON — one item per member, no CSV parsing on your side. Real shape (validated against a production community, 700+ members):

[
  {
    "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 underlying CSV from Skool (FirstName,LastName,Email,Invited By,JoinedDate,Question1-3,Answer1-3,Price,Recurring Interval,Tier,LTV) is already parsed: survey fields are merged into a single survey array of {question, answer} pairs, and price/recurring-interval are folded into the human-readable ltv string.

Data quality reality check (measured on a 700+ member community):

If you need the acquisition source (Joined from {channel}), that’s NOT in the export — 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 parsed member items directly in the dataset.

Step 2 — Get the members

The synchronous run-sync-get-dataset-items endpoint gives you one HTTP call → array of member objects:

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 — one object per member (see “What the export returns” above for the field shape). The array length is your member count.

Pipe straight to file as JSON:

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

Or convert to CSV with jq if you want the flat tabular form:

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

(The first line emits the header from the first member’s keys, the second emits the data rows.)

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.