# List the exams one of your students can apply to

- Endpoint: `GET /v1/{organizationId}/exam/available/{studentId}`
- Production: `https://api.main-team.org/v1/{organizationId}/exam/available/{studentId}`
- Sandbox: `https://apisnd.main-team.org/v1/{organizationId}/exam/available/{studentId}`
- Operation: `listAvailableExams` (Exams)
- Authentication: `Authorization: Bearer <token>`, a short-lived token you sign with your API key and secret
- Permission: `exam/read:$org:$ID`

## Description

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 `listExams` lists it. An exam is **open for applications** while all three hold: it is not closed to applications (`preventApplication` is not `true`), 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

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `organizationId` | path | yes | string | The organization’s `_id`: 24 hexadecimal digits, as `listOrganizations` (`GET /v1/organization`) lists it. |
| `studentId` | path | yes | string | The `_id` of one of your students, as `registerStudent` answered and `listStudents` lists it. |

## Response

`200` Success: `message` is "Available exams fetched successfully.".

```json
{
  "success": true,
  "message": "Available exams fetched successfully.",
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 57,
    "totalPages": 3
  },
  "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"
              }
            }
          ]
        }
      ]
    }
  ]
}
```

## Errors

| Status | Code | When |
| --- | --- | --- |
| 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) | The student has no grade. Every exam is restricted to a set of grades, so no exam could be offered; set one first. |
| 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 `exam/read` on the organization in the path, or a role denies it. |
| 404 | [`not_found`](https://hub.main-team.org/api/errors#not_found) | `organizationId` is not the `_id` of an organization. |
| 404 | [`not_found`](https://hub.main-team.org/api/errors#not_found) | No student of yours has this id: it is nobody’s, or another account’s. Both answer the same. |
| 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 'https://api.main-team.org/v1/<organizationId>/exam/available/<studentId>' \
  -H "Authorization: Bearer $TOKEN"
```

### 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/<organizationId>/exam/available/<studentId>', {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});
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/<organizationId>/exam/available/<studentId>');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $token,
    ],
    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']);
```
