Guide
Pagination
Lists come a page at a time with a cursor to the next page. Cursors stay correct while records are added, so you never skip or repeat one.
Cursors
Lists return { data, nextCursor, hasMore }. Ask for up to 200 records with limit (default 50) and pass nextCursor back as cursor for the next page, until hasMore is false. Lists of students, families and staff also take updatedSince to fetch only what changed.
curl "https://api.school360.cloud/v1/students?status=active&limit=200" \
-H "Authorization: Bearer $SCHOOL360_KEY"
# → { "data": [...], "nextCursor": "eyJpZCI6MjAwfQ", "hasMore": true }
curl "https://api.school360.cloud/v1/students?status=active&limit=200&cursor=eyJpZCI6MjAwfQ" \
-H "Authorization: Bearer $SCHOOL360_KEY"
# … until "hasMore": falseconst headers = { Authorization: `Bearer ${process.env.SCHOOL360_KEY}` }
// Every student matching params, one page (up to 200) at a time
async function* students(params = {}) {
let cursor = null
do {
const query = new URLSearchParams({ limit: "200", ...params, ...(cursor ? { cursor } : {}) })
const res = await fetch(`https://api.school360.cloud/v1/students?${query}`, { headers })
const page = await res.json()
if (!res.ok) throw new Error(`${res.status} ${page.error.code}`)
yield* page.data
cursor = page.hasMore ? page.nextCursor : null
} while (cursor)
}
for await (const s of students({ status: "active" })) {
console.log(s.admissionNo, s.firstName, s.lastName, s.classId)
}<?php
// Every student matching $params, one page (up to 200) at a time
function students(array $params = []): Generator
{
$cursor = null;
do {
$query = http_build_query(['limit' => 200] + $params + ($cursor ? ['cursor' => $cursor] : []));
$ch = curl_init("https://api.school360.cloud/v1/students?$query");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SCHOOL360_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
if (isset($page['error'])) {
throw new RuntimeException($page['error']['message']);
}
foreach ($page['data'] as $student) {
yield $student;
}
$cursor = $page['hasMore'] ? $page['nextCursor'] : null;
} while ($cursor);
}
foreach (students(['status' => 'active']) as $s) {
echo "{$s['admissionNo']} {$s['firstName']}\n";
}import os
import requests
api = requests.Session()
api.headers["Authorization"] = f"Bearer {os.environ['SCHOOL360_KEY']}"
def students(**params):
"""Every student matching params, one page (up to 200) at a time."""
cursor = None
while True:
query = {"limit": 200, **params, **({"cursor": cursor} if cursor else {})}
res = api.get("https://api.school360.cloud/v1/students", params=query, timeout=30)
res.raise_for_status()
page = res.json()
yield from page["data"]
if not page["hasMore"]:
break
cursor = page["nextCursor"]
for s in students(status="active"):
print(s["admissionNo"], s["firstName"], s["lastName"])Only what changed
Lists of students, families and staff take updatedSince (an ISO date-time): keep the time you started your last sync and pass it next time to fetch only what was added or changed. For deletions and other records, poll GET /changes.
# Only the students added or changed since your last sync
curl "https://api.school360.cloud/v1/students?updatedSince=2026-10-01T00:00:00Z&limit=200" \
-H "Authorization: Bearer $SCHOOL360_KEY"Good to know
- Pass the cursor exactly as you got it; don't build or change one.
- Keep the other parameters (filters, limit) the same while you follow cursors.
- Use limit=200 for bulk syncs: fewer requests, well inside the rate limit.
- Short lists (campuses, sessions, classes, sections, subjects) come in one answer, without paging.