The hosted Skool API.
Skool has no official API. Skooly hosts the hard part — Playwright login, session refresh, CloudFront, cursor pagination, snake_case — behind stable, versioned /v1 routes. You automate your own authenticated Skool account.
Unofficial-API disclaimer
Every route rides a reverse-engineered surface (api2.skool.com + Next.js data). It can change without notice — when Skool drifts we surface skool_shape_changed (502) instead of silent garbage.
Four steps.
- Sign up (magic link or Google).
- Connect your Skool account in the dashboard.
- Mint an API key — it’s shown once.
- Make your first call:
# 1. Sign up + connect your Skool account in the dashboard
# 2. Mint a key → sk_live_…
# 3. Call the API
curl https://api.skooly.dev/v1/groups/$GID/posts \
-H "Authorization: Bearer $SKOOL_API_KEY"
One Bearer token.
The Data API is authed with an API key: Authorization: Bearer sk_live_…. A key belongs to your account and carries scopes (posts:read, comments:read, …). If you linked several Skool accounts, target one with the X-Skool-Connectionheader; otherwise the key’s connection (or your single one) is used.
Rotate with POST /v1/keys/:id/rotate — the old key keeps working for a 24h grace window so you can swap without downtime. Revoke instantly with DELETE.
Authorization: Bearer sk_live_xxxxxxxxxxxx
# optionally target a specific linked connection:
X-Skool-Connection: <connectionId>
Per-key, per-tier.
| Tier | req/min | req/month | connections |
|---|---|---|---|
| Free | 30 | 5k | 1 |
| Pro | 120 | 100k | 5 |
| Scale | 600 | 1M+ | 25 |
Every response carries X-RateLimit-Limit / -Remaining / -Reset. Over the limit → 429 + Retry-After. A second per-connection throttle protects your underlying Skool account.
One envelope.
// success
{ "data": <resource | resource[]>,
"meta": { "pagination": { "cursor": "abc", "hasMore": true, "truncated": false } } }
// error
{ "error": { "code": "session_unavailable", "message": "…", "status": 401 } }
Error codes
| unauthorized | 401 | API key ausente, inválida o expirada. |
| session_unavailable | 401 | La conexión de Skool expiró o falta. Reconecta la cuenta. |
| forbidden | 403 | La API key no tiene el scope que la ruta exige. |
| rate_limited | 429 | Se superó el límite del tier. Ver Retry-After. |
| skool_bad_request | 400/403/404 | Skool respondió un 4xx duro (passthrough). |
| skool_upstream_error | 502 | Skool falló (5xx/429) tras los reintentos. |
| skool_shape_changed | 502 | La estructura de Skool cambió (respuesta irreconocible). |
The /v1 surface.
Groups
| GET | /v1/groupsgrupos de la conexión activa | Implemented |
| GET | /v1/groups/:gid | Implemented |
| GET | /v1/groups/:gid/postsfeed ?pages=N | Implemented |
Posts
| GET | /v1/posts/:iddetalle + comentarios · ?group= | Implemented |
| POST | /v1/postscrear post — Phase 2 | Planned |
| PATCH | /v1/posts/:id | Planned |
| DELETE | /v1/posts/:id | Planned |
| POST | /v1/posts/:id/pin | Planned |
Comments
| GET | /v1/posts/:id/commentspaginación por cursor · ?group= | Implemented |
| POST | /v1/posts/:id/commentsPhase 2 | Planned |
| DELETE | /v1/comments/:id | Planned |
Members
| GET | /v1/groups/:gid/members | Planned |
| POST | /v1/members/:id/(approve|reject|ban) | Planned |
Courses · Chat · Events · Leaderboard
| GET | /v1/groups/:gid/courses | Planned |
| GET | /v1/dms | Planned |
| GET | /v1/groups/:gid/events | Planned |
| GET | /v1/groups/:gid/leaderboard | Planned |
Management (web-session authed)
| POST | /v1/connectionsconectar Skool | Implemented |
| GET | /v1/connections | Implemented |
| POST | /v1/keysmint · devuelve la key una vez | Implemented |
| POST | /v1/keys/:id/rotate | Implemented |
| GET | /v1/mewhoami + uso | Implemented |
@skoolapi/sdk
Isomorphic, zero-dep, fetch-based. Typed errors mirror the envelope; iterate() auto-paginates. Ships in Phase 3.
import { SkoolClient } from "@skoolapi/sdk";
const skool = new SkoolClient({ apiKey: process.env.SKOOL_API_KEY });
const { data } = await skool.posts.list(groupId);
for await (const post of skool.posts.iterate(groupId)) {
// typed errors mirror the envelope's error.code
}
Built for agents.
Every command speaks JSON, with stable exit codes an agent can branch on. An MCP server exposes the same surface to Claude & Codex. Ships in Phase 4.
# --json on every command → one JSON object to stdout
skool posts list $GID --json | jq '.data[].title'
# stable exit codes: 0 ok · 2 auth · 3 rate-limited · 4 upstream · 5 shape-changed