docs
/
AppEngine API

Content Studio

Posts taken page by page — courses, applications, trainings, documentation — with outline, access, progress, answers, marking and review.

ContentStudioModule serves posts that people take page by page. Two surfaces: /client/content-studio/* for the person taking a post, and /content-studio/* for staff. The admin screen is Content Studio; on a site, the Content Player is its client.

Datatypes: post (the content), post_progress (one per person per post), form_submission (answers sent in).

The post

A post's pages live inside the record: pages[] (id, title, summary, content HTML, type, duration, reviewers, conditions, answerKey) and an outline, toc[], of chapters holding pages. Access follows the same rules as a CRM form: accessMode (open · code · participants), accessCode, authenticationType (none · magic-link · code · password · email), startDate / endDate, participants. course holds dueInDays, layout (sidebar · steps) and navigation (sidebar · top · none).

Nothing about a person is worked out ahead of time: which items are open, why one is locked, % complete, chapter counts, step numbers and what comes next are computed on read from the post and the person's progress.

Conditions

A page or chapter opens when every condition holds. Each points at an earlier item:

checkHolds when
donethe page (or every page in the chapter) is done
approveda reviewer approved it
scoreits score is at least min (0–100)
watchedat least min % of its video or audio was watched
percentthe chapter is at least min % complete

A chapter's conditions apply to everything in it.

Customer routes

GET/client/content-studio/mineJWT
GET/client/content-studio/:courseJWT
POST/client/content-studio/:course/enrollJWT
GET/client/content-studio/:course/items/:itemIdJWT
POST/client/content-studio/:course/items/:itemId/progressJWT
POST/client/content-studio/:course/items/:itemId/answerJWT
GET/client/content-studio/:course/preview?key=JWT

:course is the post's id or slug; every route takes an optional ?code= (access code or invitation). The caller is the customer (or signed-in user) on the request — the site's app token alone is no one.

  • Only the published copy is served. Unpublished edits never reach these routes.
  • Reading needs no one. The outline and any unlocked item answer without a signed-in person; with one, they include that person's progress and saved answer.
  • Saving needs a person and an enrollment. progress and answer answer 403 Enroll in this course first until enroll has run.
  • The outline returns course (title, summary, cover, layout, navigation, steps), enrollment, items[] — each with status (open · locked · done · pending-review · changes), lockReason, type, duration, step, and for chapters done / total — and next.
  • preview?key= is the author's preview, opened by the site with its app token alone: it answers only with a live key from POST /content-studio/:course/preview-key, good for that post in that org for one hour.
  • progress takes { done, percent, score }; percent only ever goes up.
  • answer takes { values, done }. The answer is kept in one form_submission per item, owned by the progress record, and rewritten on each save; done: false keeps a draft. With an answerKey, the answer is marked 0–100. A page with reviewers then waits for review instead of finishing; an approved page can no longer be changed.

Staff routes

GET/content-studio/reviewJWT
GET/content-studio/:course/reviewJWT
GET/content-studio/enrollments/:idJWT
POST/content-studio/enrollments/:id/review/:itemIdJWT
POST/content-studio/enrollments/:id/withdrawJWT
GET/content-studio/:course/previewJWT
POST/content-studio/:course/preview-keyJWT
POST/content-studio/:course/enrollmentsJWT
GET/content-studio/:course/enrollmentsJWT

All are @StaffOnly() — a signed-in business user, never the site's app token (see how a request is authorized). They read the working draft, not the published copy.

  • Review queue — everything waiting for a reviewer, oldest first, with the person and what they sent (answer). review/:itemId takes { status: approved | changes, note }; only the page's reviewers, or admins, may decide.
  • Enrollments — POST takes { emails }; someone without an account is matched by email when they sign in. GET is the roster, with status, search, page and pageSize.
  • Preview — the post with every item open and nothing recorded; each item carries opensWhen, the condition a learner would wait on. preview-key returns { key, expiresAt }, a one-hour key the site's /__preview/post/<post>?key= opens it with.

post_progress

Field
postIdThe post's sk
statusactive · completed · withdrawn
enrolledAt, dueAt, completedAtdueAt from the post's dueInDays
progressKeyed by outline item: done, at, score, percent, answerRef, review (status pending · approved · changes, by, at, note)

The person is the record's owner. It moves to completed when every page is done.