GraphQL API Reference

The B12 SIS provides a read-only GraphQL API for flexible data querying. This API allows you to fetch exactly the data you need in a single request, with support for filtering, pagination, sorting, and nested relationships.

Overview

Feature

Description

Endpoint

/graphql

Methods

POST, GET

Authentication

JWT Bearer token required

Operations

Query only (read-only API)

Playground

/playground (development only)

Note

The GraphQL API is read-only. All create, update, and delete operations should use the REST API.

Quick Start

1. Authentication

Include your JWT token and team ID in request headers:

curl -X POST https://api.yourschool.edu/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "X-Team-ID: YOUR_TEAM_ID" \
  -d '{"query": "{ students { nodes { id firstName lastName } } }"}'

2. Basic Query

query {
  students {
    nodes {
      id
      code
      firstName
      lastName
      email
      status
    }
    pageInfo {
      totalCount
      totalPages
      currentPage
      hasNextPage
    }
  }
}

3. Query with Variables

query GetStudent($id: ID!) {
  student(id: $id) {
    id
    firstName
    lastName
    email
    currentClass {
      name
      code
    }
  }
}

Variables:

{
  "id": "550e8400-e29b-41d4-a716-446655440000"
}

Request Headers

Header

Required

Description

Authorization

Yes

JWT Bearer token: Bearer <token>

X-Team-ID

Yes

Team/Campus UUID for multi-tenancy

Content-Type

Yes

Must be application/json

Accept-Language

No

Language preference (en, vi)


Available Queries

Academic Structure

Query

Single

Description

academicSessions

academicSession(id)

School years (e.g., 2024-2025)

semesters

semester(id)

Terms within academic sessions

classes

class(id)

Homeroom classes

courses

course(id)

Subject courses

subjects

subject(id)

Subject definitions

programs

program(id)

Educational programs

gradeLevels

gradeLevel(id)

Grade levels (K-12)

educationLevels

educationLevel(id)

Education level groupings

Personnel

Query

Single

Description

students

student(id)

Student records

teachers

teacher(id)

Teacher/staff records

users

user(id)

System user accounts

guardians

guardian(id)

Parent/guardian records

Assessment & Grading

Query

Single

Description

gradebooks

gradebook(id)

Semester gradebooks

gradebookCourses

gradebookCourse(id)

Course-level grades

assessments

assessment(id)

Assignments, tests, quizzes

assessmentResults

assessmentResult(id)

Student scores

gradeScales

gradeScale(id)

Grading scale definitions

Attendance

Query

Single

Description

attendances

attendance(id)

Daily attendance records

attendanceConfigs

attendanceConfig(id)

Attendance status configurations

Enrollment

Query

Single

Description

studentEnrollClasses

studentEnrollClass(id)

Class enrollments

studentEnrollCourses

studentEnrollCourse(id)

Course enrollments

Timetabling

Query

Single

Description

timetables

timetable(id)

Timetable definitions

periods

period(id)

Class periods

rooms

room(id)

Rooms/facilities

Communication

Query

Single

Description

behaviors

behavior(id)

Student behavior records

emailTemplates

emailTemplate(id)

Email templates

System Configuration

Query

Single

Description

roles

role(id)

User roles

teams

team(id)

Teams/campuses

fieldDefs

fieldDef(id)

Dynamic field definitions

auditLogs

auditLog(id)

System audit logs

Other

Query

Single

Description

facilities

facility(id)

Campus facilities

holidays

holiday(id)

Holiday definitions

personRelationships

personRelationship(id)

Guardian-student relationships

leads

lead(id)

CRM leads


Pagination

All list queries support pagination via the pagination argument.

Input

input PaginationInput {
  page: Int = 1      # Page number (1-indexed)
  limit: Int = 20    # Items per page (max: 100)
}

Example

query {
  students(pagination: { page: 2, limit: 25 }) {
    nodes {
      id
      firstName
      lastName
    }
    pageInfo {
      totalCount
      totalPages
      currentPage
      hasNextPage
      hasPreviousPage
    }
  }
}

Response

{
  "data": {
    "students": {
      "nodes": [...],
      "pageInfo": {
        "totalCount": 150,
        "totalPages": 6,
        "currentPage": 2,
        "hasNextPage": true,
        "hasPreviousPage": true
      }
    }
  }
}

Sorting

All list queries support sorting via the sort argument.

Input

input SortInput {
  field: String!           # Field name (camelCase)
  order: SortOrder = ASC   # ASC or DESC
}

Example

query {
  students(
    sort: { field: "createdAt", order: DESC }
    pagination: { limit: 10 }
  ) {
    nodes {
      id
      firstName
      lastName
      createdAt
    }
  }
}

Common Sort Fields

Field

Description

createdAt

Record creation date (default)

updatedAt

Last update date

name

Name field

code

Code field

status

Status field


Filtering

The GraphQL API supports complex filtering with AND/OR logic.

Filter Structure

input FilterGroup {
  group: FilterGroupType!    # AND or OR
  conditions: [FilterCondition!]
  groups: [FilterGroup!]     # Nested groups
}

input FilterCondition {
  field: String!
  operator: FilterOperator!
  value: String              # Single value
  values: [String!]          # Multiple values (for IN, NOT_IN)
}

Available Operators

Operator

Description

Example

EQ

Equal

status = "active"

NE

Not equal

status != "inactive"

GT

Greater than

enrollmentYear > 2020

GTE

Greater than or equal

enrollmentYear >= 2020

LT

Less than

enrollmentYear < 2024

LTE

Less than or equal

enrollmentYear <= 2024

CONTAINS

Contains substring

name LIKE "%john%"

STARTS_WITH

Starts with

name LIKE "john%"

ENDS_WITH

Ends with

name LIKE "%son"

IN

In list

status IN ["active", "alumni"]

NOT_IN

Not in list

status NOT IN ["withdrawn"]

IS_NULL

Is null

deletedAt IS NULL

IS_NOT_NULL

Is not null

email IS NOT NULL

Filter Examples

Simple Filter

query {
  students(
    filter: {
      group: AND
      conditions: [
        { field: "status", operator: EQ, value: "active" }
      ]
    }
  ) {
    nodes {
      id
      firstName
      lastName
      status
    }
  }
}

Multiple Conditions (AND)

query {
  students(
    filter: {
      group: AND
      conditions: [
        { field: "status", operator: EQ, value: "active" }
        { field: "enrollmentYear", operator: GTE, value: "2020" }
      ]
    }
  ) {
    nodes {
      id
      firstName
      enrollmentYear
    }
  }
}

OR Conditions

query {
  students(
    filter: {
      group: OR
      conditions: [
        { field: "status", operator: EQ, value: "active" }
        { field: "status", operator: EQ, value: "alumni" }
      ]
    }
  ) {
    nodes {
      id
      status
    }
  }
}

Using IN Operator

query {
  students(
    filter: {
      group: AND
      conditions: [
        { field: "status", operator: IN, values: ["active", "alumni"] }
      ]
    }
  ) {
    nodes {
      id
      status
    }
  }
}

Search by Name (CONTAINS)

query {
  students(
    filter: {
      group: AND
      conditions: [
        { field: "firstName", operator: CONTAINS, value: "john" }
      ]
    }
  ) {
    nodes {
      id
      firstName
      lastName
    }
  }
}

Nested AND/OR Groups

query {
  students(
    filter: {
      group: AND
      conditions: [
        { field: "status", operator: EQ, value: "active" }
      ]
      groups: [
        {
          group: OR
          conditions: [
            { field: "enrollmentYear", operator: EQ, value: "2023" }
            { field: "enrollmentYear", operator: EQ, value: "2024" }
          ]
        }
      ]
    }
  ) {
    nodes {
      id
      firstName
      enrollmentYear
    }
  }
}

Entity Schemas

Student

type Student {
  # Identity
  id: ID!
  code: String!
  moetId: String
  status: String!

  # Personal Info
  firstName: String
  lastName: String
  fullNameLastFirst: String
  fullNameFirstLast: String
  email: String
  phone: String
  avatar: String
  gender: String
  religion: String
  personalIdNumber: String
  birthDate: Date
  nationality: String
  ethnicity: String

  # Enrollment
  enrollmentDate: Date
  enrollmentYear: Int
  source: String

  # App/LMS Access
  appActive: Boolean
  appUser: String
  appLastLogin: Time
  lmsActive: Boolean
  lmsId: String
  lmsUser: String

  # Addresses
  permanentAddress: Address
  currentAddress: Address
  birthPlaceAddress: Address
  birthRegisAddress: Address

  # Relationships
  currentClass: Class
  currentClassId: ID
  program: Program
  programId: ID
  user: User
  userId: ID

  # Collections
  behaviors: [Behavior!]!
  attendances: [Attendance!]!
  gradebooks: [Gradebook!]!
  studentEnrollClasses: [StudentEnrollClass!]!
  studentEnrollCourses: [StudentEnrollCourse!]!
  guardians: [PersonRelationship!]!

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Teacher

type Teacher {
  # Identity
  id: ID!
  code: String!
  type: String!
  status: String!

  # Personal Info
  firstName: String
  lastName: String
  fullNameLastFirst: String
  fullNameFirstLast: String
  email: String
  phone: String
  avatar: String
  gender: String
  religion: String
  personalIdNumber: String
  birthDate: Date
  nationality: String
  ethnicity: String

  # Addresses
  permanentAddress: Address
  currentAddress: Address

  # Relationships
  user: User
  userId: ID

  # Collections
  workExperiences: [WorkExperience!]!
  certifications: [Certification!]!
  educationQualifications: [EducationQualification!]!
  classes: [Class!]!

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Class

type Class {
  id: ID!
  name: String
  description: String
  code: String!
  status: String!

  # Relationships
  academicSession: AcademicSession
  academicSessionId: ID
  gradeLevel: GradeLevel
  gradeLevelId: ID
  program: Program
  programId: ID
  facility: Facility
  facilityId: ID
  primaryTeacher: Teacher
  primaryTeacherId: ID
  teacherAssistants: [Teacher!]!
  studentEnrollClasses: [StudentEnrollClass!]!
  timetables: [Timetable!]!

  # Computed
  studentCount: Int

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Course

type Course {
  id: ID!
  name: String
  description: String
  code: String!
  shortName: String
  status: String!
  type: String!

  # Canvas Integration
  canvasId: String
  needCanvasSync: Boolean

  # Relationships
  academicSession: AcademicSession
  academicSessionId: ID
  program: Program
  programId: ID
  educationLevel: EducationLevel
  educationLevelId: ID
  subject: Subject
  subjectId: ID
  semester: Semester
  semesterId: ID
  primaryTeacher: Teacher
  primaryTeacherId: ID
  teacherAssistants: [Teacher!]!
  studentEnrollCourses: [StudentEnrollCourse!]!
  assessments: [Assessment!]!

  # Computed
  studentCount: Int

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Assessment

type Assessment {
  id: ID!
  name: String
  description: String
  title: String!
  type: String!
  category: String
  weight: Float
  totalPoints: Float!
  dueDate: Time
  assessmentDate: Time
  isPublished: Boolean!
  allowLateSubmission: Boolean
  latePenalty: Float
  instructions: String
  rubric: JSON
  isLocked: Boolean!
  noShowReport: Boolean

  # Canvas Integration
  canvasAssignmentId: String
  canvasAssignmentGroupId: String
  canvasAssignmentGroupName: String
  canvasGroupWeight: Float
  omitFromFinalGrade: Boolean

  # Relationships
  course: Course
  courseId: ID
  gradeScale: GradeScale
  gradeScaleId: ID
  assessmentResults: [AssessmentResult!]!

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Gradebook

type Gradebook {
  id: ID!
  name: String
  description: String
  overallScore: Float
  overallGrade: String
  gpa: Float
  totalCredits: Float
  earnedCredits: Float
  status: String!
  isPublished: Boolean!
  publishedAt: Time
  comments: String
  lastCalculatedAt: Time
  lastGenReportAt: Time
  reportCardFile: String
  isLocked: Boolean!

  # Relationships
  student: Student
  studentId: ID
  semester: Semester
  semesterId: ID
  academicSession: AcademicSession
  academicSessionId: ID
  gradebookCourses: [GradebookCourse!]!

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Behavior

type Behavior {
  id: ID!
  name: String
  description: String
  type: String!
  category: String
  points: Int
  date: Date
  time: Time
  notes: String
  isNotified: Boolean
  notifiedAt: Time

  # Relationships
  student: Student
  studentId: ID
  students: [Student!]!        # Many-to-many for group behaviors
  academicSession: AcademicSession
  academicSessionId: ID
  class: Class
  classId: ID
  emailTemplate: EmailTemplate
  emailTemplateId: ID

  # Metadata
  createdAt: Time
  updatedAt: Time
  teamId: ID
  createdBy: User
  updatedBy: User
  team: Team
}

Complex Query Examples

Dashboard Statistics

query Dashboard {
  students(filter: { group: AND, conditions: [{ field: "status", operator: EQ, value: "active" }] }) {
    pageInfo { totalCount }
  }

  teachers(filter: { group: AND, conditions: [{ field: "status", operator: EQ, value: "working" }] }) {
    pageInfo { totalCount }
  }

  classes {
    pageInfo { totalCount }
  }

  courses {
    pageInfo { totalCount }
  }
}

Student with Full Details

query GetStudentDetails($id: ID!) {
  student(id: $id) {
    id
    code
    firstName
    lastName
    email
    phone
    status
    enrollmentYear
    birthDate
    gender

    currentClass {
      id
      name
      code
      gradeLevel {
        name
      }
    }

    program {
      id
      name
    }

    guardians {
      id
      relationship
      isPrimary
    }

    gradebooks {
      id
      overallScore
      overallGrade
      semester {
        name
      }
    }
  }
}

Class Roster with Students

query GetClassRoster($classId: ID!) {
  class(id: $classId) {
    id
    name
    code

    primaryTeacher {
      firstName
      lastName
    }

    gradeLevel {
      name
    }

    studentEnrollClasses {
      student {
        id
        code
        firstName
        lastName
        email
        status
      }
      enrollmentDate
      status
    }
  }
}

Course with Assessments and Results

query GetCourseAssessments($courseId: ID!) {
  course(id: $courseId) {
    id
    name
    code

    primaryTeacher {
      firstName
      lastName
    }

    assessments {
      id
      title
      type
      totalPoints
      dueDate
      isPublished

      assessmentResults {
        student {
          id
          firstName
          lastName
        }
        score
        grade
        submittedAt
      }
    }
  }
}

Teachers with Their Classes and Courses

query GetTeacherSchedule($teacherId: ID!) {
  teacher(id: $teacherId) {
    id
    firstName
    lastName
    email

    classes {
      id
      name
      code
      gradeLevel {
        name
      }
      studentCount
    }
  }

  courses(
    filter: {
      group: AND
      conditions: [
        { field: "primaryTeacherId", operator: EQ, value: $teacherId }
      ]
    }
  ) {
    nodes {
      id
      name
      code
      subject {
        name
      }
      studentCount
    }
  }
}

Attendance Report

query GetAttendanceReport($classId: ID!, $startDate: String!, $endDate: String!) {
  attendances(
    filter: {
      group: AND
      conditions: [
        { field: "classId", operator: EQ, value: $classId }
        { field: "date", operator: GTE, value: $startDate }
        { field: "date", operator: LTE, value: $endDate }
      ]
    }
    sort: { field: "date", order: ASC }
  ) {
    nodes {
      id
      date
      status
      checkInTime
      checkOutTime
      notes

      student {
        id
        code
        firstName
        lastName
      }
    }
    pageInfo {
      totalCount
    }
  }
}

Gradebook Report

query GetGradebookReport($semesterId: ID!) {
  gradebooks(
    filter: {
      group: AND
      conditions: [
        { field: "semesterId", operator: EQ, value: $semesterId }
        { field: "isPublished", operator: EQ, value: "true" }
      ]
    }
    sort: { field: "overallScore", order: DESC }
  ) {
    nodes {
      id
      overallScore
      overallGrade
      gpa

      student {
        id
        code
        firstName
        lastName
        currentClass {
          name
        }
      }

      gradebookCourses {
        score
        grade
        course {
          name
          subject {
            name
          }
        }
      }
    }
    pageInfo {
      totalCount
    }
  }
}

Using GraphQL Clients

JavaScript (fetch)

async function graphqlQuery(query, variables = {}) {
  const response = await fetch('/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${localStorage.getItem('token')}`,
      'X-Team-ID': localStorage.getItem('teamId')
    },
    body: JSON.stringify({ query, variables })
  });

  const result = await response.json();

  if (result.errors) {
    throw new Error(result.errors[0].message);
  }

  return result.data;
}

// Usage
const data = await graphqlQuery(`
  query GetStudents($limit: Int!) {
    students(pagination: { limit: $limit }) {
      nodes {
        id
        firstName
        lastName
      }
      pageInfo {
        totalCount
      }
    }
  }
`, { limit: 10 });

Angular (Apollo Angular)

import { Apollo, gql } from 'apollo-angular';

@Injectable()
export class StudentService {
  constructor(private apollo: Apollo) {}

  getStudents(limit: number) {
    return this.apollo.watchQuery({
      query: gql`
        query GetStudents($limit: Int!) {
          students(pagination: { limit: $limit }) {
            nodes {
              id
              firstName
              lastName
            }
            pageInfo {
              totalCount
            }
          }
        }
      `,
      variables: { limit }
    }).valueChanges;
  }
}

cURL

# Simple query
curl -X POST https://api.yourschool.edu/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Team-ID: YOUR_TEAM_ID" \
  -d '{"query": "{ students(pagination: { limit: 5 }) { nodes { id firstName lastName } } }"}'

# Query with variables
curl -X POST https://api.yourschool.edu/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Team-ID: YOUR_TEAM_ID" \
  -d '{
    "query": "query GetStudent($id: ID!) { student(id: $id) { id firstName lastName email } }",
    "variables": {"id": "550e8400-e29b-41d4-a716-446655440000"}
  }'

GraphQL Playground

Access the interactive GraphQL IDE at /playground (development mode only).

Features

  • Auto-complete for queries

  • Schema documentation browser

  • Query history

  • Variables panel

  • Headers configuration

Enabling Playground

Set DEV_MODE=true in your environment to enable the playground.

Warning

The playground is disabled in production for security reasons.


Error Handling

Error Response Format

{
  "errors": [
    {
      "message": "record not found",
      "path": ["student"],
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ],
  "data": {
    "student": null
  }
}

Common Error Codes

Code

Description

UNAUTHENTICATED

Missing or invalid JWT token

FORBIDDEN

Insufficient permissions

NOT_FOUND

Resource not found

BAD_REQUEST

Invalid query or variables

INTERNAL_ERROR

Server error


Best Practices

1. Request Only What You Need

# Good - Only request needed fields
query {
  students {
    nodes {
      id
      firstName
      lastName
    }
  }
}

# Avoid - Requesting all fields unnecessarily
query {
  students {
    nodes {
      id
      code
      firstName
      lastName
      email
      phone
      avatar
      # ... many more fields
    }
  }
}

2. Use Pagination for Large Datasets

query {
  students(pagination: { page: 1, limit: 50 }) {
    nodes { id firstName lastName }
    pageInfo { totalCount hasNextPage }
  }
}

3. Use Variables for Dynamic Values

# Good - Using variables
query GetStudentsByStatus($status: String!) {
  students(
    filter: {
      group: AND
      conditions: [{ field: "status", operator: EQ, value: $status }]
    }
  ) {
    nodes { id firstName }
  }
}

5. Use Fragments for Reusable Fields

fragment StudentBasicInfo on Student {
  id
  code
  firstName
  lastName
  email
  status
}

query {
  students {
    nodes {
      ...StudentBasicInfo
    }
  }
}

Rate Limiting

The GraphQL API shares rate limits with the REST API:

  • 100 requests per minute per user

  • 1000 requests per hour per user

Exceeding limits returns a 429 Too Many Requests response.


Multi-tenancy

Data is automatically filtered by team:

  1. X-Team-ID header identifies the current team

  2. All queries filter by team and child teams

  3. Users only see data they have permission to access


Comparison: GraphQL vs REST API

Feature

GraphQL

REST API

Use Case

Flexible data fetching

CRUD operations

Operations

Read-only (Query)

Create, Read, Update, Delete

Response

Exactly what you request

Fixed structure

Multiple Resources

Single request

Multiple requests

Filtering

Built-in with operators

POST body filter

Nested Data

Native support

Preloads parameter

Recommendation: Use GraphQL for complex read queries and dashboards. Use REST API for all write operations.