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
| Code | Description | How to Fix |
|---|---|---|
UNAUTHENTICATED | Missing or invalid access token | Login to get a new access token, or refresh your expired token |
FORBIDDEN | User not assigned to a school | Contact your administrator to assign a school to your account |
Invalid credentials | Wrong username or password | Check your credentials and try again |
Invalid or expired refresh token | Refresh token is no longer valid | Login 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.