REST API Reference
LearnDash Dashboard exposes two REST API namespaces:
ldd_report/v1— User statisticsld-dashboard/v2— Reports, charts, and private messaging
Base URL: {site_url}/wp-json/
Authentication
All endpoints require a logged-in WordPress session. Use cookie-based authentication with the X-WP-Nonce header.
// Get nonce from localized data
const nonce = ldReportData.nonce; // or ld_dashboard_public_js.nonce
fetch( '/wp-json/ld-dashboard/v2/reports', {
headers: {
'X-WP-Nonce': nonce,
'Content-Type': 'application/json',
},
credentials: 'same-origin',
} );
The nonce is generated with wp<em>create</em>nonce( 'wp_rest' ).
Messaging Endpoints
Namespace: ld-dashboard/v2
Messaging requires the enable-private-messaging setting to be enabled. All messaging endpoints require the user to be logged in.
GET /messages
Retrieve conversations (inbox) for the current user.
Permission: Logged in
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
per_page | int | 20 | Conversations per page (max 50) |
Response:
{
"conversations": [
{
"thread_id": 42,
"subject": "Question about Lesson 3",
"course_id": 100,
"course_name": "Introduction to PHP",
"other_user": {
"id": 5,
"name": "Jane Smith",
"avatar": "https://example.com/avatar.jpg"
},
"last_message": "Thanks for the clarification!",
"last_message_at": "2026-02-20 14:32:00",
"unread_count": 2
}
],
"total": 15,
"pages": 1
}
POST /messages
Send a new message or reply to an existing thread.
Permission: Logged in
Body parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_id | int | Yes | WordPress user ID of the recipient |
course_id | int | Yes | Course ID for context (required for new threads) |
subject | string | Yes* | Subject line (*required for new threads only) |
message | string | Yes | Message body (HTML allowed via wp<em>kses</em>post) |
parent_id | int | No | Thread ID to reply to (omit for new conversations) |
Messaging rules:
- Admins can message anyone.
- Instructors can message students enrolled in their courses and co-instructors.
- Group leaders can message members of their groups.
- Students can only message instructors of their enrolled courses.
Response (201):
{
"id": 57,
"message": "Message sent successfully.",
"thread_id": 42
}
Error (403):
{
"code": "rest_forbidden",
"message": "You cannot message this user. You must share a course relationship.",
"data": { "status": 403 }
}
GET /messages/{id}
Get all messages in a thread.
Permission: Must be the sender or recipient of the thread
URL parameters:
| Parameter | Type | Description |
|---|---|---|
id | int | Thread ID (root message ID) |
Response:
{
"thread_id": 42,
"subject": "Question about Lesson 3",
"course_id": 100,
"course_name": "Introduction to PHP",
"other_user": {
"id": 5,
"name": "Jane Smith",
"avatar": "https://example.com/avatar.jpg"
},
"messages": [
{
"id": 42,
"sender_id": 5,
"sender": {
"id": 5,
"name": "Jane Smith",
"avatar": "https://example.com/avatar.jpg"
},
"message": "<p>Hi, I have a question...</p>",
"read_at": null,
"created_at": "2026-02-20 12:00:00",
"is_mine": false
}
]
}
DELETE /messages/{id}
Soft-delete a thread for the current user.
Permission: Must be the sender or recipient
Deletion is user-specific: the other participant still sees the conversation. The row is permanently deleted only when both users have deleted it.
Response (200):
{
"deleted": true
}
PUT /messages/{id}/read
Mark all messages in a thread as read for the current user.
Permission: Must be the sender or recipient
Response (200):
{
"success": true
}
GET /messages/unread-count
Get the total unread message count for the current user.
Permission: Logged in
Response:
{
"count": 3
}
GET /messages/recipients
Get eligible message recipients for the current user.
Permission: Logged in
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
course_id | int | 0 | Filter recipients by course context |
search | string | "" | Search by display name, login, or email |
Response:
[
{
"id": 12,
"name": "John Doe",
"email": "john@example.com",
"avatar": "https://example.com/avatar.jpg",
"role": "Instructor"
}
]
Report Endpoints
Namespace: ld-dashboard/v2
Report endpoints require the user to pass LD<em>Dashboard</em>REST_Permissions::check(). Administrators, instructors, and group leaders have access. Students are automatically scoped to their own data.
GET /reports
List all reports and charts available to the current user.
Permission: Administrator, instructor, or group leader
Response:
{
"success": true,
"data": {
"tables": {
"quiz-results": {
"id": "quiz-results",
"title": "Quiz Results",
"type": "table",
"exports": ["csv", "excel"]
}
},
"charts": {
"course-completion": {
"id": "course-completion",
"title": "Course Completion",
"type": "chart",
"chartType": "doughnut"
}
}
}
}
GET /reports/{report_id}
Get data for a specific table report.
Also accepts POST (for large filter payloads).
Permission: Administrator, instructor, or group leader
URL parameters:
| Parameter | Type | Description |
|---|---|---|
report_id | string | Report ID (e.g., quiz-results, essay-submissions) |
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
course_id | int | 0 | Filter by course ID |
group_id | int | 0 | Filter by group ID |
lesson_id | int | 0 | Filter by lesson ID |
user_id | int | 0 | Filter by user ID (auto-set for students) |
status | string | all | Filter by status |
per_page | int | -1 | Items per page (-1 for all) |
page | int | 1 | Page number |
Response:
{
"success": true,
"data": {
"id": "quiz-results",
"title": "Quiz Results",
"type": "table",
"columns": [
{
"data": "student_name",
"title": "Student",
"visible": true,
"orderable": true
}
],
"data": [ /* row objects */ ],
"exports": ["csv", "excel"],
"meta": {
"report_id": "quiz-results",
"report_type": "table",
"total": 42,
"cached_at": "2026-02-20 14:00:00"
}
}
}
Report data is cached in WordPress transients for 1 hour (by default per get<em>cache</em>duration()).
DELETE /reports/{report_id}/cache
Clear cached data for a specific report.
Permission: Administrator only
Response:
{
"success": true,
"message": "Cache cleared successfully."
}
Chart data via reports endpoint
Charts are served through the same /reports/{id} endpoint. The response structure differs for chart types.
Example chart response:
{
"success": true,
"data": {
"id": "course-completion",
"title": "Course Completion",
"type": "chart",
"chartType": "doughnut",
"chartData": {
"labels": ["Completed", "In Progress", "Not Started"],
"datasets": [
{
"label": "Course Completion",
"data": [45, 30, 25],
"backgroundColor": ["rgba(46,204,113,0.8)", "rgba(241,196,15,0.8)", "rgba(113,125,150,0.8)"],
"borderColor": ["rgba(46,204,113,1)", "rgba(241,196,15,1)", "rgba(113,125,150,1)"],
"borderWidth": 1
}
]
},
"options": {
"responsive": true,
"maintainAspectRatio": true,
"plugins": {
"legend": { "display": true, "position": "top" }
}
},
"meta": {
"report_id": "course-completion",
"report_type": "chart",
"chart_type": "doughnut",
"total": 3,
"cached_at": "2026-02-20 14:00:00"
}
}
}
Chart query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
course_id | int | 0 | Filter by course |
group_id | int | 0 | Filter by group |
filter | string | year | Time range: year, month, or week |
date_from | string | "" | Start date in YYYY-MM-DD format |
date_to | string | "" | End date in YYYY-MM-DD format |
Available Reports
Use these IDs as the report<em>id parameter in /reports/{report</em>id} requests.
Table Reports
| Report ID | Title | Description |
|---|---|---|
quiz-results | Quiz Results | Quiz attempt scores and pass/fail status per student |
essay-submissions | Essay Submissions | Student essay submissions with grading status |
assignment-status | Assignment Status | Assignment submissions with approval status |
course-progress | Course Progress | Student progress through enrolled courses |
time-by-course | Time by Course | Learning time totals broken down by course |
time-by-student | Time by Student | Learning time totals broken down by student |
time-by-content | Time by Content | Learning time broken down by lesson or topic |
instructor-performance | Instructor Performance | Course completion rates and student counts per instructor |
group-performance | Group Performance | Completion rates and activity per LearnDash group |
quiz-question-analysis | Quiz Question Analysis | Per-question pass/fail rates across all attempts |
Chart Reports
| Chart ID | Title | Chart Type | Description |
|---|---|---|---|
course-completion | Course Completion | Doughnut | Completed vs. in-progress vs. not started |
top-courses | Top Courses | Bar | Courses ranked by enrollment or completion count |
instructor-earnings | Instructor Earnings | Bar | Commission earnings per instructor over time |
time-spent | Time Spent | Bar | Total learning time with week/month/year filter |
time-trend | Time Trend | Line | Learning time trend over a selected period |
enrollment-trends | Enrollment Trends | Line | New enrollments over time |
revenue-per-course | Revenue per Course | Bar | Revenue broken down by course |
course-dropoff | Course Drop-off | Bar | Where students stop progressing in a course |
Registering Custom Reports
Use the ld<em>dashboard</em>reports_registered action to register your own report class after built-in reports load:
add_action( 'ld_dashboard_reports_registered', function( $registry ) {
LD_Dashboard_Report_Registry::register( 'my-report', 'My_Custom_Report_Class' );
} );
Your class must extend LD<em>Dashboard</em>Report<em>Base (table) or LD</em>Dashboard<em>Chart</em>Base (chart).
User Statistics Endpoint
Namespace: ldd_report/v1
GET /user/{id}/statistics
Get aggregate statistics for a specific user.
URL: /wp-json/ldd_report/v1/user/{id}/statistics
Permission: Administrator, instructor, or the user themselves
URL parameters:
| Parameter | Type | Description |
|---|---|---|
id | int | WordPress user ID |
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
force | bool | false | Bypass transient cache and recalculate fresh statistics |
Response:
{
"success": true,
"cached": true,
"data": {
"user_id": 5,
"course_count": 12,
"lessons_count": 48,
"topics_count": 96,
"quizzes_count": 24,
"enrolled_course_count": 3,
"active_course_count": 2,
"completed_course_count": 1,
"approved_assignment_count": 7,
"not_approved_assignment_count": 3,
"graded_essays_count": 5,
"not_graded_essays_count": 2,
"students_count": 143,
"group_count": 4,
"certificate_count": 1,
"calculated_at": "2026-02-20 14:00:00"
}
}
cached is true when the response was served from the transient cache. Pass ?force=true to recalculate and refresh the cache. Statistics are cached for 1 hour per user.
Response field reference:
| Field | Description |
|---|---|
course_count | Courses created (instructors/admins) or completed quizzes (students) |
lessons_count | Lessons in the user’s courses |
topics_count | Topics in the user’s courses |
quizzes_count | Quizzes in the user’s courses (or completed quizzes for students) |
enrolled<em>course</em>count | Total courses the user is enrolled in |
active<em>course</em>count | Enrolled courses with status in_progress |
completed<em>course</em>count | Enrolled courses with status completed |
approved<em>assignment</em>count | Assignments with approval_status = 1 |
not<em>approved</em>assignment_count | Assignments pending approval |
graded<em>essays</em>count | Essays marked as graded |
not<em>graded</em>essays_count | Essays pending grading |
students_count | Students enrolled in the user’s courses or groups |
group_count | LearnDash groups the user belongs to or manages |
certificate_count | Certificates earned by the user |
calculated_at | MySQL timestamp of when statistics were last calculated |
Error Responses
All endpoints return standard WordPress REST error objects on failure:
{
"code": "ld_dashboard_forbidden",
"message": "You do not have permission to view this report.",
"data": { "status": 403 }
}
| HTTP Status | Code | Meaning |
|---|---|---|
| 401 | rest_forbidden | Not logged in |
| 403 | ld<em>dashboard</em>forbidden | Insufficient permissions |
| 404 | ld<em>dashboard</em>not_found | Report or message not found |
| 500 | ld<em>dashboard</em>error | Server error during data fetch |
