# List an organization’s exam categories

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

## Description

Lists every exam category the organization has, active or not: `isActive` says which. No exam in an inactive category is open for applications.

Categories belong to the organization, not to your account, so every account allowed to read them sees the same list. The order is not specified; sort by `order` yourself if you show them.

A page holds 20 categories unless you ask for up to 100 with `limit`. 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. |
| `page` | query | no | number | Page number. Defaults to 1. |
| `limit` | query | no | number | Items per page. Defaults to 20, max 100. |

## Response

`200` Success: `message` is "Categories fetched successfully.".

```json
{
  "success": true,
  "message": "Categories 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"
    }
  ]
}
```

## Errors

| Status | Code | When |
| --- | --- | --- |
| 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-category/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. |
| 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-category?page=1&limit=20' \
  -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-category?page=1&limit=20', {
  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, body.pagination);
```

### 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-category?page=1&limit=20');
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']);
print_r($body['pagination']);
```
