Enrolments and student progress

"Who's enrolled, and how far have they got?" is the question most schools eventually want answered in a spreadsheet, a CRM or a dashboard. For most schools, one request answers both halves.

Who's enrolled, and how far they've got

GET /v1/courses/{course_id}/enrollments

This lists the students enrolled in a course, a page at a time. Each entry has the student's ID and their overall percent_complete, along with when they enrolled and, if they have, when they finished.

With optio-teachable:

const { enrollments } = await teachable.v1.courses.getEnrolments(3033045);

enrollments[0].user_id;            // who
enrollments[0].percent_complete;   // how far, 0 to 100

const { users } = await teachable.v1.users.getByEmail('student@example.com');
const student   = users[0];

getByEmail is handy on its own: most requests start with an email address from somewhere else, and this turns it into a Teachable user. It returns a list, so take the first match.

Putting them together

A whole course, every page of students, in a list ready for a spreadsheet:

async function courseProgress(courseId) {
  const rows = [];
  let page = 1, pages = 1;

  do {
    const { enrollments, meta } = await teachable.v1.courses.getEnrolments(courseId, page, 100);

    for (const e of enrollments) {
      rows.push({ user: e.user_id, percent: e.percent_complete ?? 0 });
    }

    pages = meta?.number_of_pages ?? 1;
    page++;
  } while (page <= pages);

  return rows;
}

The paging is the same loop as the last lesson. That's one request per 100 students, so even a large course takes a handful of requests.

Lesson by lesson

GET /v1/courses/{course_id}/progress?user_id={user_id}

When you need more than the percentage, this gives one student's progress lesson by lesson: every section, and whether each lesson in it is complete.

const { course_progress } = await teachable.v1.courses.getProgress(3033045, student.id);

course_progress.percent_complete;   // 0 to 100
course_progress.lecture_sections;   // each section, with is_completed on every lesson

That's one request per student, so save it for the students you actually need detail on, not the whole course. Asking for every student at once is how a script runs into the rate limit in the next lesson.

Enrolling someone

V1 can enrol a student too:

POST /v1/enroll

It's how you'd give access after a purchase made somewhere other than Teachable. optio-teachable only reads, so this one is a plain request, sent the same way as the one in lesson 2. In V2, enrolments can be created, updated and removed, with the right scopes.

Webhooks first, API second

For keeping something up to date, don't poll every student every hour. Let Enrollment.created and LectureProgress.created webhooks tell you what changed, and use these requests to fill in detail and catch up once a day.

In short

  • /courses/{id}/enrollments lists who's in a course, with each student's percent_complete.
  • /courses/{id}/progress?user_id= adds lesson-by-lesson detail, one student at a time.
  • Use the enrolment list for the big picture, and let webhooks do the day-to-day work.
Complete and Continue