REST API and App
Learnomy ships a full REST API that powers both the mobile/web app and third-party integrations. Everything students and instructors can do through the browser is also accessible through the API.
Overview
The REST API is built on top of the WordPress REST API framework. All Learnomy endpoints are registered under the namespace learnomy/v1, making them available at:
https://yoursite.com/wp-json/learnomy/v1/
The API is what the Learnomy mobile app and web app use to communicate with your site. If you are building an integration, a custom mobile app, or connecting Learnomy to another system, the REST API is the right layer to work with.
What the API Covers
The API provides endpoints for every major data type in Learnomy:
- Courses, sections, lessons, and topics
- Quizzes, questions, and quiz attempts
- Enrollments and student progress
- Certificates
- Reviews
- Memberships and subscriptions
- Coupons
- Earnings, commissions, and withdrawals
- Notifications and announcements
- Webhooks
- Admin operations (student management, settings, revenue reports)
Learnomy Pro adds its own endpoints under the learnomy-pro/v1 namespace for Pro-only features such as Cohorts, Learning Paths, Spaces, Assignments, and Membership Analytics.
What a learner can read about themselves
Two additions in 1.9.2 close the gap between what the website shows a student and what an app could retrieve:
GET /account/quiz-attempts- the caller's own attempt history, paginated (per_page,offset), each row carrying its quiz and course title. This is the same list the website shows at/account/quiz-results/, read from the same source, so the two cannot drift. A row opens into the existingGET /attempts/{id}for the full review. The user is always taken from the session, never from the request.GET /courses/{id}/lessonsnow returns aquizzes[]array on every lesson, always present even when empty. Each quiz carries aviewerblock (is_locked,lock_reason,unlocks_at,lock_message) resolved on the server, exactly like the lesson's own.
That last point matters for anyone building a client: do not evaluate lock
rules yourself. Whether a lesson or its quiz is open can depend on drip mode,
a per-student unlock, a section date, course-level sequencing, or the previous
lesson's completion requirement, in any combination a site owner chooses - and
a rule added in a later release would render as "open" in an app already on
someone's phone. Ask the server and render its answer, including its
lock_message, which is already translated into the site's language.
Two things that changed in 1.9.4
Bulk course actions take one request, not one per course. POST /courses/bulk
accepts an action plus a list of ids, and the action may be either a verb
(publish, archive, trash, restore, draft, delete) or a canonical
status (published, archived, trashed, draft). Both forms are accepted so
existing callers keep working; verbs are normalised server-side. restore
deliberately returns a course to draft rather than republishing it, so
bringing something back from the trash never silently makes it public again.
Some routes only exist when their integration is present. The H5P completion
route and the WP Fusion rule routes register only when their companion plugin is
active, and return 404 otherwise. They are marked CONDITIONAL ROUTE in the
generated catalogue, with the condition stated in the description. If you build a
client against the spec, treat those as optional rather than assuming a 404 means
a broken install.
Authentication
The API accepts four kinds of credential. They are resolved in order and an already-authenticated session is never overridden (includes/api/class-api-key-auth.php:28-56).
| Method | Use it for |
|---|---|
| Cookie + nonce | Same-site requests from your own theme or admin screens |
| Application Passwords | The companion mobile app |
| API key | Server-to-server integrations and scripts |
| JWT | Headless front ends and custom integrations |
Application Passwords (what the app uses)
The Learnomy companion app signs in with WordPress core Application Passwords, not with Learnomy's JWT. This is deliberate and is the same across the Wbcom portfolio: the app relies on a core credential a site owner can see and revoke from the user's profile screen. Nothing in Learnomy needs enabling for it.
API keys
Long-lived keys for server-to-server integrations ship today. Create one with POST /wp-json/learnomy/v1/auth/api-keys, list with GET, and revoke with DELETE /wp-json/learnomy/v1/auth/api-keys/{key_id}. Send the key in the X-API-Key header; a key in a query parameter is ignored. There is no wp-admin screen for keys - the API Keys settings section was removed in 1.5.0 and keys are managed over REST. See API Keys.
JWT (obtaining a token)
Send a POST request to /wp-json/learnomy/v1/auth/token with a valid WordPress username and password. The response returns an access token and a refresh token.
Pass the access token in the Authorization header on every authenticated request:
Authorization: Bearer <token>
Tokens expire and can be refreshed using the /auth/token/refresh endpoint.
Response Shape
Every API response uses a consistent envelope format:
- List responses:
{ "data": [...], "meta": { "total": N, "cursor_next": "..." } } - Single resource:
{ "data": { ... } } - Errors:
{ "error": { "code": "...", "message": "...", "details": [...], "request_id": "..." } }
This shape is the same whether you are calling a Free endpoint or a Pro endpoint.
Rate Limiting
Learnomy applies rate limiting to write endpoints flagged as sensitive (login, password reset, quiz start, coupon validation, and others). The default limit is 60 requests per minute per user. Administrators and instructors are exempt from the rate limit. You can adjust this in Learnomy Settings > API and Performance.
Where to Find More
This page is a conceptual overview. The full endpoint reference is part of the Developer Guide:
- Full endpoint reference:
docs/api/openapi.jsonin the plugin repository - an OpenAPI 3 document generated from the live route table, covering both namespaces. At 1.9.4 it describes 396 paths (learnomy/v1217,learnomy-pro/v1179) across 510 operations. This is the reference to build a client against; it is the same document the mobile app generates from. - Curated inventory with per-endpoint purpose notes:
audit/manifest.json(222 entries). Free registers 226register_rest_routecall sites underincludes/api/- the three counts differ legitimately, because one call site can register several methods on one path, and the curated list is a documented subset rather than a total. - CAPABILITIES.md is the buyer-facing source of truth for what those endpoints add up to.
- Code: REST controllers live in
includes/api/in the Free plugin andincludes/extensions/*/rest/in Learnomy Pro.
Tips
- The REST API is always available as long as the plugin is active. There is no separate toggle.
- All endpoints use
permission_callbackfor access control. Anonymous requests to protected endpoints receive a 401 response. There are no unprotected write endpoints. - ETag headers on read endpoints allow your HTTP client to use conditional requests and avoid re-downloading unchanged data.
- Application Passwords are the companion app's primary credential, not a fallback for JWT. Both work; pick per the table in Authentication above.
Developer hooks (1.2.0)
Learnomy 1.2.0 added two groups of extension points so integrations can ride the existing editors and the WordPress privacy pipeline without forking templates or touching lrn_* tables. Every hook below was confirmed in the plugin source.
Editor field seams
Courses, lessons, and quizzes are custom-table rows, not custom post types, so there is no meta-box path for third-party fields. These seams give integrations one render hook per editor plus one save action per entity. Render your inputs on the *_editor_fields action, then read $request and persist your own user meta or option storage on the matching *_saved action. Account fields work the same way across the registration forms and the account-details page.
This is what the WP Fusion bridge (Pro) and the built-in custom account fields feature build on. Learnomy's own Custom_Fields service is a first-party consumer of the account-field hooks, so admin-defined fields and developer-registered fields render through the same seam.
| Hook | Type | Fires | Signature | Purpose |
|---|---|---|---|---|
learnomy_course_editor_fields |
action | Course editor sidebar (includes/admin/views/course-form.php:562) |
( object|null $course ) — null on create |
Render extra inputs in the course editor. |
learnomy_course_saved |
action | Course create/update REST save (includes/api/class-courses-controller.php:552) |
( int $course_id, WP_REST_Request $request ) |
Persist integration course fields after a course is saved. |
learnomy_lesson_editor_fields |
action | Lesson editor (includes/admin/views/lesson-editor.php:643) |
( object|null $lesson ) — null on create; covers sub-lessons |
Render extra inputs in the lesson editor. |
learnomy_lesson_saved |
action | Lesson create/update REST save (includes/api/class-lessons-controller.php:513, :558, :622) |
( int $lesson_id, WP_REST_Request $request ) |
Persist integration lesson fields after a lesson is saved. |
learnomy_quiz_editor_fields |
action | Quiz editor (includes/admin/views/quiz-editor.php:744) |
( object|null $quiz ) — null on create |
Render extra inputs in the quiz editor. |
learnomy_quiz_saved |
action | Quiz create/update REST save (includes/api/class-quizzes-controller.php:781, :820) |
( int $quiz_id, WP_REST_Request $request ) |
Persist integration quiz fields after a quiz is saved. |
learnomy_account_fields |
action | Registration forms and account-details page (templates/register-student.php:166, templates/register-instructor.php:261, templates/account-details.php:160) |
( int $user_id ) — 0 at registration |
Render extra account/profile inputs. |
learnomy_account_fields_save |
action | Registration save and account-update REST save (includes/api/class-auth-controller.php:601, :757) |
( int $user_id, WP_REST_Request $request ) |
Persist account fields on signup and on later edits. |
The render hooks pass the entity being edited (or null / 0 when the row does not exist yet). Field storage stays in the consumer plugin; Learnomy only exposes the surface. A minimal round-trip:
// Render an input in the course editor.
add_action( 'learnomy_course_editor_fields', function ( $course ) {
$value = $course ? get_post_meta( (int) $course->id, 'crm_tag', true ) : '';
printf( '<input name="crm_tag" value="%s">', esc_attr( $value ) );
} );
// Persist it when the course is saved.
add_action( 'learnomy_course_saved', function ( $course_id, $request ) {
update_post_meta( $course_id, 'crm_tag', sanitize_text_field( (string) $request->get_param( 'crm_tag' ) ) );
}, 10, 2 );
Privacy and anonymisation
Three additive filters let an extension hide individual learner identity (name, email, avatar) from the instructor roster while keeping counts and metrics intact. Each filter receives an $is_admin flag (or the admin scope), so a consumer can keep manage_options users at full visibility and only mask for instructors. These were added so the BTC Instructor_Privacy integration survives plugin updates. None changes behaviour unless something hooks it.
| Hook | Type | Fires | Signature | Purpose |
|---|---|---|---|---|
learnomy_instructor_students_list |
filter | Instructor roster list (includes/services/view-data/class-instructor-students-view-data.php:131) |
( array $students, array|null $scope_course_ids, bool $is_admin ) |
Anonymise rows in the instructor's student list. |
learnomy_instructor_student_detail |
filter | Instructor student-detail view (includes/services/view-data/class-instructor-students-view-data.php:219) |
( array $student, bool $is_admin ) |
Anonymise a single student-detail record. |
learnomy_course_students_rest_items |
filter | GET /courses/{id}/students REST response (includes/api/class-enrollments-controller.php:521) |
( array $items, int $course_id, int $user_id ) |
Anonymise course-student rows returned by the API. |
The $is_admin argument is true when the caller has full visibility (no course scope), so consumers can short-circuit and never mask for administrators.