> 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/authentication.md).

# Authentication

Exchange your client credentials for a short-lived Bearer token using the OAuth 2.0 client credentials grant.

The API uses the OAuth 2.0 **client credentials** grant. You hold a `client_id` and `client_secret`, exchange them for a short-lived Bearer token, and send that token on every request. There is no user login, no redirect and no refresh token — this is a machine-to-machine flow.

## Getting Credentials

Credentials are issued per district by Instructure. Ask your MasteryConnect representative or Instructure Support, and state which [scopes](#scopes) your integration needs.

You will receive:

|                 |                                                          |
| --------------- | -------------------------------------------------------- |
| `client_id`     | Identifies your integration. Not secret.                 |
| `client_secret` | Proves it is you. **Secret** — treat it like a password. |

{% hint style="danger" %}
The `client_secret` is shown once, at creation. Store it in a secrets manager, never in source control, a browser, or a mobile app. Anything that holds it can read your district's data. If it leaks, ask Support to revoke the credential — revocation is immediate and invalidates every token already issued from it.
{% endhint %}

Credentials may carry an expiry date. Once past it, the token endpoint returns `expired_client` and no new tokens can be minted.

## Requesting a Token

```bash
curl -X POST https://api.masteryconnect.com/api/v2/token \
  -H 'Content-Type: application/json' \
  -d '{
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET"
      }'
```

All three parameters are required. `grant_type` must be exactly `client_credentials`; any other value is a `422`.

### Response

```json
{
  "access_token": "mc2_a1b2c3d4e5f6...",
  "token_type": "Bearer",
  "scope": "classrooms:read sections:read teachers:read",
  "expires_in": 7200
}
```

| Field          | Meaning                                                |
| -------------- | ------------------------------------------------------ |
| `access_token` | The Bearer token. Send it on every subsequent request. |
| `token_type`   | Always `Bearer`.                                       |
| `scope`        | Space-delimited list of the scopes actually granted.   |
| `expires_in`   | Seconds until the token expires.                       |

{% hint style="info" %}
Check the returned `scope`. It reflects what your credential was granted, which may be narrower than what you asked for. A credential created without any explicit scopes gets **none** and can read nothing — there is no default grant.
{% endhint %}

## Using a Token

Send it in the `Authorization` header:

```bash
curl https://api.masteryconnect.com/api/v2/classrooms \
  -H 'Authorization: Bearer mc2_a1b2c3d4e5f6...'
```

## Token Lifetime

Tokens last **2 hours**, and never outlive the credential that minted them — if your credential expires in 30 minutes, so does a token issued now, and `expires_in` will say `1800`.

Tokens are opaque and verified against the database on every request, so revocation takes effect immediately rather than at expiry.

### Handling Expiry

Reuse a token until it expires; do not fetch a fresh one per request. The token endpoint is [rate limited far more tightly](/services/mastery-connect/limits-policies.md#token-endpoint) than the data endpoints, and a token-per-request pattern will trip it.

The robust pattern:

1. Cache the token and the moment it expires (`now + expires_in`).
2. Reuse it while valid, refreshing a little early — 60 seconds of margin avoids a race with a token expiring mid-flight.
3. On a `401` with code `invalid_token`, fetch a new token once and retry the request. If the retry also `401`s, stop and surface the error — retrying further will not help and will burn your token budget.

## Scopes

Each endpoint requires one scope. All scopes are read-only.

| Scope                   | Grants read access to  |
| ----------------------- | ---------------------- |
| `districts:read`        | The district           |
| `schools:read`          | Schools                |
| `sections:read`         | Sections               |
| `classrooms:read`       | Classrooms             |
| `teachers:read`         | Teachers               |
| `materials:read`        | Materials              |
| `assessments:read`      | Assessments            |
| `items:read`            | Assessment items       |
| `objectives:read`       | Objectives (standards) |
| `class_objectives:read` | Class objectives       |
| `banks:read`            | Item banks             |
| `questions:read`        | Questions              |
| `passages:read`         | Passages               |
| `curriculum_maps:read`  | Curriculum maps        |
| `pathways:read`         | Pathways               |
| `reports:read`          | Reports                |

Request the narrowest set that does the job. A credential scoped to what it actually needs limits the damage if it is ever exposed.

### Nested Endpoints Need the Child's Scope

For a nested route, the scope that matters is the one governing the data being returned — not the parent in the path:

| Endpoint                                    | Required scope     |
| ------------------------------------------- | ------------------ |
| `/api/v2/banks/{bank_id}/questions`         | `questions:read`   |
| `/api/v2/materials/{material_id}/items`     | `items:read`       |
| `/api/v2/curriculum_maps/{id}/objectives`   | `objectives:read`  |
| `/api/v2/trackers/{tracker_id}/assessments` | `assessments:read` |

Calling with only the parent's scope returns `403 insufficient_scope`.

## Authentication Errors

| Status | Code                 | Cause                                                                                            |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------ |
| `401`  | `invalid_token`      | Bad credentials at the token endpoint, or a missing, malformed, expired or revoked Bearer token. |
| `401`  | `expired_client`     | The credential itself has passed its expiry date.                                                |
| `401`  | `revoked_credential` | The credential has been revoked.                                                                 |
| `401`  | `unknown_district`   | The credential's district is no longer resolvable.                                               |
| `403`  | `insufficient_scope` | Valid token, but it lacks the scope this endpoint requires.                                      |
| `403`  | `feature_disabled`   | The API is not enabled for this district.                                                        |
| `422`  | `validation_error`   | `grant_type` missing or not `client_credentials`.                                                |

{% hint style="info" %}
An unknown `client_id` and a wrong `client_secret` return the identical response, so the endpoint cannot be used to discover which client ids exist.
{% endhint %}

See [Errors](/services/mastery-connect/errors.md) for the full envelope.


---

# 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/authentication.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.
