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(())
}