Analytics dashboard data for a group
Changed onReturn every figure the analytics dashboard renders, in one read-only response: submission totals, above-threshold counts, medians, membership, a time series, a score distribution, paginated breakdowns by course and by assignment, and a paginated list of the submissions themselves.
Access. Group owners only. A teacher or student of the same group receives 403, as does a member of any other group.
What counts as a submission. A text belonging to the group, not deleted, whose created_at (the moment it was submitted, not the moment its check finished) falls inside [date_from, date_to] in the response timezone, and which matches the course / course_id and assignment / assignment_id filters.
Scores. Plagiarism comes from the text's single plagiarism report. AI comes from the most recent completed AI check on that text, so a later failed recheck does not erase an earlier score and a later successful one replaces it. A submission missing either score still counts in data.submissions.total but is excluded from every calculation based on that score: the above-threshold counts, the median, the average, the distribution, and the table flag columns.
Thresholds. ai_threshold and plagiarism_threshold are independent and inclusive (>=). They never affect data.submissions.total, data.members, or filters.options.
Courses and assignments. A course or assignment name exists only for submissions that arrived through an LMS integration, and only where that integration recorded the name in the shared name tables. Submissions without a resolvable course name are counted everywhere else but do not appear in data.courses, data.assignments, or filters.options, so the rows of those tables can sum to less than data.submissions.total. Names are merged across integrations and terms, so one course name is one row however many LMS courses carry it. course_id and assignment_id identify those names (one id per distinct name), not an LMS course or assignment.
Submissions list. data.submissions.items lists the filtered submissions one per row, with the submitting user, both scores and the course and assignment. The date range and the course / assignment filters apply to it like to everything else; the submissions_* parameters then narrow, sort and page the list only - no total, chart or table changes when they do. So data.submissions.pagination.total_count can be lower than data.submissions.total.
Pagination. Three independent paginators - courses, assignments, submissions - each with its own <table>_page / <table>_per_page parameters and its own pagination object inside the table. Read pagination.page_count to know where to stop; a page past the end is empty, not an error.
Timezone. Always UTC. Range boundaries and time-series buckets are drawn in it, and it is echoed in filters.timezone.
Caching. A successful response is cached server-side for ten minutes, keyed by the group and every parameter below, and cannot be bypassed from the request. Figures can therefore trail reality by up to ten minutes, which is most visible during a submission deadline. Changing any parameter — a page number or a sort direction included — is a fresh read rather than a cached one.
Request
- Base URLhttps://plagiarismcheck.org
- AuthAPI key in header X-API-TOKEN
Path parameters
Numeric group ID.
Query parameters
First day of the range, inclusive, in the response timezone. Must be earlier than or equal to date_to; there is no limit on how wide the range is. Defaults to the day the group was created, so a request without dates covers the group's whole history - or to date_to, if that is earlier.
Last day of the range, inclusive, in the response timezone. Defaults to today.
Restrict every figure except membership to one course, matched exactly on the course name after trimming surrounding whitespace. Omitted, empty, or whitespace-only means all courses.
Restrict every figure except membership to one assignment, matched exactly on the assignment name after trimming surrounding whitespace. Without course it matches that assignment name in every course, which is intended: two courses can both run an assignment called "Essay 2". Omitted, empty, or whitespace-only means all assignments.
Restrict every figure except membership to one course, by the course_id that filters.options.courses and the table rows publish. The id names one distinct course name, so this matches exactly what course with that name would. Sending both course and course_id is a 422 against course_id. Omitted or empty means all courses.
Restrict every figure except membership to one assignment, by the assignment_id that filters.options.assignments and the assignments table publish. Like assignment, without a course filter it matches that assignment name in every course. Sending both assignment and assignment_id is a 422 against assignment_id. Omitted or empty means all assignments.
Time-series bucket size. Weeks are ISO weeks and start on Monday; months are calendar months. There is no limit on the number of buckets: a range of several years at step=day returns one per day.
Minimum AI score, inclusive, for a submission to be counted by any AI figure. Does not affect data.submissions.total, data.members, or filters.options.
Minimum plagiarism score, inclusive, for a submission to be counted by any plagiarism figure. Independent of ai_threshold.
Column to sort data.courses.items by. ai_flag and plagiarism_flag sort by the percentage, not by the raw count. Ties break on course_name ascending.
Sort direction for data.courses.items.
One-based page number for data.courses.items. A page past the end returns no items.
Number of rows per page for data.courses.items.
Column to sort data.assignments.items by. Ties break on assignment_name ascending, then course_name ascending.
Sort direction for data.assignments.items.
One-based page number for data.assignments.items.
Number of rows per page for data.assignments.items.
Column to sort data.submissions.items by. user_name sorts a user with no name by their email, which is what the row shows in its place. A missing score, course or assignment sorts last in both directions. Ties break on the newest submission first.
Sort direction for data.submissions.items.
One-based page number for data.submissions.items. A page past the end returns no items.
Number of rows per page for data.submissions.items.
List only: keep submissions whose course name contains this text, case-insensitively. % and _ are matched literally. Trimmed; empty means off. A submission with no course never matches. Unlike course, this narrows data.submissions.items and nothing else.
List only: keep submissions whose assignment name contains this text, case-insensitively. Same rules as submissions_course.
List only: keep submissions whose submitting user's name or email contains this text, case-insensitively. Same rules as submissions_course.
List only - keep one user's submissions, by user_id. Empty means off.
List only: keep the submissions at or above a threshold - ai (AI score >= ai_threshold), plagiarism (plagiarism score >= plagiarism_threshold) or any (either). A submission without the relevant score is never flagged. Omitted or empty lists every submission, unscored ones included.
Response
The request succeeded and returned the dashboard figures.
Example response
{
"success": true,
"filters": {
"group_id": 12,
"date_from": "2026-06-21",
"date_to": "2026-09-18",
"timezone": "UTC",
"course": "ENG 210 - Composition",
"assignment": "Essay 2 - Argument",
"course_id": 4,
"assignment_id": 17,
"step": "week",
"ai_threshold": 20,
"plagiarism_threshold": 15,
"sort": {
"courses": {
"field": "submissions",
"direction": "desc"
},
"assignments": {
"field": "submissions",
"direction": "desc"
},
"submissions": {
"field": "submissions",
"direction": "desc"
}
},
"pagination": {
"courses": {
"page": 1,
"per_page": 20
},
"assignments": {
"page": 1,
"per_page": 20
},
"submissions": {
"page": 1,
"per_page": 20
}
},
"submissions": {
"course": "compos",
"user": "smith",
"flagged": "any"
},
"options": {
"courses": [
{
"course_id": 4,
"course_name": "ENG 210 - Composition"
}
],
"assignments": [
{
"assignment_id": 17,
"assignment_name": "Essay 2 - Argument",
"course_id": 4,
"course_name": "ENG 210 - Composition"
}
]
}
},
"data": {
"submissions": {
"total": 4828,
"ai_scored": 4610,
"plagiarism_scored": 4828,
"items": [
{
"user_id": 5021,
"user_name": "Ann Smith",
"user_email": "ann.smith@north.edu",
"text_id": 90211,
"plagiarism_percent": 12.25,
"ai_percent": 81.5,
"course_id": 4,
"course_name": "ENG 210 - Composition",
"assignment_id": 17,
"assignment_name": "Essay 2 - Argument",
"submitted_at": 1789029000
}
],
"pagination": {
"last": 3,
"current": 1,
"num_items_per_page": 30,
"first": 1,
"page_count": 3,
"total_count": 67,
"page_range": 3,
"start_page": 1,
"end_page": 3,
"pages_in_range": [
1,
2,
3
],
"first_page_in_range": 1,
"lastPage_in_range": 3,
"current_item_count": 30,
"first_item_number": 1,
"last_item_number": 30
}
},
"ai_above_threshold": {
"count": 912,
"percent": 19.78
},
"plagiarism_above_threshold": {
"count": 912,
"percent": 19.78
},
"median": {
"ai": 4.2,
"plagiarism": 6.8
},
"members": {
"total": 540,
"teachers": 32,
"students": 508
},
"timeseries": [
{
"period_start": "2026-06-22",
"period_end": "2026-06-28",
"submissions": 96,
"ai_avg": 38.12,
"ai_flagged": 74,
"ai_scored": 91,
"ai_percent": 81.32,
"plagiarism_avg": 24.4,
"plagiarism_flagged": 31,
"plagiarism_scored": 96,
"plagiarism_percent": 32.29
}
],
"distribution": [
{
"from": 90,
"to": 100,
"ai": 41,
"plagiarism": 7
}
],
"courses": {
"items": [
{
"course_id": 4,
"course_name": "ENG 210 - Composition",
"submissions": 1204,
"ai_flag": 24.09,
"ai_flag_count": 290,
"ai_scored": 1204,
"plagiarism_flag": 7.97,
"plagiarism_flag_count": 96,
"plagiarism_scored": 1204
}
],
"pagination": {
"last": 3,
"current": 1,
"num_items_per_page": 30,
"first": 1,
"page_count": 3,
"total_count": 67,
"page_range": 3,
"start_page": 1,
"end_page": 3,
"pages_in_range": [
1,
2,
3
],
"first_page_in_range": 1,
"lastPage_in_range": 3,
"current_item_count": 30,
"first_item_number": 1,
"last_item_number": 30
}
},
"assignments": {
"items": [
{
"assignment_id": 17,
"assignment_name": "Essay 2 - Argument",
"course_id": 4,
"course_name": "ENG 210 - Composition",
"submissions": 1204,
"ai_flag": 24.09,
"ai_flag_count": 290,
"ai_scored": 1204,
"plagiarism_flag": 7.97,
"plagiarism_flag_count": 96,
"plagiarism_scored": 1204
}
],
"pagination": {
"last": 3,
"current": 1,
"num_items_per_page": 30,
"first": 1,
"page_count": 3,
"total_count": 67,
"page_range": 3,
"start_page": 1,
"end_page": 3,
"pages_in_range": [
1,
2,
3
],
"first_page_in_range": 1,
"lastPage_in_range": 3,
"current_item_count": 30,
"first_item_number": 1,
"last_item_number": 30
}
}
}
}Changes
- breaking
the
///response's property type changed fromstringtoobjectfor status - info
added the new optional
queryrequest parameterassignment_id - info
added the new optional
queryrequest parametercourse_id - info
added the new optional
queryrequest parametersubmissions_assignment - info
added the new optional
queryrequest parametersubmissions_course - info
added the new optional
queryrequest parametersubmissions_dir - info
added the new optional
queryrequest parametersubmissions_flagged - info
added the new optional
queryrequest parametersubmissions_page - info
added the new optional
queryrequest parametersubmissions_per_page - info
added the new optional
queryrequest parametersubmissions_sort - info
added the new optional
queryrequest parametersubmissions_user - info
added the new optional
queryrequest parametersubmissions_user_id - info
added the required property
////////to the response with the status - info
added the required property
/////to the response with the status - info
added the required property
////to the response with the status - info
added the required property
//to the response with the status - info
added the required property
//to the response with the status - info
added the required property
/to the response with the status - info
added the required property
/to the response with the status - info
added the required property
////to the response with the status - info
added the required property
////to the response with the status - info
added the required property
////to the response with the status - info
added the required property
////to the response with the status - info
added the required property
//to the response with the status - info
added the required property
//to the response with the status - info
added the required property
/to the response with the status
- breaking
- info
endpoint added
- info