Skip to content
API documentation

Changelog

Version 1.1.0

Three new operations — bulk registration, its status, and a registration pre-check — with an email filter on listStudents, a signedIn field and filter on listOrgStudents, and an optional details object on error bodies. Nothing that worked against 1.0.1 changes.

Added

  • createStudentImport: POST /v1/student/import registers 30 to 1000 students in one request. Every row follows registerStudent's rules and takes everything it takes except password, plus an optional externalRef of your own that is echoed back. The whole batch is checked before anything is queued; the answer is 202 with the import and a Location header, and the students are registered in the background. They are written in one transaction: a batch registers every row or registers nothing, so there is no partial import to reconcile. Each student gets the same welcome email registerStudent sends. Limits: one unfinished import per account (409 otherwise), 10 requests an hour, and bodies up to 1.5 MB on this operation where every other takes 100 kB. Sending the same rows again inside 24 hours answers with the import you already have rather than a second one. It needs student/create on mto, the permission registration needs.
  • getStudentImport: GET /v1/student/import/{importId} returns one of your imports and one page of its rows, with page and limit. status is queued, running, succeeded, failed or cancelled; on succeeded every row carries the student's _id, and on anything else no student of that batch was registered. An import stops being readable 30 days after it finishes and then answers 404, as does one another account sent. It needs student/create on mto.
  • Error bodies may carry an optional details object, and createStudentImport is the first operation to send one: its 422 puts every row it cannot register in error.details.rows, each with its position, the property at fault and a code to branch on. Ignore a details you do not recognise; no other operation sends one.
  • checkStudentRegistration: POST /v1/student/check runs the checks registerStudent makes before it creates anything, on the same body without password, and answers 200 with what they found: valid; every country, grade, city or school that matches nothing, with the message registration would refuse it with; the _id each reference resolves to; and whether one of your students already has the email address, with that student's _id. Nothing is created and no username is issued. It needs student/create on mto, the permission registration needs. Only your own students are checked for the address, so an address a student on another account holds is still refused by registerStudent alone.
  • listStudents: an optional email query parameter lists only your students who have one of up to 100 addresses, separated by commas, matched exactly and without regard to case. A value that is not such a list is refused with 400 bad_request.
  • listOrgStudents: each student carries signedIn, whether they have signed in to that organization at least once, and an optional signedIn query parameter, true or false, lists only the students who have or only those who have not. Any other value is refused with 400 bad_request. createApplication and linkStudentSupervisor on an organization need the student to have signed in there once.

Release notes

Version 1.1.0 adds three operations and three fields, and removes and changes nothing. Code written against 1.0.1 keeps working without a change, and every request you send today answers as it did before.

Register a class in one request

createStudentImport takes 30 to 1000 students in one request. Every row follows registerStudent's rules and takes everything it takes except password, plus an optional externalRef of your own that comes back on the row.

The whole batch is checked before anything is queued. If every row passes, the answer is 202 with the import and a Location header, and the students are registered in the background. The batch is written in one transaction: it registers every row or it registers nothing, so you never have half a class to reconcile. Each student gets the welcome email registerStudent sends.

Follow the import with getStudentImport, which returns the import and one page of its rows, with page and limit as every list takes them. status is queued, running, succeeded, failed or cancelled. On succeeded every row carries the student's _id; on anything else no student of that batch was registered. An import stops being readable 30 days after it finishes, and then answers 404, as does an import another account sent.

Both need student/create on mto, the permission registration already needs, so an account that registers students today can import them without a new role.

Limits, which are stricter than the rest of the API:

LimitValue
Rows in one request30 to 1000
Requests10 an hour
Body1.5 MB on this operation, where every other takes 100 kB
Unfinished imports per account1; a second answers 409

Sending the same rows again inside 24 hours answers with the import you already have, rather than starting a second one, so a retry after a timeout is safe. The whole workflow, with a worked run, is in Registering many students at once.

A 422 that names the rows

When a batch is refused, nothing is queued and the answer is 422 unprocessable_entity with every row it will not register. That list arrives in a new, optional details object on the error body:

{
  "error": {
    "code": "unprocessable_entity",
    "message": "The import was refused.",
    "documentation_url": "https://hub.main-team.org/api/errors#unprocessable_entity",
    "request_id": "0199d2d6-6f04-7a31-9f2a-0f4d2a17c2b1",
    "details": {
      "rows": [
        { "row": 4, "field": "grade", "code": "unknown_reference", "message": "Grade not found!" }
      ]
    }
  }
}

details is optional and operation-specific: createStudentImport is the only operation that sends one today, and every other error body is exactly what it was in 1.0.1. Ignore a details you do not recognize, as you ignore any field you do not recognize. The four fields your code already reads — code, message, documentation_url and request_id — are unchanged and always there. See Errors and Requests and responses.

Check a registration before you send it

checkStudentRegistration runs the checks registerStudent makes before it creates anything, on the same body without password, and answers 200 with what they found:

  • valid, whether registration would go through;
  • every country, grade, city or school that matches nothing, with the message registration would refuse it with, and the _id each reference that does match resolves to;
  • whether one of your own students already has the email address, and that student's _id.

Nothing is created, no username is issued, and it needs student/create on mto, the permission registration needs. Only your own students are checked for the address: an address a student on another account holds is still refused by registerStudent itself, so a valid answer is a strong hint, never a promise. See Students.

Find students by address, and see who has signed in

listStudents takes an optional email query parameter: up to 100 addresses, separated by commas, matched exactly and without regard to case. It lists only your own students, as it always has. A value that is not such a list is refused with 400 bad_request.

listOrgStudents carries a new signedIn field on every student, whether that student has signed in to that organization at least once, and takes signedIn as an optional filter, true or false. Any other value is refused with 400 bad_request. This is the field to look at when an entry or a supervisor link is refused: both createApplication and linkStudentSupervisor need the student to have signed in to that organization once, which a sign-in link does.

Nothing to change

  • No operation, request field, response field or error code was removed or narrowed.
  • No request field became required, and no response field became optional.
  • The new response field, signedIn on listOrgStudents, is an addition: a client that rejects fields it does not know is the only one that notices it.

Search the API documentation

Guides, endpoints by name, path or permission, and error codes such as not_found.