NextSchoolNextSchool Data API

Full Client

Complete API client with automatic token refresh

Full Example (with Token Refresh)

#!/bin/bash
API_URL="https://data.nextschool.io/"

# Login
LOGIN_RESPONSE=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation { login(username: \"your_username\", password: \"your_password\") { accessToken refreshToken expiresIn } }"
  }')

ACCESS_TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.login.accessToken')
REFRESH_TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.login.refreshToken')

# Query students
STUDENTS=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "query": "{ students(limit: 10) { id fullName classroomName } }"
  }')

echo "$STUDENTS" | jq '.data.students'

# Refresh token when expired
REFRESH_RESPONSE=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -d "{
    \"query\": \"mutation { refreshToken(refreshToken: \\\"$REFRESH_TOKEN\\\") { accessToken expiresIn } }\"
  }")

ACCESS_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refreshToken.accessToken')
const API_URL = 'https://data.nextschool.io/';

class NextSchoolAPI {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
  }

  async login(username, password) {
    const res = await fetch(API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `mutation { login(username: "${username}", password: "${password}") {
          accessToken refreshToken expiresIn
        }}`
      })
    });
    const { data } = await res.json();
    this.accessToken = data.login.accessToken;
    this.refreshToken = data.login.refreshToken;
  }

  async query(graphql, variables = {}) {
    const res = await fetch(API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.accessToken}`
      },
      body: JSON.stringify({ query: graphql, variables })
    });
    const result = await res.json();

    if (result.errors?.[0]?.extensions?.code === 'UNAUTHENTICATED') {
      await this.refresh();
      return this.query(graphql, variables);
    }
    return result.data;
  }

  async refresh() {
    const res = await fetch(API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `mutation { refreshToken(refreshToken: "${this.refreshToken}") {
          accessToken expiresIn
        }}`
      })
    });
    const { data } = await res.json();
    this.accessToken = data.refreshToken.accessToken;
  }
}

// Usage
const api = new NextSchoolAPI();
await api.login('your_username', 'your_password');
const students = await api.query('{ students(limit: 10) { id fullName classroomName } }');
console.log(students);
import requests

class NextSchoolAPI:
    API_URL = 'https://data.nextschool.io/'

    def __init__(self):
        self.access_token = None
        self.refresh_token = None

    def login(self, username: str, password: str):
        result = self._request(f'''mutation {{
            login(username: "{username}", password: "{password}") {{
                accessToken refreshToken expiresIn
            }}
        }}''')
        self.access_token = result['login']['accessToken']
        self.refresh_token = result['login']['refreshToken']

    def query(self, graphql: str, variables: dict = None):
        result = self._request(graphql, variables, auth=True)
        return result

    def _request(self, query: str, variables: dict = None, auth: bool = False):
        headers = {'Content-Type': 'application/json'}
        if auth and self.access_token:
            headers['Authorization'] = f'Bearer {self.access_token}'

        resp = requests.post(self.API_URL, headers=headers, json={
            'query': query,
            'variables': variables or {}
        })
        data = resp.json()

        if 'errors' in data:
            code = data['errors'][0].get('extensions', {}).get('code')
            if code == 'UNAUTHENTICATED' and self.refresh_token:
                self._refresh()
                return self._request(query, variables, auth=True)
            raise Exception(data['errors'][0]['message'])

        return data['data']

    def _refresh(self):
        result = self._request(f'''mutation {{
            refreshToken(refreshToken: "{self.refresh_token}") {{
                accessToken expiresIn
            }}
        }}''')
        self.access_token = result['refreshToken']['accessToken']

# Usage
api = NextSchoolAPI()
api.login('your_username', 'your_password')
students = api.query('{ students(limit: 10) { id fullName classroomName } }')
print(students)
<?php
class NextSchoolAPI {
    private string $apiUrl = 'https://data.nextschool.io/';
    private ?string $accessToken = null;
    private ?string $refreshToken = null;

    public function login(string $username, string $password): void {
        $result = $this->request('mutation {
            login(username: "' . $username . '", password: "' . $password . '") {
                accessToken refreshToken expiresIn
            }
        }');
        $this->accessToken = $result['login']['accessToken'];
        $this->refreshToken = $result['login']['refreshToken'];
    }

    public function query(string $graphql, array $variables = []): array {
        return $this->request($graphql, $variables, true);
    }

    private function request(string $query, array $variables = [], bool $auth = false): array {
        $ch = curl_init($this->apiUrl);
        $headers = ['Content-Type: application/json'];
        if ($auth && $this->accessToken) {
            $headers[] = "Authorization: Bearer {$this->accessToken}";
        }
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_POSTFIELDS => json_encode([
                'query' => $query,
                'variables' => $variables ?: new \stdClass(),
            ]),
            CURLOPT_RETURNTRANSFER => true,
        ]);
        $data = json_decode(curl_exec($ch), true);
        curl_close($ch);

        if (isset($data['errors'])) {
            $code = $data['errors'][0]['extensions']['code'] ?? null;
            if ($code === 'UNAUTHENTICATED' && $this->refreshToken) {
                $this->refresh();
                return $this->request($query, $variables, true);
            }
            throw new \RuntimeException($data['errors'][0]['message']);
        }
        return $data['data'];
    }

    private function refresh(): void {
        $result = $this->request('mutation {
            refreshToken(refreshToken: "' . $this->refreshToken . '") {
                accessToken expiresIn
            }
        }');
        $this->accessToken = $result['refreshToken']['accessToken'];
    }
}

// Usage
$api = new NextSchoolAPI();
$api->login('your_username', 'your_password');
$students = $api->query('{ students(limit: 10) { id fullName classroomName } }');
print_r($students);
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

const apiURL = "https://data.nextschool.io/"

type NextSchoolAPI struct {
    AccessToken  string
    RefreshToken string
}

func (api *NextSchoolAPI) Login(username, password string) error {
    query := fmt.Sprintf(`mutation {
        login(username: "%s", password: "%s") {
            accessToken refreshToken expiresIn
        }
    }`, username, password)
    result, err := api.request(query, false)
    if err != nil {
        return err
    }
    login := result["login"].(map[string]any)
    api.AccessToken = login["accessToken"].(string)
    api.RefreshToken = login["refreshToken"].(string)
    return nil
}

func (api *NextSchoolAPI) Query(graphql string) (map[string]any, error) {
    return api.request(graphql, true)
}

func (api *NextSchoolAPI) request(query string, auth bool) (map[string]any, error) {
    body, _ := json.Marshal(map[string]string{"query": query})
    req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    if auth && api.AccessToken != "" {
        req.Header.Set("Authorization", "Bearer "+api.AccessToken)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result map[string]any
    json.NewDecoder(resp.Body).Decode(&result)

    if errors, ok := result["errors"].([]any); ok && len(errors) > 0 {
        errMap := errors[0].(map[string]any)
        ext := errMap["extensions"].(map[string]any)
        if ext["code"] == "UNAUTHENTICATED" && api.RefreshToken != "" {
            api.refresh()
            return api.request(query, true)
        }
        return nil, fmt.Errorf("%s", errMap["message"])
    }
    return result["data"].(map[string]any), nil
}

func (api *NextSchoolAPI) refresh() {
    query := fmt.Sprintf(`mutation {
        refreshToken(refreshToken: "%s") { accessToken expiresIn }
    }`, api.RefreshToken)
    result, _ := api.request(query, false)
    rt := result["refreshToken"].(map[string]any)
    api.AccessToken = rt["accessToken"].(string)
}

func main() {
    api := &NextSchoolAPI{}
    api.Login("your_username", "your_password")
    students, _ := api.Query(`{ students(limit: 10) { id fullName classroomName } }`)
    fmt.Println(students)
}
use reqwest::Client;
use serde_json::{json, Value};

struct NextSchoolAPI {
    client: Client,
    api_url: String,
    access_token: Option<String>,
    refresh_token: Option<String>,
}

impl NextSchoolAPI {
    fn new() -> Self {
        Self {
            client: Client::new(),
            api_url: "https://data.nextschool.io/".into(),
            access_token: None,
            refresh_token: None,
        }
    }

    async fn login(&mut self, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
        let query = format!(r#"mutation {{
            login(username: "{}", password: "{}") {{
                accessToken refreshToken expiresIn
            }}
        }}"#, username, password);
        let result = self.request(&query, false).await?;
        let login = &result["login"];
        self.access_token = Some(login["accessToken"].as_str().unwrap().into());
        self.refresh_token = Some(login["refreshToken"].as_str().unwrap().into());
        Ok(())
    }

    async fn query(&mut self, graphql: &str) -> Result<Value, Box<dyn std::error::Error>> {
        self.request(graphql, true).await
    }

    async fn request(&mut self, query: &str, auth: bool) -> Result<Value, Box<dyn std::error::Error>> {
        let mut req = self.client.post(&self.api_url)
            .header("Content-Type", "application/json");
        if auth {
            if let Some(ref token) = self.access_token {
                req = req.header("Authorization", format!("Bearer {}", token));
            }
        }
        let resp: Value = req
            .json(&json!({ "query": query }))
            .send().await?
            .json().await?;

        if let Some(errors) = resp["errors"].as_array() {
            if let Some(first) = errors.first() {
                let code = first["extensions"]["code"].as_str().unwrap_or("");
                if code == "UNAUTHENTICATED" && self.refresh_token.is_some() {
                    self.refresh().await?;
                    return self.request(query, true).await;
                }
                return Err(first["message"].as_str().unwrap_or("Unknown error").into());
            }
        }
        Ok(resp["data"].clone())
    }

    async fn refresh(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        let rt = self.refresh_token.clone().unwrap();
        let query = format!(r#"mutation {{
            refreshToken(refreshToken: "{}") {{ accessToken expiresIn }}
        }}"#, rt);
        let result = self.request(&query, false).await?;
        self.access_token = Some(result["refreshToken"]["accessToken"].as_str().unwrap().into());
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut api = NextSchoolAPI::new();
    api.login("your_username", "your_password").await?;
    let students = api.query(r#"{ students(limit: 10) { id fullName classroomName } }"#).await?;
    println!("{:#}", students);
    Ok(())
}

On this page