DEV Community

Cover image for How I Publish Podcast Episodes to a Skool Classroom 100% Automatically
Cristian Tala
Cristian Tala

Posted on

How I Publish Podcast Episodes to a Skool Classroom 100% Automatically

How I Publish Podcast Episodes to a Skool Classroom 100% Automatically

We record an episode of Es La Hora De Aprender every week. Three hosts, hour and change of conversation about AI, startups, and the LATAM ecosystem. By the time we finish recording, the next 60 minutes of work are mechanical:

  • Transcribe.
  • Cut a thumbnail.
  • Publish to the podcast website.
  • Notify the email list.
  • Cross-post to two WordPress sites for SEO.
  • Add the episode as a lesson in the Skool community classroom.

I do exactly zero of those steps by hand. The whole chain runs from one shell command. The Skool step — adding the new episode as a lesson in the Podcasts CAR course inside my community — is what I want to walk through here.

This is one of the few automations that requires the Skool API. Skool's UI doesn't expose any bulk-add for classroom lessons. If you want a new episode to appear in your classroom every week, you either click through eight modals manually, or you call the API.

I run the Skool API actor on Apify for this. The actor exposes the classroom endpoints I reverse-engineered. Here's what the pipeline does end-to-end, and the gotchas I hit building it.


What the automation produces

The end state, for context: a course inside my community called Podcasts CAR, free for all members, with one folder per show and one lesson per episode. Episodes are ordered newest-first.

Each lesson is a single page with:

  • A 1-2 line hook for the episode
  • The YouTube thumbnail (full size, clickable to YouTube)
  • Direct links to YouTube, Spotify, Apple Podcasts
  • Two short paragraphs on what's covered
  • Five concrete takeaways
  • An open question for the community
  • Footer with hosts + episode date

The lesson is not the transcript. The transcript lives on eslahoradeaprender.com for SEO. The classroom lesson is a curated synthesis — the version a community member skims to decide whether the episode is worth their hour.

That distinction matters. If you dump the transcript into the classroom, you cannibalize your own SEO and the lesson becomes too long to skim. Synthesis in the classroom, full transcript on the podcast site.

12 episodes published this way so far. Zero manual clicks per episode.


The pipeline, end to end

The chain is six steps. Skool is step 5.

  1. Episode published on the podcast website (Astro static site)
  2. Build check + git push + IndexNow notification to Bing/Yandex
  3. Syndicated to cristiantala.com (WordPress, as a podcast_episodes custom post type) and ecosistemastartup.com (WordPress, as a regular post)
  4. Google Search Console sitemap resubmit
  5. Skool lesson created in the Podcasts CAR course → this post is about this step
  6. Email notification to the newsletter list

Steps 1-4 and 6 are out of scope for this post. Step 5 is what the Skool API handles.


Why Skool needed the API

When I designed the Podcasts CAR course, I had two options:

Option A: publish each episode as a separate Skool post in the community feed.

Option B: publish each episode as a lesson inside a course in the classroom.

Option A is what most Skool community owners do. It's the path of least resistance because the UI makes it easy. It also means the episode scrolls out of the feed in two days and is never findable again.

Option B (classroom) gives you a permanent, indexed, browsable archive. New members can binge old episodes the way they binge any other course. Episodes don't compete with daily feed activity. And the format scales: I added a second folder for the Ecosistema Startup podcast without rebuilding anything.

But Option B has zero UI affordance for "add a new lesson to an existing course from outside Skool." You have to log in, navigate to the course, click "Add lesson", fill in the modal, drag the lesson into the right position, click save, repeat for the body content, set the cover, save again. Every. Single. Week.

The API lets me do it in two function calls.


The actor calls (the actual code)

Here's the slimmed-down version of what the pipeline does after the episode is published on the podcast site. Skipping error handling and the YouTube ID extraction so the flow is clear.

Step 1: Build the lesson body in markdown

# EP12: Ley IA Chile · agentes autónomos · default a IA

Mediados de mayo. Cristian, Diego y Rodrigo discuten el borrador
de la ley IA chilena, qué significa "agente autónomo" en la
práctica, y por qué cada vez más gente vuelve al default de
preguntarle a la IA antes que a Google.

## Ver o escuchar el episodio

![EP12 en YouTube](https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg)

▶️ **YouTube**[youtube.com/watch?v=VIDEO_ID](https://youtube.com/watch?v=VIDEO_ID)
🎧 **Spotify**[open.spotify.com/episode/...](https://open.spotify.com/episode/...)
🍎 **Apple Podcasts**[podcasts.apple.com/...](https://podcasts.apple.com/...)

## Lo que se cubre

[Two paragraphs of synthesis — what the episode is about, written
to be skimmable in 45 seconds]

## 5 takeaways concretos

1. ...
2. ...
3. ...
4. ...
5. ...

## Pregunta abierta para la comunidad

[A real open question that invites a comment in the community,
not a rhetorical one]

---
*Cristian Tala · Diego Arias · Rodrigo Rojo · 15-may-2026*
Enter fullscreen mode Exit fullscreen mode

This is just a string in my pipeline. Skool eats markdown via the actor; I don't have to learn their internal TipTap JSON format.

Step 2: Authenticate (once per 3.5 days)

The actor's auth:login action returns a cookie string that's valid for ~3.5 days. I cache it in my pipeline's KV store and only re-login when it expires.

{
  "action": "auth:login",
  "email": "admin@cristiantala.com",
  "password": "...",
  "groupSlug": "cagala-aprende-repite"
}
Enter fullscreen mode Exit fullscreen mode

Returns:

{
  "success": true,
  "cookies": "auth_token=...; client_id=...; aws-waf-token=...",
  "expiresAt": "2026-05-20T22:51:34.636Z",
  "buildId": "1778782667413"
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the lesson page

{
  "action": "classroom:createPage",
  "groupSlug": "cagala-aprende-repite",
  "cookies": "<from auth:login>",
  "params": {
    "courseId": "76b80da4e0ef48cb9da7b7f23229f44a",
    "parentId": "<folder-id-for-ELHDA>",
    "title": "EP12: Ley IA Chile · agentes autónomos · default a IA"
  }
}
Enter fullscreen mode Exit fullscreen mode

The response includes a pageId. We use that next.

Step 4: Set the lesson body

{
  "action": "classroom:setBody",
  "groupSlug": "cagala-aprende-repite",
  "cookies": "<from auth:login>",
  "params": {
    "pageId": "<from step 3>",
    "title": "EP12: Ley IA Chile · agentes autónomos · default a IA",
    "bodyMarkdown": "<the markdown from step 1>"
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it. The episode is now a lesson in the classroom, ordered at the top of the ELHDA folder.

End-to-end from "episode published on podcast site" to "lesson live in Skool classroom" — about 30 seconds, mostly waiting on Skool's API.


Three gotchas that cost me time

1. YouTube embeds: thumbnail-only, no native player

I wanted an embedded YouTube player in the lesson. Skool's REST API accepts a videoId field in setBody — but it requires Skool's internal video ID, not the YouTube ID.

When you paste a YouTube URL into Skool's UI editor, Skool's frontend hits some endpoint, resolves the YouTube URL into an internal video ID, and stores that. There's no documented REST endpoint to do this resolution programmatically.

For now I render the YouTube thumbnail as a markdown image and put the YouTube link immediately below. Visually it looks like a clickable embed. Functionally, the click goes to YouTube instead of playing inline. Acceptable trade-off.

Capturing the YouTube → Skool video ID endpoint with Playwright is on the roadmap. If you'd find it useful, open an issue on the actor — that's how I prioritize.

2. YouTube ID regex: don't use \w+

My first version of the pipeline extracted YouTube IDs from URLs with a regex that used \w+. That matches letters, digits, and underscores — but not hyphens.

YouTube IDs are exactly 11 characters from [A-Za-z0-9_-]. Two of my episodes had IDs starting with or containing hyphens. The regex captured 43nvC instead of 43nvC-1fxKY and Ph instead of Ph-7H1atqoA. Episode 7's ID started with - and the regex didn't match at all.

Fix: use [A-Za-z0-9_-]{11} for YouTube IDs. Always specify the character class and the fixed length.

Generic-feeling gotcha, but it cost me an afternoon. Sharing in case it saves you one.

3. Re-running the pipeline creates a NEW course

The script I use for one-shot course creation (initial publish of the Podcasts CAR course, all 12 episodes at once) is not idempotent. Re-running it creates an entirely new course, not an update. Skool has no "upsert course by name."

For incremental episode additions, I use a different code path that:

  1. Looks up the existing courseId (hardcoded in my pipeline)
  2. Looks up the existing folderId (also hardcoded)
  3. Creates just the new page (createPage) and sets the body (setBody)

If you build this kind of automation: keep the course ID and folder IDs as constants in your pipeline. Don't recompute them by name lookup unless you want a course nuke on a typo.


What this saves me per week

The manual version of this:

  1. Log in to Skool
  2. Navigate to Podcasts CAR course
  3. Click Add lesson on the ELHDA folder
  4. Type the title
  5. Drag the new lesson to position 1 (newest first)
  6. Click into the lesson
  7. Type or paste the body content
  8. Save
  9. Confirm cover renders, save again if it doesn't

Realistic time: 8-12 minutes per episode if you're focused, more if you context-switch in the middle. Weekly podcast = 35-50 minutes/month, every month, forever.

The automated version: zero seconds of human time. The shell command that publishes the episode to the website fires the whole chain.

Multiply this across all the other things I automate against Skool (member approvals, daily engagement bot, course publishing) and the actor pays for itself the first week.


How to copy this

If you have a Skool community and a podcast (or any recurring content), the pattern transfers directly:

  1. Create one course in your Skool classroom to hold the archive (do this manually, once)
  2. Note down the courseId and folderId — you'll hardcode these in your pipeline
  3. Authenticate once with auth:login, cache the cookies for 3.5 days
  4. For each new content item, call classroom:createPage + classroom:setBody

The whole automation is two actor calls per episode (plus the auth refresh every few days). In n8n, that's two HTTP request nodes. In Make.com, two Apify modules. In Zapier, two steps.

👉 Skool API — Posts, Members & Classroom on Apify

Pricing: pay-per-event. Two writes per episode + auth occasionally = a few cents per week per podcast.

If you want the full pillar on what the actor can do beyond classroom (posts, members, comments, files): I wrote that here — The Complete Skool API Guide (2026).

And if you want the next case study — uploading an 18-course classroom from markdown in a single script — that's here: How I Uploaded an 18-Course Classroom to Skool With One Script.


Found a gotcha I didn't cover, or you've automated something on Skool that requires an endpoint the actor doesn't expose? Open an issue on the actor's Apify page. That's how the roadmap gets built.

Top comments (0)