Back to skills
SkillHub ClubShip Full StackFull StackBackend

thinkific

Thinkific — manage courses, students, enrollments, coupons, and products via REST API

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,073
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B84.0

Install command

npx @skill-hub/cli install openclaw-skills-thinkific

Repository

openclaw/skills

Skill path: skills/aiwithabidi/thinkific

Thinkific — manage courses, students, enrollments, coupons, and products via REST API

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Backend.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install thinkific into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding thinkific to shared team environments
  • Use thinkific for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: thinkific
description: "Thinkific — manage courses, students, enrollments, coupons, and products via REST API"
homepage: https://www.agxntsix.ai
license: MIT
compatibility: Python 3.10+ (stdlib only — no dependencies)
metadata: {"openclaw": {"emoji": "🎓", "requires": {"env": ["THINKIFIC_API_KEY", "THINKIFIC_SUBDOMAIN"]}, "primaryEnv": "THINKIFIC_API_KEY", "homepage": "https://www.agxntsix.ai"}}
---

# 🎓 Thinkific

Thinkific — manage courses, students, enrollments, coupons, and products via REST API

## Requirements

| Variable | Required | Description |
|----------|----------|-------------|
| `THINKIFIC_API_KEY` | ✅ | API key |
| `THINKIFIC_SUBDOMAIN` | ✅ | School subdomain |

## Quick Start

```bash
# List courses
python3 {{baseDir}}/scripts/thinkific.py courses --page <value>

# Get course
python3 {{baseDir}}/scripts/thinkific.py course-get id <value>

# Create course
python3 {{baseDir}}/scripts/thinkific.py course-create --name <value> --slug <value>

# Update course
python3 {{baseDir}}/scripts/thinkific.py course-update id <value> --name <value>

# Delete course
python3 {{baseDir}}/scripts/thinkific.py course-delete id <value>

# List chapters
python3 {{baseDir}}/scripts/thinkific.py chapters id <value>

# List users
python3 {{baseDir}}/scripts/thinkific.py users --page <value> --query <value>

# Get user
python3 {{baseDir}}/scripts/thinkific.py user-get id <value>
```

## All Commands

| Command | Description |
|---------|-------------|
| `courses` | List courses |
| `course-get` | Get course |
| `course-create` | Create course |
| `course-update` | Update course |
| `course-delete` | Delete course |
| `chapters` | List chapters |
| `users` | List users |
| `user-get` | Get user |
| `user-create` | Create user |
| `enrollments` | List enrollments |
| `enroll` | Create enrollment |
| `coupons` | List coupons |
| `coupon-create` | Create coupon |
| `products` | List products |
| `orders` | List orders |
| `groups` | List groups |
| `instructors` | List instructors |

## Output Format

All commands output JSON by default. Add `--human` for readable formatted output.

```bash
python3 {{baseDir}}/scripts/thinkific.py <command> --human
```

## Script Reference

| Script | Description |
|--------|-------------|
| `{{baseDir}}/scripts/thinkific.py` | Main CLI — all commands in one tool |

## Credits
Built by [M. Abidi](https://www.linkedin.com/in/mohammad-ali-abidi) | [agxntsix.ai](https://www.agxntsix.ai)
[YouTube](https://youtube.com/@aiwithabidi) | [GitHub](https://github.com/aiwithabidi)
Part of the **AgxntSix Skill Suite** for OpenClaw agents.

📅 **Need help setting up OpenClaw for your business?** [Book a free consultation](https://cal.com/agxntsix/abidi-openclaw)


---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "aiwithabidi",
  "slug": "thinkific",
  "displayName": "Thinkific",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1772859145973,
    "commit": "https://github.com/openclaw/skills/commit/73e10b46cb17ba0db3dcd0bee878df1f14d9d48f"
  },
  "history": []
}

```

### scripts/thinkific.py

```python
#!/usr/bin/env python3
"""Thinkific CLI — Thinkific — manage courses, students, enrollments, coupons, and products via REST API

Zero dependencies beyond Python stdlib.
Built by M. Abidi | agxntsix.ai
"""

import argparse, json, os, sys, urllib.request, urllib.error, urllib.parse

API_BASE = "https://api.thinkific.com/api/public/v1"

def get_env(name):
    val = os.environ.get(name, "")
    if not val:
        env_path = os.path.join(os.environ.get("WORKSPACE", os.path.expanduser("~/.openclaw/workspace")), ".env")
        if os.path.exists(env_path):
            with open(env_path) as f:
                for line in f:
                    line = line.strip()
                    if line.startswith(name + "="):
                        val = line.split("=", 1)[1].strip().strip('"').strip("'")
                        break
    if not val:
        print(f"Error: {name} not set", file=sys.stderr)
        sys.exit(1)
    return val


def get_headers():
    token = get_env("THINKIFIC_API_KEY")
    return {"Authorization": f"Bearer {token}", "Content-Type": "application/json", "Accept": "application/json"}


# Thinkific requires subdomain header


def get_api_base():
    base = API_BASE
    pass
    return base

def req(method, path, data=None, params=None):
    headers = get_headers()
    if path.startswith("http"):
        url = path
    else:
        url = get_api_base() + path
    if params:
        qs = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
        if qs:
            url = f"{url}?{qs}" if "?" not in url else f"{url}&{qs}"
    body = json.dumps(data).encode() if data else None
    r = urllib.request.Request(url, data=body, method=method)
    for k, v in headers.items():
        r.add_header(k, v)
    try:
        resp = urllib.request.urlopen(r, timeout=30)
        raw = resp.read().decode()
        return json.loads(raw) if raw.strip() else {"ok": True}
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
        print(json.dumps({"error": True, "code": e.code, "message": err_body}), file=sys.stderr)
        sys.exit(1)


def try_json(val):
    if val is None:
        return None
    try:
        return json.loads(val)
    except (json.JSONDecodeError, ValueError):
        return val


def out(data, human=False):
    if human and isinstance(data, dict):
        for k, v in data.items():
            print(f"  {k}: {v}")
    elif human and isinstance(data, list):
        for item in data:
            if isinstance(item, dict):
                for k, v in item.items():
                    print(f"  {k}: {v}")
                print()
            else:
                print(item)
    else:
        print(json.dumps(data, indent=2, default=str))


def cmd_courses(args):
    """List courses."""
    path = "/courses"
    params = {}
    if getattr(args, "page", None): params["page"] = args.page
    data = req("GET", path, params=params)
    out(data, getattr(args, "human", False))

def cmd_course_get(args):
    """Get course."""
    path = f"/courses/{args.id}"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_course_create(args):
    """Create course."""
    path = "/courses"
    body = {}
    if getattr(args, "name", None): body["name"] = try_json(args.name)
    if getattr(args, "slug", None): body["slug"] = try_json(args.slug)
    data = req("POST", path, data=body)
    out(data, getattr(args, "human", False))

def cmd_course_update(args):
    """Update course."""
    path = f"/courses/{args.id}"
    body = {}
    if getattr(args, "name", None): body["name"] = try_json(args.name)
    data = req("PUT", path, data=body)
    out(data, getattr(args, "human", False))

def cmd_course_delete(args):
    """Delete course."""
    path = f"/courses/{args.id}"
    data = req("DELETE", path)
    out(data, getattr(args, "human", False))

def cmd_chapters(args):
    """List chapters."""
    path = f"/courses/{args.id}/chapters"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_users(args):
    """List users."""
    path = "/users"
    params = {}
    if getattr(args, "page", None): params["page"] = args.page
    if getattr(args, "query", None): params["query"] = args.query
    data = req("GET", path, params=params)
    out(data, getattr(args, "human", False))

def cmd_user_get(args):
    """Get user."""
    path = f"/users/{args.id}"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_user_create(args):
    """Create user."""
    path = "/users"
    body = {}
    if getattr(args, "email", None): body["email"] = try_json(args.email)
    if getattr(args, "first_name", None): body["first_name"] = try_json(args.first_name)
    if getattr(args, "last_name", None): body["last_name"] = try_json(args.last_name)
    data = req("POST", path, data=body)
    out(data, getattr(args, "human", False))

def cmd_enrollments(args):
    """List enrollments."""
    path = "/enrollments"
    params = {}
    if getattr(args, "course_id", None): params["course_id"] = args.course_id
    if getattr(args, "user_id", None): params["user_id"] = args.user_id
    data = req("GET", path, params=params)
    out(data, getattr(args, "human", False))

def cmd_enroll(args):
    """Create enrollment."""
    path = "/enrollments"
    body = {}
    if getattr(args, "user_id", None): body["user_id"] = try_json(args.user_id)
    if getattr(args, "course_id", None): body["course_id"] = try_json(args.course_id)
    data = req("POST", path, data=body)
    out(data, getattr(args, "human", False))

def cmd_coupons(args):
    """List coupons."""
    path = "/coupons"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_coupon_create(args):
    """Create coupon."""
    path = "/coupons"
    body = {}
    if getattr(args, "code", None): body["code"] = try_json(args.code)
    if getattr(args, "discount_type", None): body["discount_type"] = try_json(args.discount_type)
    if getattr(args, "discount_amount", None): body["discount_amount"] = try_json(args.discount_amount)
    data = req("POST", path, data=body)
    out(data, getattr(args, "human", False))

def cmd_products(args):
    """List products."""
    path = "/products"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_orders(args):
    """List orders."""
    path = "/orders"
    params = {}
    if getattr(args, "page", None): params["page"] = args.page
    data = req("GET", path, params=params)
    out(data, getattr(args, "human", False))

def cmd_groups(args):
    """List groups."""
    path = "/groups"
    data = req("GET", path)
    out(data, getattr(args, "human", False))

def cmd_instructors(args):
    """List instructors."""
    path = "/instructors"
    data = req("GET", path)
    out(data, getattr(args, "human", False))



def main():
    parser = argparse.ArgumentParser(description="Thinkific CLI")
    parser.add_argument("--human", action="store_true", help="Human-readable output")
    sub = parser.add_subparsers(dest="command")

    courses_p = sub.add_parser("courses", help="List courses")
    courses_p.add_argument("--page", help="Page", default=None)
    courses_p.set_defaults(func=cmd_courses)

    course_get_p = sub.add_parser("course-get", help="Get course")
    course_get_p.add_argument("id", help="Course ID")
    course_get_p.set_defaults(func=cmd_course_get)

    course_create_p = sub.add_parser("course-create", help="Create course")
    course_create_p.add_argument("--name", help="Name", default=None)
    course_create_p.add_argument("--slug", help="URL slug", default=None)
    course_create_p.set_defaults(func=cmd_course_create)

    course_update_p = sub.add_parser("course-update", help="Update course")
    course_update_p.add_argument("id", help="ID")
    course_update_p.add_argument("--name", help="Name", default=None)
    course_update_p.set_defaults(func=cmd_course_update)

    course_delete_p = sub.add_parser("course-delete", help="Delete course")
    course_delete_p.add_argument("id", help="ID")
    course_delete_p.set_defaults(func=cmd_course_delete)

    chapters_p = sub.add_parser("chapters", help="List chapters")
    chapters_p.add_argument("id", help="Course ID")
    chapters_p.set_defaults(func=cmd_chapters)

    users_p = sub.add_parser("users", help="List users")
    users_p.add_argument("--page", help="Page", default=None)
    users_p.add_argument("--query", help="Search", default=None)
    users_p.set_defaults(func=cmd_users)

    user_get_p = sub.add_parser("user-get", help="Get user")
    user_get_p.add_argument("id", help="User ID")
    user_get_p.set_defaults(func=cmd_user_get)

    user_create_p = sub.add_parser("user-create", help="Create user")
    user_create_p.add_argument("--email", help="Email", default=None)
    user_create_p.add_argument("--first_name", help="First name", default=None)
    user_create_p.add_argument("--last_name", help="Last name", default=None)
    user_create_p.set_defaults(func=cmd_user_create)

    enrollments_p = sub.add_parser("enrollments", help="List enrollments")
    enrollments_p.add_argument("--course_id", help="Course ID", default=None)
    enrollments_p.add_argument("--user_id", help="User ID", default=None)
    enrollments_p.set_defaults(func=cmd_enrollments)

    enroll_p = sub.add_parser("enroll", help="Create enrollment")
    enroll_p.add_argument("--user_id", help="User ID", default=None)
    enroll_p.add_argument("--course_id", help="Course ID", default=None)
    enroll_p.set_defaults(func=cmd_enroll)

    coupons_p = sub.add_parser("coupons", help="List coupons")
    coupons_p.set_defaults(func=cmd_coupons)

    coupon_create_p = sub.add_parser("coupon-create", help="Create coupon")
    coupon_create_p.add_argument("--code", help="Code", default=None)
    coupon_create_p.add_argument("--discount_type", help="Type", default=None)
    coupon_create_p.add_argument("--discount_amount", help="Amount", default=None)
    coupon_create_p.set_defaults(func=cmd_coupon_create)

    products_p = sub.add_parser("products", help="List products")
    products_p.set_defaults(func=cmd_products)

    orders_p = sub.add_parser("orders", help="List orders")
    orders_p.add_argument("--page", help="Page", default=None)
    orders_p.set_defaults(func=cmd_orders)

    groups_p = sub.add_parser("groups", help="List groups")
    groups_p.set_defaults(func=cmd_groups)

    instructors_p = sub.add_parser("instructors", help="List instructors")
    instructors_p.set_defaults(func=cmd_instructors)


    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        sys.exit(1)
    args.func(args)


if __name__ == "__main__":
    main()

```