NextSchoolNextSchool Data API

Error Handling

Error codes and how to handle API errors

Error Handling

The API returns errors in the standard GraphQL error format.

Error Response Format

{
  "errors": [
    {
      "message": "Authentication required",
      "locations": [{ "line": 1, "column": 3 }],
      "path": ["students"],
      "extensions": {
        "code": "UNAUTHENTICATED"
      }
    }
  ]
}

Error Codes

CodeDescriptionHow to Fix
UNAUTHENTICATEDMissing or invalid access tokenLogin to get a new access token, or refresh your expired token
FORBIDDENUser not assigned to a schoolContact your administrator to assign a school to your account
Invalid credentialsWrong username or passwordCheck your credentials and try again
Invalid or expired refresh tokenRefresh token is no longer validLogin again to get new tokens

Common Scenarios

Expired Access Token

The access token expires after 15 minutes. When you get an UNAUTHENTICATED error, refresh the token:

# Make a request — if UNAUTHENTICATED, refresh and retry
RESPONSE=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d "{\"query\": \"$QUERY\"}")

CODE=$(echo "$RESPONSE" | jq -r '.errors[0].extensions.code // empty')

if [ "$CODE" = "UNAUTHENTICATED" ]; then
  # Refresh the token
  REFRESH_RESPONSE=$(curl -s -X POST "$API_URL" \
    -H "Content-Type: application/json" \
    -d "{\"query\": \"mutation { refreshToken(refreshToken: \\\"$REFRESH_TOKEN\\\") { accessToken } }\"}")
  ACCESS_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refreshToken.accessToken')

  # Retry with new token
  RESPONSE=$(curl -s -X POST "$API_URL" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -d "{\"query\": \"$QUERY\"}")
fi

echo "$RESPONSE" | jq .
async function makeRequest(query, accessToken, refreshToken) {
  let response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken}`
    },
    body: JSON.stringify({ query })
  });

  let result = await response.json();

  // Check if token expired
  if (result.errors?.[0]?.extensions?.code === 'UNAUTHENTICATED') {
    // Refresh the token
    const { accessToken: newToken } = await refreshAccessToken(refreshToken);

    // Retry with new token
    response = await fetch(API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${newToken}`
      },
      body: JSON.stringify({ query })
    });
    result = await response.json();
  }

  return result;
}
import requests

def make_request(query: str, access_token: str, refresh_token: str):
    resp = requests.post(API_URL, headers={
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {access_token}',
    }, json={'query': query})

    result = resp.json()

    # Check if token expired
    errors = result.get('errors', [])
    if errors and errors[0].get('extensions', {}).get('code') == 'UNAUTHENTICATED':
        # Refresh the token
        new_token = refresh_access_token(refresh_token)

        # Retry with new token
        resp = requests.post(API_URL, headers={
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {new_token}',
        }, json={'query': query})
        result = resp.json()

    return result
<?php
function makeRequest(string $query, string $accessToken, string $refreshToken): array {
    $result = graphqlRequest($query, $accessToken);

    // Check if token expired
    $code = $result['errors'][0]['extensions']['code'] ?? null;
    if ($code === 'UNAUTHENTICATED') {
        // Refresh the token
        $newToken = refreshAccessToken($refreshToken);

        // Retry with new token
        $result = graphqlRequest($query, $newToken);
    }

    return $result;
}

function graphqlRequest(string $query, string $token): array {
    $ch = curl_init(API_URL);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            "Authorization: Bearer {$token}",
        ],
        CURLOPT_POSTFIELDS => json_encode(['query' => $query]),
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $data = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $data;
}
func makeRequest(query, accessToken, refreshToken string) (map[string]any, error) {
	result, resp, err := graphqlRequest(query, accessToken)
	if err != nil {
		return nil, err
	}

	// Check if token expired
	if errors, ok := result["errors"].([]any); ok && len(errors) > 0 {
		errMap := errors[0].(map[string]any)
		if ext, ok := errMap["extensions"].(map[string]any); ok {
			if ext["code"] == "UNAUTHENTICATED" {
				// Refresh the token
				newToken, err := refreshAccessToken(refreshToken)
				if err != nil {
					return nil, err
				}
				// Retry with new token
				result, _, err = graphqlRequest(query, newToken)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	_ = resp
	return result, nil
}
async fn make_request(
    client: &Client,
    query: &str,
    access_token: &str,
    refresh_token: &str,
) -> Result<Value, Box<dyn std::error::Error>> {
    let mut result = graphql_request(client, query, access_token).await?;

    // Check if token expired
    if let Some(errors) = result["errors"].as_array() {
        if let Some(first) = errors.first() {
            let code = first["extensions"]["code"].as_str().unwrap_or("");
            if code == "UNAUTHENTICATED" {
                // Refresh the token
                let new_token = refresh_access_token(client, refresh_token).await?;

                // Retry with new token
                result = graphql_request(client, query, &new_token).await?;
            }
        }
    }

    Ok(result)
}

Expired Refresh Token

Refresh tokens expire after 7 days. When the refresh token is invalid, you need to login again:

# Try refreshing — if it fails, login again
REFRESH_RESPONSE=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -d "{\"query\": \"mutation { refreshToken(refreshToken: \\\"$REFRESH_TOKEN\\\") { accessToken } }\"}")

NEW_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refreshToken.accessToken // empty')

if [ -z "$NEW_TOKEN" ]; then
  # Refresh token expired, login again
  LOGIN_RESPONSE=$(curl -s -X POST "$API_URL" \
    -H "Content-Type: application/json" \
    -d "{\"query\": \"mutation { login(username: \\\"$USERNAME\\\", password: \\\"$PASSWORD\\\") { accessToken refreshToken } }\"}")
  NEW_TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.login.accessToken')
fi

echo "Token: $NEW_TOKEN"
async function getValidToken(refreshToken, username, password) {
  try {
    const result = await refreshAccessToken(refreshToken);
    return result.accessToken;
  } catch {
    // Refresh token expired, login again
    const { accessToken } = await login(username, password);
    return accessToken;
  }
}
def get_valid_token(refresh_token: str, username: str, password: str) -> str:
    try:
        result = refresh_access_token(refresh_token)
        return result['accessToken']
    except Exception:
        # Refresh token expired, login again
        result = login(username, password)
        return result['accessToken']
<?php
function getValidToken(string $refreshToken, string $username, string $password): string {
    try {
        $result = refreshAccessToken($refreshToken);
        return $result['accessToken'];
    } catch (\Exception $e) {
        // Refresh token expired, login again
        $result = login($username, $password);
        return $result['accessToken'];
    }
}
func getValidToken(refreshToken, username, password string) (string, error) {
	token, err := refreshAccessToken(refreshToken)
	if err == nil {
		return token, nil
	}

	// Refresh token expired, login again
	token, err = login(username, password)
	if err != nil {
		return "", err
	}
	return token, nil
}
async fn get_valid_token(
    client: &Client,
    refresh_token: &str,
    username: &str,
    password: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    match refresh_access_token(client, refresh_token).await {
        Ok(token) => Ok(token),
        Err(_) => {
            // Refresh token expired, login again
            login(client, username, password).await
        }
    }
}

GraphQL Validation Errors

If your query has syntax errors, you'll get a validation error:

{
  "errors": [
    {
      "message": "Cannot query field \"nonExistentField\" on type \"Student\".",
      "locations": [{ "line": 1, "column": 30 }]
    }
  ]
}

Check the API Reference for available fields.

On this page