EMPO Academy Docs
API quickstart

API & MCP · Endpoints

REST endpoints

Base URL: https://academy.empomm.com. All endpoints require Authorization: Bearer empo_…. Errors return JSON { error: { code, message, field? } } with the appropriate HTTP status. New authoring mutations are REST/CLI-only; MCP exposes a selected documented subset, not every endpoint.

MethodPathScopeDescription
GET/api/v1/meanyReturn the user that owns the bearer token + the key's scopes.
GET/api/v1/coursescourses:readList courses. Admins see all; instructors see own + collab. Supports `?limit=50` (max 100).
POST/api/v1/coursescourses:writeCreate a draft course. Body: { title, subtitle?, description?, categoryId?, price?, language? }.
GET/api/v1/courses/{courseId}courses:readGet a course with modules and complete reconciliation lesson snapshots (metadata, assignment prompt, timestamps).
PATCH/api/v1/courses/{courseId}courses:writeUpdate fields incl. learningOutcomes, requirements, targetAudience, materialsIncluded (null/[] clears; strings max 200). REST/CLI-only.
POST/api/v1/courses/{courseId}/statuscourses:writeTransition course status. Body: { status: 'DRAFT' | 'PUBLISHED' | 'PRIVATE' | 'ARCHIVED' }.
POST/api/v1/courses/{courseId}/modulescourses:writeAppend a new module. Body: { title }.
POST/api/v1/modules/{moduleId}/lessonscourses:writeAppend {title,contentType?,videoUrl?,content?,assignmentPrompt?,duration?,isFree?}; ASSIGNMENT needs assignmentPrompt; returns block/hash.
PATCH/api/v1/modules/{moduleId}courses:writeRename a module. Body: { title }. REST/CLI-only.
PATCH/api/v1/lessons/{lessonId}courses:writeUpdate title/contentType/duration/videoUrl; duration is seconds (null or 1..2,147,483,647), URL null or http(s) <=191. REST/CLI-only.
GET/api/v1/lessons/{lessonId}/blockscourses:readList reachable nested blocks + hashes and totalBlockCount for every block row belonging to the lesson, including malformed orphans.
POST/api/v1/lessons/{lessonId}/blockscourses:writeAppend a single block to a lesson. Sanitised at storage.
PATCH/api/v1/lessons/{lessonId}/blocks/{blockId}courses:writeCompare-and-update a block. Body: { expectedContentHash: sha256:<64 lowercase hex>, content: object }. REST/CLI-only.
DELETE/api/v1/lessons/{lessonId}/blocks/{blockId}courses:writeCompare-and-delete a block. Body: { expectedContentHash: sha256:<64 lowercase hex>, cascade?: boolean }. Returns 409 BLOCK_HAS_CHILDREN without cascade or BLOCK_INTEGRITY_CONFLICT for a cross-lesson descendant. REST/CLI-only.
POST/api/v1/lessons/{lessonId}/blocks/from-markdowncourses:writeBulk-create blocks from a Markdown body. Body: { markdown, replace? }. 250 KB cap; replace=true is atomic and returns 409 BLOCK_INTEGRITY_CONFLICT if existing descendants span lessons.
POST/api/v1/scaffold-coursecourses:writeOne-shot scaffold: course + modules + lessons + markdown blocks. 1 MB cap.
POST/api/v1/certificates/externalcertificates:issueIssue a single EMPO T&C certificate. Admin-only. Body: holder info + trainingTitle + trainingDate + deliveryFormat.
POST/api/v1/certificates/external/batchcertificates:issueBulk issue T&C certs (up to 200 holders). Admin-only. Body: { trainingTitle, trainingDate, deliveryFormat, holders[] }.
POST/api/v1/certificates/{certId}/revokecertificates:manageRevoke a certificate. Body: { reason }. Reason is shown on the public verification page.
POST/api/v1/certificates/{certId}/resend-emailcertificates:manageResend the certificate notification email to the holder.
GET/api/v1/notifications/templates(admin)List all notification email templates with their current overrides.
PATCH/api/v1/notifications/templates/{type}templates:writeOverride one template. Body: { subject?, greeting?, ctaLabel?, footer?, enabled? }.

Error format

{
  "error": {
    "code": "INVALID_INPUT",
    "message": "trainingDate is required",
    "field": "trainingDate"
  }
}

Common codes: UNAUTHORIZED (401), FORBIDDEN (403), MISSING_SCOPE (403), NOT_FOUND (404), INVALID_INPUT (400), RATE_LIMITED (429), CONTENT_CONFLICT, BLOCK_HAS_CHILDREN, BLOCK_INTEGRITY_CONFLICT (409), PAYLOAD_TOO_LARGE (413), INTERNAL_ERROR (500).

Rate limits

60 requests/minute per API key, 120/minute per client IP. The stricter verdict wins. Every response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers; 429s also set Retry-After. Need more headroom for a legitimate integration? Email academy@empomm.com before pointing prod traffic.

Looking for the MCP version?

MCP wraps a selected documented subset; the new authoring mutations are REST/CLI-only. See the MCP setup guide.