> For the complete documentation index, see [llms.txt](https://developerdocs.instructure.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developerdocs.instructure.com/services/mastery-connect/conventions.md).

# Conventions

The response envelope, pagination, filtering, sorting and embedded relationships — the rules every endpoint follows.

Every endpoint follows the same conventions, so once you can read one response you can read them all.

|              |                                                         |
| ------------ | ------------------------------------------------------- |
| Base URL     | `https://api.masteryconnect.com`                        |
| Path prefix  | `/api/v2`                                               |
| Methods      | `GET` only                                              |
| Content type | `application/json`                                      |
| Envelope     | JSON:API-shaped                                         |
| Timestamps   | ISO 8601, UTC (`2026-09-01T14:32:07.000Z`)              |
| Ids          | **Strings** in `data.id`, integers in filter parameters |

{% hint style="warning" %}
The JSON:API convention is that resource ids are strings, so `data.id` is `"1234"`, not `1234`. Numeric ids inside `attributes` (such as `school_id`) stay numbers. Don't compare the two without casting.
{% endhint %}

## Response Envelope

### A Single Resource

`GET /api/v2/sections/4321`

```json
{
  "data": {
    "id": "4321",
    "type": "section",
    "attributes": {
      "name": "Period 3 Biology",
      "archived": false,
      "course_number": "BIO-101",
      "created_at": "2026-08-14T09:12:44.000Z",
      "updated_at": "2026-08-28T16:03:11.000Z"
    },
    "relationships": {
      "school": { "data": { "id": "5678", "type": "school" } }
    }
  }
}
```

### A Collection

Collections add `meta` and `links`. Note that `data` is an array, and each entry has the same shape as a single resource.

```json
{
  "data": [
    { "id": "4321", "type": "section", "attributes": { "...": "..." } },
    { "id": "4322", "type": "section", "attributes": { "...": "..." } }
  ],
  "meta": {
    "total_count": 137,
    "total_pages": 6,
    "page": 1,
    "page_size": 25
  },
  "links": {
    "first": "https://api.masteryconnect.com/api/v2/sections?page[number]=1&page[size]=25",
    "next":  "https://api.masteryconnect.com/api/v2/sections?page[number]=2&page[size]=25",
    "last":  "https://api.masteryconnect.com/api/v2/sections?page[number]=6&page[size]=25"
  }
}
```

### Members

| Member               | Present on                            | Contains                                               |
| -------------------- | ------------------------------------- | ------------------------------------------------------ |
| `data`               | Always                                | The resource, or an array of them.                     |
| `data.id`            | Always                                | The id, as a string.                                   |
| `data.type`          | Always                                | The resource type, singular (`section`, `material`).   |
| `data.attributes`    | Always                                | The resource's own fields.                             |
| `data.relationships` | When the resource has related records | Linkage only — ids and types, not the related objects. |
| `meta`               | Collections                           | Pagination counts.                                     |
| `links`              | Collections                           | Pagination URLs.                                       |

## Pagination

Every collection is paginated.

| Parameter      | Default | Maximum | Meaning              |
| -------------- | ------- | ------- | -------------------- |
| `page[number]` | `1`     | —       | 1-based page number. |
| `page[size]`   | `25`    | `100`   | Records per page.    |

```bash
curl "https://api.masteryconnect.com/api/v2/sections?page[number]=2&page[size]=100" \
  -H "Authorization: Bearer $MC_TOKEN"
```

Out-of-range values are rejected rather than clamped: `page[size]=500` returns `422`, not a page of 100. A `page[number]` beyond the last page returns `200` with an empty `data` array.

### Paging Correctly

Follow `links.next` until it is absent, rather than incrementing a counter and guessing when to stop:

```bash
url="https://api.masteryconnect.com/api/v2/sections?page[size]=100"
while [ -n "$url" ]; do
  body=$(curl -sS -H "Authorization: Bearer $MC_TOKEN" "$url")
  echo "$body" | jq -c '.data[]'
  url=$(echo "$body" | jq -r '.links.next // empty')
done
```

`links.next` is omitted on the last page; `first` and `last` are always present.

{% hint style="info" %}
Always request `page[size]=100` for bulk reads. It is the same [rate limit](/services/mastery-connect/limits-policies.md) cost as `page[size]=25` but returns four times the data.
{% endhint %}

### Pagination Is Not a Snapshot

Paging is offset-based against live data. If records are created or deleted while you page, a record can appear twice across pages or be skipped. For a consistent extract, sort by a stable column (`sort=created_at`) and de-duplicate by `id` as you go.

## Filtering

Filters use `filter[key]=value`. Each endpoint allows a specific set of keys.

```bash
# Non-archived classrooms at one school
curl "https://api.masteryconnect.com/api/v2/classrooms?filter[school_id]=5678&filter[archived]=false" \
  -H "Authorization: Bearer $MC_TOKEN"
```

Multiple filters combine with AND. Each key takes a single value — there is no `IN`, no range and no negation.

{% hint style="warning" %}
An unrecognised filter key is a `422`, never silently ignored. This is deliberate: a typo like `filter[shcool_id]` fails loudly instead of handing you a full unfiltered result set that looks correct.
{% endhint %}

### Allowed Filters

| Endpoint                  | Filters                                                             |
| ------------------------- | ------------------------------------------------------------------- |
| `/districts`              | —                                                                   |
| `/districts/{id}/schools` | `nces_school_id`                                                    |
| `/sections`               | `school_id`, `archived`                                             |
| `/classrooms`             | `school_id`, `teacher_id`, `archived`                               |
| `/teachers`               | `school_id`                                                         |
| `/materials`              | `subject_id`, `objective_id`, `draft`, `available`, `updated_since` |
| `/banks`                  | `bank_type`, `name`                                                 |
| `/curriculum_maps`        | `privacy_level`                                                     |
| `/class_objectives`       | `available`, `class_objective_type`                                 |
| `/pathways`               | `available`, `subject_id`                                           |
| `/reports/{report_name}`  | Per report — see [Reports](/services/mastery-connect/reports.md)    |

Nested collections (`/banks/{id}/questions`, `/materials/{id}/items`, `/curriculum_maps/{id}/objectives`, `/classrooms/{id}/objectives`, `/trackers/{id}/assessments`) are already scoped by their parent and accept no filters of their own.

`/teachers` intentionally has no `name` filter — a teacher's name is assembled from several columns, so there is no single column to match on.

### Filtering by Modification Time

`filter[updated_since]` takes a full ISO 8601 datetime and returns records modified at or after it — the basis of an incremental sync:

```bash
curl "https://api.masteryconnect.com/api/v2/materials?filter[updated_since]=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer $MC_TOKEN"
```

{% hint style="info" %}
`updated_since` is currently supported on **`/materials`** only. Other resources either have no modification timestamp or have not yet opted in; sending it elsewhere is a `422`. A value that is not a complete ISO 8601 datetime is also a `422` rather than being ignored, so a malformed timestamp cannot silently return your whole dataset.
{% endhint %}

## Sorting

`sort` takes a comma-separated list of columns. Prefix a column with `-` for descending.

```bash
# Newest first
curl "https://api.masteryconnect.com/api/v2/classrooms?sort=-created_at"

# Title A-Z, then newest first within each title
curl "https://api.masteryconnect.com/api/v2/materials?sort=title,-created_at"
```

Like filters, sort columns are allow-listed; an unknown column is a `422`.

| Endpoint                           | Sortable columns                    |
| ---------------------------------- | ----------------------------------- |
| `/districts/{id}/schools`          | `name`, `created_at`                |
| `/sections`                        | `name`, `created_at`                |
| `/classrooms`                      | `title`, `created_at`               |
| `/classrooms/{id}/objectives`      | `name`, `created_at`                |
| `/teachers`                        | `last_name`, `created_at`           |
| `/materials`                       | `title`, `created_at`, `updated_at` |
| `/materials/{id}/items`            | `id`                                |
| `/banks`                           | `name`, `created_at`                |
| `/banks/{id}/questions`            | `created_at`                        |
| `/banks/{id}/passages`             | `created_at`                        |
| `/curriculum_maps`                 | `title`, `created_at`, `updated_at` |
| `/curriculum_maps/{id}/materials`  | `title`, `created_at`, `updated_at` |
| `/curriculum_maps/{id}/objectives` | `name`, `created_at`, `updated_at`  |
| `/class_objectives`                | `name`                              |
| `/pathways`                        | `name`                              |

## Relationships and Embeds

Related data appears in two different places, and the difference matters.

### `relationships` — Linkage Only

`relationships` gives you ids and types, never the related object:

```json
"relationships": {
  "teacher":   { "data": { "id": "77",  "type": "teacher" } },
  "subject":   { "data": { "id": "12",  "type": "subject" } },
  "objective": { "data": { "id": "901", "type": "objective" } }
}
```

To resolve one, call its own endpoint. A to-one relationship with no record is `{ "data": null }`.

### Embeds — The Related Object Inline

Some resources also render related objects *inside* `attributes`, so you get them without a second request. A material, for example, embeds its objective, subject and teacher:

```json
{
  "data": {
    "id": "3001",
    "type": "material",
    "attributes": {
      "title": "Unit 2 Benchmark",
      "objective": {
        "id": "901",
        "name": "RL.9-10.2",
        "description": "Determine a theme or central idea of a text"
      },
      "teacher": { "id": "77", "name": "A. Nguyen" }
    },
    "relationships": {
      "objective": { "data": { "id": "901", "type": "objective" } }
    }
  }
}
```

Note that `objective` appears in both places — embedded under `attributes` and as linkage under `relationships`. That is expected; they are independent.

{% hint style="info" %}
Embeds are fixed by the API, not requested by you. There is no `include` or `fields` parameter — which relationships are embedded is decided per resource, and you get the same shape on every call.
{% endhint %}

Two rules govern which embeds you see:

* **To-one embeds** (a material's teacher) appear on both single-resource and collection responses.
* **To-many embeds** (a material's items) appear only on single-resource responses. Embedding them in a collection would multiply the payload by the page size, so collections omit them.

So if you need every material's items, list the materials and then fetch each one — or use `/materials/{id}/items`, which pages properly. To-many embeds are also count-capped, so a resource with hundreds of children will not return all of them inline; page the nested route when you need the full set.

## Next

* [Errors](/services/mastery-connect/errors.md) — status codes and error codes.
* [Rate Limits & Policies](/services/mastery-connect/limits-policies.md) — budgets and headers.
* [API Reference](/services/mastery-connect/openapi.md) — per-endpoint parameters and schemas.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developerdocs.instructure.com/services/mastery-connect/conventions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
