Courses
A course is an online course in your workspace: its lessons, its look, its settings, and its certificate template. The Courses API lives at /players. It lists and creates courses, reads and updates one course, and publishes or unpublishes it. Lessons, header links, the certificate, the paywall, comments, and analytics have their own routes under the course.
Overview
A course created through the API starts as draft. List Courses returns the Course Object. Create, publish, and unpublish return that object plus preview_url. Update Course also returns draft_version and draft_updated_at. Get Course returns the Course Detail.
Lessons are the ordered curriculum. Header links are the items in the course header. The certificate is the template printed when a learner earns a certificate. The paywall links existing Easycart products to the course. Comments are the live question threads. Analytics are the live numbers for students, visits, lesson completion, and certificates. Lessons and header links are separate collections, each with its own ids. A paywall row is addressed by the Easycart product UUID. A comment id is the comment UUID.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /players | Create a course |
| GET | /players | List courses |
| GET | /players/{id} | Get a course |
| PATCH | /players/{id} | Update a course |
| PATCH | /players/{id}/styles | Update styles |
| PATCH | /players/{id}/layout | Update layout |
| PATCH | /players/{id}/settings | Update settings |
| POST | /players/{id}/publish | Publish a course |
| POST | /players/{id}/unpublish | Unpublish a course |
Lesson routes are under Lessons. Header-link routes are under Header links. Certificate routes are under Certificate. Paywall routes are under Paywall. Comment routes are under Comments. Analytics routes are under Analytics. HTTP paths stay /players/{id}/files, /players/{id}/header-links, /players/{id}/certificate, /players/{id}/products, /players/{id}/comments, /players/{id}/files/comment-counts, /players/{playerId}/files/{fileId}/comments, and /players/{id}/analytics.
Course Object
Every field is present. A missing timestamp is null, not an omitted key.
| Field | Type | Description |
|---|---|---|
id | string | Immutable identifier (UUID) |
slug | string | URL label. It can change. The API does not address a course by its slug. |
name | string | Display name. Maximum 255 characters. Always the live name. |
status | string | draft or published |
created_at | string|null | ISO 8601 timestamp (UTC) |
updated_at | string|null | ISO 8601 timestamp (UTC) |
published_at | string|null | When the course was published, or null while it is a draft |
has_draft | boolean | true when a published course has saved edits that publish has not applied yet. false after publish or unpublish. See Draft, preview, and publish. |
Course Detail
Get Course returns the Course Object plus the fields below. Every include key is present.
| Field | Type | Description |
|---|---|---|
public_url | string | Live visitor URL, https://app.easy.tools/players/{slug}. Returned for a draft and for a published course. |
preview_url | string | Signed preview link, minted for this response and lasting one hour. See Preview. |
draft_version | integer | Current draft counter. See Draft version. |
draft_updated_at | string|null | When the draft was last saved. On a course with status draft, this is the course's updated_at. On a published course it is when the pending edits were last saved, or null when there are none, including right after publish. |
styles | object|null | Styles Object when include contains styles, otherwise null |
layout | object|null | Layout Object when include contains layout, otherwise null |
settings | object|null | Settings Object when include contains settings, otherwise null |
files | array|null | Every Lesson Object, in order, when include contains files. Otherwise null. This is the whole curriculum, not a page. An empty curriculum is []. |
header_links | array|null | Every Header Link Object, in order, when include contains header_links. Otherwise null. This is the whole collection, not a page. An empty collection is []. |
certificate | object|null | Certificate Object when include contains certificate, otherwise null. When no template has been saved, these are the starting values. |
products | array|null | Every Product Link Object when include contains products. Otherwise null. This is the live paywall, the whole collection, not a page. Zero links is []. |
Draft, preview, and publish
Updates to the styles, layout, settings, and certificate, and creates, updates, deletes, and reorders of lessons and header links, are draft writes. What they change depends on status:
draftcourse. Visitors cannot see the course, so a write changes the course itself. Nothing is left pending:has_draftisfalseafter the write, anddraft_updated_atis the course'supdated_at. A deleted lesson or header link is gone for good.publishedcourse. A write saves pending edits. Visitors keep seeing the live course until you publish.has_draftistruewhile pending edits exist. A deleted lesson or header link stays on the live course until you publish.
Reads of the course, its certificate template, its lessons, and its header links include pending edits when there are any. Some changes are never pending: a rename with Update Course and every paywall write change the live course immediately, and publish does not change them. Comment and analytics reads are always the live course.
Publish Course makes the course public, then applies the pending edits. Unpublish Course makes the course private at once, then folds the pending edits into the course. After either call has_draft is false.
Draft version
Every draft write sends draft_version, the integer from Get Course. It is 1 before anything has been saved. A successful write returns the next integer and draft_updated_at. Omitting draft_version, or sending a value below 1, is 422. A value that is not the current counter is 409:
{
"error": {
"code": "CONFLICT",
"message": "The draft was edited elsewhere. GET the draft and retry with the current draft_version."
}
}
Read the course and retry with the draft_version from that response.
Create Course, Publish Course, and Unpublish Course do not send draft_version and do not return it. Publish and unpublish never reset the counter, but each increases it by 1 when it applies pending edits. A write that still sends the value read before that call gets 409, so read the course again after publish or unpublish before the next draft write. Paywall writes do not use draft_version.
Preview
preview_url is a signed link to the course as it would look with its pending edits. It is minted for that response, is not saved, and lasts one hour. Get Course, create, each draft write that returns a body, publish, and unpublish each mint a new one. Paywall writes do not. On one object, preview_url is a field of data. A reorder returns a list, so preview_url, draft_version, and draft_updated_at sit beside data. Delete is 204 with no body.
The link has the shape https://app.easy.tools/players/{id}/preview?expires={unix time}&signature={signature}, where {id} is the course UUID. It uses the same origin as public_url.
public_url is the live visitor link, https://app.easy.tools/players/{slug}. Only Get Course returns it, for a draft and for a published course.
List Courses, the lesson and header-link lists and gets, the paywall list, and the comment and analytics reads have no preview_url.
Include
include on Get Course is a comma-separated list of styles, layout, settings, files, header_links, certificate, and products. Spaces around a token are ignored. A token outside that list returns 422. Leave a token out and that key is null. Requested products is the live paywall, the whole collection, not a page.
Identifiers
A course id, a lesson id, a header link id, and a comment id are UUIDs. The course id is {id} or {playerId} in the path. A lesson or header link id is the {id} beside it. On a lesson thread, the lesson id is {fileId}. A paywall {productId} is an Easycart product UUID. A student id is 32 hexadecimal characters, and a certificate id is a short URL-safe id; neither is a UUID. See Paywall, Comments, and Analytics.
A course id that is missing or not a UUID returns 404. A lesson or header link id that is missing, that is not on that course, or that is not a UUID returns 404. On a comment read, a heading or a lesson that is not on the live course returns 404. A file filter that is not a UUID returns 422. A product that Easycart resolves and that is not linked to the course returns 404 on update and disconnect. A product UUID that Easycart does not resolve returns 422. The 404 body is:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found"
}
}
Updates
Update Course and the styles, layout, settings, and certificate updates change only the keys you send. A nested object, such as layout.header or certificate.content, merges the same way. Omit a key to leave the stored value. These are draft writes and do not publish. name is the exception: a rename changes the live course immediately, also on a published course.
Create Course accepts the same section objects. { "name" } alone still creates a draft and keeps the starting values. Sections on that request are saved on the new course, which is a draft and not visible to visitors. Read a section from Get Course or from that section's route.
Do not send files, header_links, or products on create or update. Lessons, header links, and the paywall have their own routes.
Where a field can be cleared, the object table says null clears it. On every other field, null returns 422.
Styles Object
Colors and the theme are strings. A new course starts with the values in the table.
| Field | Type | Description |
|---|---|---|
theme | string | Theme name. Starts as light. |
font_family | string | Font name. Starts as Inter. |
main_color | string | Accent color. Starts as #F6083D. |
background_color | string | Page background. Starts as #f9fafb. |
text_color | string | Body text color. Starts as #0e0f11. |
header_color | string | Header color. Starts as #fff. |
heading_color | string | Heading color. Starts as #3c404b. |
border_color | string | Border color. Starts as #e0e3e6. |
Layout Object
Navigation labels start in the account's language. The English starting values are in the table. page_title and icon_url start the same for every account.
| Field | Type | Description |
|---|---|---|
page_title | string | Browser title. Starts as Easyplayer. |
icon_url | string | Favicon address. Starts as /assets/easyplayer.png. |
sidebar_title | string | Sidebar heading. On an English account, starts as Files list:. |
previous_label | string | Previous control. On an English account, starts as Previous file. |
next_label | string | Next control. On an English account, starts as Next file. |
mark_done_label | string | Mark-done control. On an English account, starts as Mark as done. |
finished_label | string | Finished label. On an English account, starts as Lesson finished. |
header | object | Header Object |
footer | object | Footer Object |
Header Object
| Field | Type | Description |
|---|---|---|
is_visible | boolean | Whether the header is shown. Starts true. |
logo_url | string | Logo address. Starts as /assets/easyplayer.png. |
Footer Object
| Field | Type | Description |
|---|---|---|
is_visible | boolean | Whether the footer is shown. Starts true. |
text | string | Footer text. Starts as "". |
policy_url | string | Privacy policy address. Starts as /policy. |
terms_url | string | Terms address. Starts as /terms. |
Settings Object
| Field | Type | Description |
|---|---|---|
release_at | string|null | When the course opens, as an ISO 8601 timestamp with a Z suffix. null means it is open, and null clears a stored time. Starts null. |
before_release_url | string|null | Address used before release_at. null clears it. Starts null. |
webhook_url | string | Where course webhooks are sent. A public https URL, at most 1024 characters. "" clears it. Any other value, including null, an http URL, or a private or link-local address, returns 422. Starts as "". |
webhooks | object | Webhooks Object |
Webhooks Object
Each flag turns on one webhook. Omit a flag to leave it unchanged.
| Field | Type | Description |
|---|---|---|
lesson_finished | boolean | A learner marked a lesson finished. Starts true. |
lesson_unfinished | boolean | A learner cleared that mark. Starts true. |
lesson_viewed | boolean | A learner opened a lesson. Starts false. |
certificate_issued | boolean | A certificate was issued. Starts false. |
course_finished | boolean | A learner finished the course. Starts false. |
Status
| Status | Meaning |
|---|---|
draft | Not published. published_at is null. |
published | Published. published_at is the publish time. |