List the exams one of your students can apply to
- Bearer token
- Permission
exam/read - Per organization
The exams one of your students can apply to in this organization, as a tree: categories, each holding its sittings, each holding the languages it can be sat in. Every leaf carries matchedExam, and matchedExam._id is the examId that createApplication takes.
Only your students. A student is yours when your account registered it. Another account’s student answers 404, exactly like an id that is nobody’s.
What is offered. An exam is in the tree when all of these hold:
- it is open for applications, as
listExamslists it. An exam is open for applications while all three hold: it is not closed to applications (preventApplicationis nottrue), its sitting (session.date) is still to come, and its category is active. It stops being open at the moment its sitting starts. - the student’s grade is one of the exam’s
grades; - the exam is open to the student’s country, or to every country. When the student has no country, or the organization does not list it, only exams open to every country are offered;
- the exam has a language;
- the student holds no application in this organization for the same category on the same sitting.
createApplication refuses an exam by the same rules, so what is listed here is what it accepts, as long as nothing changes in between. It also needs the student to have signed in to the organization once (createSigninLink); this list does not.
Order and size. Categories in the organization’s order, sittings soonest first, languages in order. Not paginated: data is the whole tree, and [] when nothing is offered. No branch is ever empty.
Call first: registerStudent, or listStudents, for the studentId. The student needs a grade: one without is refused with 400 until you set it with updateStudent or updateOrgStudent. A read only: nothing changes, and it is safe to repeat.
Parameters
Path parameters
| Name | Type | Description |
|---|---|---|
organizationIdrequired | stringpattern ^[0-9a-f]{24}$ | The organization’s Example |
studentIdrequired | string | The |
Responses
200 OK
Success: message is "Available exams fetched successfully.".
Body
successbooleanrequiredAlways
trueon a success.one of
truemessagestringrequiredexample
Available exams fetched successfully.dataarray of AvailableExamCategoryResponserequired10 fields of each item
_idstringrequiredThe category’s id.
example
6650a1b2c3d4e5f6a7b8c9e3namestringThe category’s name, as students see it.
example
MathematicsaltNamestringA second name for the category, where one is set.
example
MathsordernumberWhere the category sorts among the others, lowest first. The available-exams tree is in this order.
example
1isActivebooleanWhether the category is live. No exam in an inactive category is open for applications.
example
truenonAcceptedReplacementsarray of stringIds of the categories an application in this one may not be moved to:
moveApplicationrefuses such a move.example
["6650a1b2c3d4e5f6a7b8c9ea"]studyMaterialLinksarray of stringLinks to study material for the category.
example
["https://example.org/study/mathematics"]createdAtstringWhen the record was created.
format
date-timeexample2026-09-01T09:30:00.000ZupdatedAtstringWhen the record last changed.
format
date-timeexample2026-09-02T14:05:00.000Zsessionsarray of AvailableExamSessionResponserequiredThe sittings in this category the student can apply to, soonest first. Never empty. A sitting the student already holds an application for in this category is left out.
14 fields of each item
_idstringrequiredThe sitting’s id.
example
6650a1b2c3d4e5f6a7b8c9e2sessionNamestringThe sitting’s name.
example
November 2026datestringWhen the sitting takes place. Its exams are open for applications until this moment, and not after.
format
date-timeexample2026-11-14T10:00:00.000ZstartTimestringThe start time as the organization wrote it. For the moment itself, read
date.example
10:00tzstringglobal: the sitting starts at one moment everywhere.local: it starts at the same clock time in each student’s own time zone, their country’stz.one of
globallocalexample
globalsessionAliasstringA second name for the sitting, shown to students.
example
Autumn roundsessionNotestringA note about the sitting, shown to students.
example
Please join ten minutes early.enableSimulationbooleanWhether students get a practice run before the sitting.
example
truesimulationDatestringWhen the practice run opens.
format
date-timeexample2026-11-07T10:00:00.000ZsimulationEndDatestringWhen the practice run closes.
format
date-timeexample2026-11-08T10:00:00.000ZrelatedSessionstringSet on a make-up sitting: the id of the sitting it belongs to. An application to an exam on a make-up sitting is removed 6 hours after it is made.
example
6650a1b2c3d4e5f6a7b8c9e2createdAtstringWhen the record was created.
format
date-timeexample2026-09-01T09:30:00.000ZupdatedAtstringWhen the record last changed.
format
date-timeexample2026-09-02T14:05:00.000Zlanguagesarray of AvailableExamLanguageResponserequiredThe languages the student can take this sitting in, in the organization’s order. Never empty.
7 fields of each item
_idstringrequiredThe language’s id.
example
6650a1b2c3d4e5f6a7b8c9e4namestringThe language’s name.
example
EnglishcodestringA short language code.
example
enordernumberWhere the language sorts among the others, lowest first. The available-exams tree is in this order.
example
1createdAtstringWhen the record was created.
format
date-timeexample2026-09-01T09:30:00.000ZupdatedAtstringWhen the record last changed.
format
date-timeexample2026-09-02T14:05:00.000ZmatchedExamobject · ExamRecordResponserequiredThe one exam this category, sitting and language make up. Its
_idis theexamIdthatcreateApplicationtakes; itssession,categoryandlanguageare the ids of the levels above.14 fields of matchedExam
_idstringrequiredThe exam’s id: the
examIdthatcreateApplicationandmoveApplicationtake.example
6650a1b2c3d4e5f6a7b8c9e1sessionstringThe id of the sitting the exam is on.
example
6650a1b2c3d4e5f6a7b8c9e2categorystringThe id of the exam’s category.
example
6650a1b2c3d4e5f6a7b8c9e3languagestringThe id of the language the exam is sat in, when it has one.
example
6650a1b2c3d4e5f6a7b8c9e4gradesarray of stringThe grades that may sit the exam. A student in any other grade is not offered it and cannot apply to it. Grade ids are the same in every organization.
example
["6650a1b2c3d4e5f6a7b8c9d4"]countriesarray of stringThe countries the exam is restricted to; empty or absent, it is open to every country. Ids of the organization’s own country records, which need not match the ones
listCountriesgives.example
[]examTypestringstandardoressay.one of
standardessayexample
standardexamTimenumberThe time limit, in minutes, from when the student starts.
example
75durationnumberHow many hours the exam can be started in, counted from the start of the sitting.
example
15questionCountnumberHow many questions the exam has.
example
30pricenumberWhat an application costs, as a number; no currency is given. Absent when the exam has no price: an application to it is charged 0.
example
25preventApplicationbooleanWhether the exam is closed to new applications. A closed exam is never listed, so on the exam reads this is
falseor absent.example
falsecreatedAtstringWhen the record was created.
format
date-timeexample2026-09-01T09:30:00.000ZupdatedAtstringWhen the record last changed.
format
date-timeexample2026-09-02T14:05:00.000Z
Headers
X-RateLimit-LimitintegerRequests your account may make to this operation per window (100).
X-RateLimit-RemainingintegerRequests left in the current window.
X-RateLimit-ResetintegerSeconds until the current window ends.
Example
{
"success": true,
"message": "Available exams fetched successfully.",
"data": [
{
"_id": "6650a1b2c3d4e5f6a7b8c9e3",
"name": "Mathematics",
"altName": "Maths",
"order": 1,
"isActive": true,
"nonAcceptedReplacements": [
"6650a1b2c3d4e5f6a7b8c9ea"
],
"studyMaterialLinks": [
"https://example.org/study/mathematics"
],
"createdAt": "2026-09-01T09:30:00.000Z",
"updatedAt": "2026-09-02T14:05:00.000Z",
"sessions": [
{
"_id": "6650a1b2c3d4e5f6a7b8c9e2",
"sessionName": "November 2026",
"date": "2026-11-14T10:00:00.000Z",
"startTime": "10:00",
"tz": "global",
"sessionAlias": "Autumn round",
"sessionNote": "Please join ten minutes early.",
"enableSimulation": true,
"simulationDate": "2026-11-07T10:00:00.000Z",
"simulationEndDate": "2026-11-08T10:00:00.000Z",
"relatedSession": "6650a1b2c3d4e5f6a7b8c9e2",
"createdAt": "2026-09-01T09:30:00.000Z",
"updatedAt": "2026-09-02T14:05:00.000Z",
"languages": [
{
"_id": "6650a1b2c3d4e5f6a7b8c9e4",
"name": "English",
"code": "en",
"order": 1,
"createdAt": "2026-09-01T09:30:00.000Z",
"updatedAt": "2026-09-02T14:05:00.000Z",
"matchedExam": {
"_id": "6650a1b2c3d4e5f6a7b8c9e1",
"session": "6650a1b2c3d4e5f6a7b8c9e2",
"category": "6650a1b2c3d4e5f6a7b8c9e3",
"language": "6650a1b2c3d4e5f6a7b8c9e4",
"grades": [
"6650a1b2c3d4e5f6a7b8c9d4"
],
"countries": [],
"examType": "standard",
"examTime": 75,
"duration": 15,
"questionCount": 30,
"price": 25,
"preventApplication": false,
"createdAt": "2026-09-01T09:30:00.000Z",
"updatedAt": "2026-09-02T14:05:00.000Z"
}
}
]
}
]
}
]
}400 Bad request
bad_request:studentIdis not 24 hexadecimal digits.bad_request: The student has no grade. Every exam is restricted to a set of grades, so no exam could be offered; set one first.
When
bad_requeststudentIdis not 24 hexadecimal digits.bad_requestThe student has no grade. Every exam is restricted to a set of grades, so no exam could be offered; set one first.
Example
{
"error": {
"code": "bad_request",
"documentation_url": "https://hub.main-team.org/api/errors#bad_request",
"message": "Invalid value for '_id': expected ObjectId.",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.
401 Unauthorized
unauthorized: The token is missing or malformed, is not signed with your account’s apiSecret, breaks the iat and exp rules, has expired or been revoked, or its account is not active. All of these answer the same.
When
unauthorizedThe token is missing or malformed, is not signed with your account’s
apiSecret, breaks theiatandexprules, has expired or been revoked, or its account is not active. All of these answer the same.
Example
{
"error": {
"code": "unauthorized",
"documentation_url": "https://hub.main-team.org/api/errors#unauthorized",
"message": "Authentication is required or the provided credentials are invalid.",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.
403 Forbidden
forbidden: The token is valid, but no role on your account allows exam/read on the organization in the path, or a role denies it.
When
forbiddenThe token is valid, but no role on your account allows
exam/readon the organization in the path, or a role denies it.
Example
{
"error": {
"code": "forbidden",
"documentation_url": "https://hub.main-team.org/api/errors#forbidden",
"message": "Insufficient role permissions",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.
404 Not found
not_found:organizationIdis not the_idof an organization.not_found: No student of yours has this id: it is nobody’s, or another account’s. Both answer the same.
When
Example
{
"error": {
"code": "not_found",
"documentation_url": "https://hub.main-team.org/api/errors#not_found",
"message": "Organization not found!",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.
429 Too many requests
too_many_requests: Your account has made more than 100 requests to this operation in the current 60-second window. Wait the seconds in Retry-After before sending again.
When
too_many_requestsYour account has made more than 100 requests to this operation in the current 60-second window. Wait the seconds in
Retry-Afterbefore sending again.
Headers
Retry-AfterintegerSeconds to wait before sending again; a request sent sooner is refused too.
Example
{
"error": {
"code": "too_many_requests",
"documentation_url": "https://hub.main-team.org/api/errors#too_many_requests",
"message": "Too many requests to this operation. Wait the number of seconds in Retry-After, then try again.",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.
500 Internal error
internal_error: Something failed on our side. Retry later, and quote request_id if it goes on.
When
internal_errorSomething failed on our side. Retry later, and quote
request_idif it goes on.
Example
{
"error": {
"code": "internal_error",
"documentation_url": "https://hub.main-team.org/api/errors#internal_error",
"message": "An unexpected error occurred.",
"request_id": "0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8e"
}
}Fields of the error body
errorobject · ErrorDetailrequiredWhat went wrong.
5 fields of error
codestringrequiredA stable machine code: branch on this. Each one is explained at
documentation_url.one of
invalid_emailbad_requestunauthorizedforbiddennot_foundconflictpayload_too_largeunsupported_media_typeunprocessable_entitytoo_many_requestsinternal_errorservice_unavailableexample
not_foundmessagestringrequiredWritten for a person, and may change: never branch on it.
example
Not found!documentation_urlstringrequiredWhere this code is explained.
format
uriexamplehttps://hub.main-team.org/api/errors#not_foundrequest_idstringrequiredThis request’s id, also sent as the
X-Request-Idresponse header: the one you sent, when it was acceptable, or else one the API made. Quote it when you report a problem.example
0b5c6d0e-8f7a-4b1c-9d2e-3f4a5b6c7d8edetailsobjectPresent only where an operation says so, and then with the shape that operation documents — the rows it could not accept, say. Treat it as absent everywhere else, and ignore a
detailsyou do not recognise.