# Update one of your students

- Endpoint: `PUT /v1/student/{studentId}`
- Production: `https://api.main-team.org/v1/student/{studentId}`
- Sandbox: `https://apisnd.main-team.org/v1/student/{studentId}`
- Operation: `updateStudent` (Students)
- Authentication: `Authorization: Bearer <token>`, a short-lived token you sign with your API key and secret
- Permission: `student/update:$org:$ID`

## Description

Changes the fields you send on one of your students and leaves every other field as it is. Every field is optional here, and each one you send follows the same rules as in `registerStudent`. `password` is not accepted (400): use `setStudentPassword`.

- A `city` or `school` sent by name is looked up within the student’s country and city: the ones in this request, or else the ones already stored.
- Changing `firstName` or `lastName` updates `fullName`.
- `firstName`, `lastName`, `birth` and `sex` can be changed but not cleared: an empty value or `null` is refused. `phone` is cleared with `""`. `birth` has to be a date that exists: `31/02/2008` is refused.
- `activatedPlatformsThisSeason` is added to the stored list, never written over it: nothing is removed, a value already there is not added twice, and `[]` adds nothing.
- **Changing `email` to a different address sets `emailConfirmed` back to `false`.** The student has to confirm the new address, and until they do `setStudentPassword` accepts a password for them again. Sending the address already stored, in any case, is not a change.

A student another account registered answers 404, exactly like one that does not exist. Sending the same body twice has the effect of sending it once. The answer has `country`, `city`, `school`, `grade`, `supervisor` and `partner` as ids; `getStudent` resolves them.

## Parameters

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `studentId` | path | yes | string | The student’s `_id`, as `registerStudent` returned it: 24 hexadecimal digits. A student another account registered answers exactly like one that does not exist. |

## Request body

```json
{
  "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": [
    "neo"
  ]
}
```

## Response

`200` Success: `message` is "Student updated successfully.".

```json
{
  "success": true,
  "message": "Student updated successfully.",
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 57,
    "totalPages": 3
  },
  "data": {
    "_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "mainId": "6650a1b2c3d4e5f6a7b8c9d0",
    "username": "XXB1045",
    "firstName": "Jane",
    "lastName": "Doe",
    "fullName": "Jane Doe",
    "email": "jane.doe@example.com",
    "emailConfirmed": false,
    "phone": "+1 555 0100",
    "birth": "14/05/2008",
    "sex": "f",
    "country": "6650a1b2c3d4e5f6a7b8c9d1",
    "city": "6650a1b2c3d4e5f6a7b8c9d2",
    "school": "6650a1b2c3d4e5f6a7b8c9d3",
    "grade": "6650a1b2c3d4e5f6a7b8c9d4",
    "supervisor": "6650a1b2c3d4e5f6a7b8c9d5",
    "partner": "6650a1b2c3d4e5f6a7b8c9d6",
    "activatedPlatformsThisSeason": [
      "common"
    ],
    "createdAt": "2026-09-01T09:30:00.000Z",
    "updatedAt": "2026-09-02T14:05:00.000Z"
  }
}
```

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | The body is not valid JSON, breaks a field’s rules, or has a field this operation does not accept ("property <name> should not exist"). |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | `studentId` is not 24 hexadecimal digits. |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | `email` is `null`: an address can be changed, not removed. |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | `firstName`, `lastName`, `birth`, `sex`, `phone` or `activatedPlatformsThisSeason` is `null`, or `firstName` or `lastName` is empty. These can be changed, not removed; clear `phone` with `""`. |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | `country`, `grade`, `city` or `school` matches nothing or is `null`, or a `city` or `school` name cannot be looked up because the student has no country, or no city for a school. |
| 401 | [`unauthorized`](https://hub.main-team.org/api/errors#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. |
| 403 | [`forbidden`](https://hub.main-team.org/api/errors#forbidden) | The token is valid, but no role on your account allows `student/update` on `mto`, the organization every operation without `:organizationId` acts on, or a role denies it. |
| 404 | [`not_found`](https://hub.main-team.org/api/errors#not_found) | No student of yours has this `studentId`. A student another account registered answers the same. |
| 409 | [`conflict`](https://hub.main-team.org/api/errors#conflict) | `email` is changed to an address already registered, by your account or another. The message does not say whose it is. |
| 413 | [`payload_too_large`](https://hub.main-team.org/api/errors#payload_too_large) | The body is larger than 100 kB. |
| 415 | [`unsupported_media_type`](https://hub.main-team.org/api/errors#unsupported_media_type) | The body declares a charset that is not a UTF one (send UTF-8), or a `Content-Encoding` other than gzip, deflate or br. |
| 429 | [`too_many_requests`](https://hub.main-team.org/api/errors#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. |
| 500 | [`internal_error`](https://hub.main-team.org/api/errors#internal_error) | Something failed on our side. Retry later, and quote `request_id` if it goes on. |

## Code samples

### curl

```bash
# $TOKEN: a short-lived token you minted with your API key and secret
curl -sS -X PUT 'https://api.main-team.org/v1/student/<studentId>' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "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": [
    "neo"
  ]
}
JSON
```

### Node.js

```js
const token = process.env.TOKEN; // a short-lived token you minted with your API key and secret

const res = await fetch('https://api.main-team.org/v1/student/<studentId>', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "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": [
      "neo"
    ]
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### PHP

```php
<?php
$token = getenv('TOKEN'); // a short-lived token you minted with your API key and secret

$ch = curl_init('https://api.main-team.org/v1/student/<studentId>');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        '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' => [
            'neo',
        ],
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
if ($response === false) {
    throw new RuntimeException(curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$body = json_decode($response, true);
if ($status >= 400) {
    $error = $body['error'];
    throw new RuntimeException("$status {$error['code']}: {$error['message']}");
}
print_r($body['data']);
```
