Skip to content
API documentation
View as MarkdownOpen in Claude

Students

Register many students at once

POST
/v1/student/import

Registers 30 to 1000 students in one request. Every row follows registerStudent’s rules exactly, so read that operation first: this one is the same registration, a class at a time.

It answers before it registers anybody. The whole batch is checked while you wait — every field, every country, grade, city and school, and every email address — and then 202 with an import you read to follow it. The Location header is where to read it. Nothing exists yet when you get that answer: poll getStudentImport every few seconds until status is no longer queued or running.

All of them or none of them. The students are written in one transaction, so a batch either registers every row or registers nothing. There is no partial import to reconcile and nothing to undo. A batch that fails says why, and you fix the rows and send it again.

No passwords. A row takes everything registerStudent takes except password: hashing is deliberately slow, and a thousand of them would keep the batch waiting and leave the plaintext queued meanwhile. Sign the students in with createSigninLink, or set a password per student afterwards with setStudentPassword.

The welcome email is the one registerStudent sends, one per student, with the same text.

Limits. One unfinished import per account: send the next batch when this one has finished. 10 requests an hour. Bodies up to 1.5 MB here, where every other operation takes 100 kB.

Sending the same batch twice is safe. The same rows from your account inside 24 hours answer with the import you already have, not a second one, so a request whose answer you never saw can simply be sent again.

If anything is wrong with the rows the answer is 422, nothing is queued, and error.details.rows lists every row at fault with its position, property and a code to branch on. Fix them and send the batch again.

Request body

application/jsonrequired

  • studentsarray of StudentImportRowrequired

    The students to register, 30 to 1000 of them. Below that, call registerStudent per student: an import is queued and answered before it runs, and for a handful of students the round trip is not worth the wait.

    min items30max items1000

    13 fields of each item
    • firstNamestringrequired

      The student’s first name. Printed on certificates and reports, followed by lastName.

      exampleJane

    • lastNamestringrequired

      The student’s surname. Printed on certificates and reports after firstName. It cannot be empty.

      exampleDoe

    • birthstringrequired

      Date of birth as DD/MM/YYYY, and a date that exists: 31/02/2008 is refused. An ISO date such as 2008-05-14 is refused.

      pattern^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$example14/05/2008

    • sexstringrequired

      One of m, f or n.

      one ofmfn

      examplef

    • emailstringrequired

      The student’s email address, stored in lower case. An address belongs to one student on the whole platform, so one already registered, by your account or another, is refused with 409.

      formatemailexamplejane.doe@example.com

    • email2string

      Leave this out. Any value but an empty one is refused with 400.

    • phonestring

      A phone number, stored as you send it. No format is checked. On an update, "" clears it.

      example+1 555 0100

    • countrystringrequired

      The _id of a country, from listCountries (GET /v1/country). An id only: a name or an ISO code is refused. Its two-letter code starts the student’s username.

      pattern^[0-9a-fA-F]{24}$example6650a1b2c3d4e5f6a7b8c9d1

    • gradestringrequired

      The _id of a grade, from listGrades (GET /v1/grade), or its name, 1 to 12. Either way the grade’s _id is what is stored. One that matches no grade is refused with 400. The grade decides which exams the student is offered.

      example10

    • schoolstringrequired

      The _id of a school, or its name within country and city, matched without regard to case. There is no list of schools to look one up in: send the name your records hold. One that matches no school is refused with 400; no school is ever created.

      exampleSpringfield High School

    • citystringrequired

      The _id of a city, or its name within country, matched without regard to case. There is no list of cities to look one up in: send the name your records hold. One that matches no city is refused with 400; no city is ever created.

      exampleSpringfield

    • activatedPlatformsThisSeasonarray of string

      The organizations the student takes part in this season, by slug (as listOrganizations gives it, except mto: the core record, which every student is on), or common for every organization. Registration sets ["common"] when you leave it out; null is refused with 400. At most 6 entries.

      each one ofcommonstemhilinguaneogmathcoding

      max items6example["common"]

    • externalRefstring

      Your own reference for this row, echoed back when you read the import. Not stored on the student and not required to be unique. At most 64 characters.

      max length64exampleroster-2026-114

  • clientReferencestring

    Your own name for this batch, echoed back when you read the import. At most 64 characters.

    max length64exampleyear-10-autumn-2026

Responses

202 Accepted

Accepted: message is "Import accepted. The students are registered in the background; read the import to follow it.".

Or message is "You already sent this batch. Its import is unchanged.": You sent these exact rows inside the last 24 hours. The import in data is the one you already have; no second one was made.

Body

  • successbooleanrequired

    Always true on a success.

    one oftrue

  • messagestringrequired

  • dataobject · StudentImportResponserequired

    A batch of students you asked to register, and what has happened to it.

    11 fields of data
    • _idstringrequired

      The import’s _id. Read it with getStudentImport.

      example65f0c2a1d3e4f5a6b7c8d901

    • statusstringrequired

      queued until a server picks it up, running while it registers, then succeeded or failed. cancelled means an operator stopped it. There is no partial import: on anything but succeeded no student of this batch was registered.

      one ofqueuedrunningsucceededfailedcancelled

      examplequeued

    • totalnumberrequired

      How many rows you sent.

      example250

    • registerednumberrequired

      How many students were registered: total once the import has succeeded, and 0 until then and for ever after a failure.

      example250

    • clientReferencestring

      The clientReference you sent, if you sent one.

      exampleyear-10-autumn-2026

    • failureobject · StudentImportFailureResponse

      Set once the import has failed or been cancelled.

      2 fields of failure
      • codestringrequired

        A stable machine code. rows_rejected: a row stopped being registrable between the check and the write, most often because its address was registered in between; the rows say which. forbidden: the account may no longer register students. cancelled: an operator stopped it. internal_error: something failed on our side.

        examplerows_rejected

      • messagestringrequired

        Written for a person, and may change: never branch on it.

        exampleTwo rows could no longer be registered. Nothing was registered.

    • studentsarray of StudentImportRowResponserequired

      One page of the rows, in the order you sent them. Page it with page and limit; pagination.total counts every row. Absent from the answer to createStudentImport, which has nothing to report yet.

      6 fields of each item
      • rownumberrequired

        The row’s position in the students you sent, counting from 0.

        example0

      • emailstringrequired

        The address you sent for this row, in lower case.

        formatemailexamplejane.doe@example.com

      • externalRefstring

        The externalRef you sent for this row, if you sent one.

        exampleroster-2026-114

      • statusstringrequired

        pending until the import runs, then registered for every row when it succeeds, or skipped for every row when it does not. An import registers all of its students or none of them.

        one ofpendingregisteredskipped

        exampleregistered

      • studentIdstring

        The student’s _id, on a registered row. Use it with getStudent, createSigninLink and createApplication.

        example6650a1b2c3d4e5f6a7b8c9d0

      • errorobject · StudentImportRowErrorResponse

        Why this row could not be registered, when it could not.

        4 fields of error
        • codestringrequired

          A stable machine code: branch on this. invalid_field and unexpected_field are the row’s own rules; unknown_reference is a country, grade, city or school that matches nothing; duplicate_in_request is the same address twice in your own body; email_taken_by_your_student is one of your students; email_unavailable is an address that cannot be registered, and the answer never says who holds it.

          one ofinvalid_fieldunexpected_fieldunknown_referenceduplicate_in_requestemail_taken_by_your_studentemail_unavailable

          exampleemail_taken_by_your_student

        • messagestringrequired

          Written for a person, and may change: never branch on it.

          exampleOne of your students already has this email address.

        • studentIdstring

          On email_taken_by_your_student: the _id of the student of yours who has the address, so you can update them instead.

          example6650a1b2c3d4e5f6a7b8c9d0

        • duplicateOfnumber

          On duplicate_in_request: the earlier row in your own body with the same address.

          example12

    • createdAtstringrequired

      When you sent it.

      formatdate-timeexample2026-10-06T08:15:00.000Z

    • startedAtstring

      When a server picked it up.

      formatdate-timeexample2026-10-06T08:15:04.000Z

    • finishedAtstring

      When it ended, whichever way it ended.

      formatdate-timeexample2026-10-06T08:15:09.000Z

    • expiresAtstringrequired

      When this import stops being readable. After it, getStudentImport answers 404, exactly as it does for an import that never existed. The students stay registered.

      formatdate-timeexample2026-11-05T08:15:09.000Z

Headers

Location string

The import’s own path, /v1/student/import/{importId}. Read it to follow the batch.

X-RateLimit-Limit integer

Requests your account may make to this operation per window (10 per 3600 seconds).

X-RateLimit-Remaining integer

Requests left in the current window.

X-RateLimit-Reset integer

Seconds until the current window ends.

Example

{
  "data": {
    "_id": "65f0c2a1d3e4f5a6b7c8d901",
    "clientReference": "year-10-autumn-2026",
    "createdAt": "2026-10-06T08:15:00.000Z",
    "expiresAt": "2026-11-05T08:15:09.000Z",
    "failure": {
      "code": "rows_rejected",
      "message": "Two rows could no longer be registered. Nothing was registered."
    },
    "finishedAt": "2026-10-06T08:15:09.000Z",
    "registered": 250,
    "startedAt": "2026-10-06T08:15:04.000Z",
    "status": "queued",
    "students": [
      {
        "email": "jane.doe@example.com",
        "error": {
          "code": "email_taken_by_your_student",
          "duplicateOf": 12,
          "message": "One of your students already has this email address.",
          "studentId": "6650a1b2c3d4e5f6a7b8c9d0"
        },
        "externalRef": "roster-2026-114",
        "row": 0,
        "status": "registered",
        "studentId": "6650a1b2c3d4e5f6a7b8c9d0"
      }
    ],
    "total": 250
  },
  "message": "Import accepted. The students are registered in the background; read the import to follow it.",
  "success": true
}

Example request

# $TOKEN: a short-lived token you minted with your API key and secret
curl -sS -X POST 'https://api.main-team.org/v1/student/import' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "students": [
    {
      "firstName": "Jane",
      "lastName": "Doe",
      "birth": "14/05/2008",
      "sex": "f",
      "email": "jane.doe@example.com",
      "email2": "<email2>",
      "phone": "+1 555 0100",
      "country": "6650a1b2c3d4e5f6a7b8c9d1",
      "grade": "10",
      "school": "Springfield High School",
      "city": "Springfield",
      "activatedPlatformsThisSeason": [
        "common"
      ],
      "externalRef": "roster-2026-114"
    }
  ],
  "clientReference": "year-10-autumn-2026"
}
JSON

Search the API documentation

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