npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

appiversity-sdk

v1.0.0

Published

Official Node.js SDK for the Appiversity REST API with workflow support and exceptional developer ergonomics

Readme

Appiversity SDK for Node.js

Official Node.js SDK for the Appiversity REST API with exceptional developer ergonomics.

npm version License: MIT Node.js Version

Features

Simple & Intuitive - Clean API design with excellent IntelliSense support via JSDoc 🔒 Type-Safe - Comprehensive JSDoc annotations for IDE autocomplete and type checking 🚀 Modern - Uses native Node.js fetch API (Node 18+) 📦 Minimal Dependencies - Only dotenv for .env file support 🎯 Error Handling - Returns {data, error} objects instead of throwing 🔧 Configurable - .env file, environment variables, or direct configuration 📚 Well Documented - Extensive documentation and examples

Installation

npm install appiversity-sdk

Requirements

  • Node.js 18.0.0 or higher (uses native fetch API)

Quick Start

1. Create a .env File

# .env
APPIVERSITY_SDK_API=your-api-key-here
APPIVERSITY_SDK_INSTITUTION=springfield
APPIVERSITY_SDK_BASE_URL=https://appiversity.com  # optional, this is the default

Or set environment variables:

export APPIVERSITY_SDK_API="your-api-key-here"
export APPIVERSITY_SDK_INSTITUTION="springfield"
export APPIVERSITY_SDK_BASE_URL="https://appiversity.com"  # optional

2. Use the SDK

import { createClient } from 'appiversity-sdk';

// Create client (automatically loads .env file)
const client = createClient();

// Initialize (fetches institution details)
const initResult = await client.initialize();
if (initResult.error) {
  console.error('Failed to initialize:', initResult.error.message);
  process.exit(1);
}

console.log('Connected to:', initResult.data.name);

// Search for students
const result = await client.searchStudents({ search: 'Smith', limit: 10 });
if (result.error) {
  console.error('Error:', result.error.message);
} else {
  console.log('Found', result.data.total_count, 'students');
  result.data.students.forEach(student => {
    console.log(`${student.full_name} (${student.sid})`);
  });
}

Configuration

Option 1: .env File (Recommended)

Create a .env file in your project root:

# .env
APPIVERSITY_SDK_API=your-64-character-api-key
APPIVERSITY_SDK_INSTITUTION=your-institution-slug
APPIVERSITY_SDK_BASE_URL=https://appiversity.com  # optional, defaults to https://appiversity.com
import { createClient } from 'appiversity-sdk';

// Automatically loads .env file
const client = createClient();
await client.initialize();

Option 2: Environment Variables

# Required
export APPIVERSITY_SDK_API="your-64-character-api-key"
export APPIVERSITY_SDK_INSTITUTION="your-institution-slug"

# Optional (defaults to https://appiversity.com if not set)
export APPIVERSITY_SDK_BASE_URL="https://appiversity.com"
import { createClient } from 'appiversity-sdk';

const client = createClient();
await client.initialize();

Option 3: Direct Configuration

import { createClient } from 'appiversity-sdk';

const client = createClient({
  apiKey: 'your-api-key',
  institutionSlug: 'springfield',
  baseUrl: 'https://custom-domain.com'  // optional, defaults to https://appiversity.com
});

await client.initialize();

Option 4: Mix of Configuration Sources

// Override specific values while using .env for others
const client = createClient({
  institutionSlug: 'different-institution'
  // apiKey and baseUrl come from .env file or environment variables
});

Note: The default base URL is https://appiversity.com. Only set APPIVERSITY_SDK_BASE_URL if you're using a custom domain or self-hosted instance.

API Reference

Client Initialization

createClient(config)

Creates an Appiversity SDK client instance.

/**
 * @param {Object} [config] - Configuration object
 * @param {string} [config.apiKey] - API key (defaults to APPIVERSITY_SDK_API)
 * @param {string} [config.institutionSlug] - Institution slug (defaults to APPIVERSITY_SDK_INSTITUTION)
 * @param {string} [config.baseUrl] - Base URL (defaults to APPIVERSITY_SDK_BASE_URL)
 * @returns {AppiversitySDK} SDK instance
 */

client.initialize()

Initializes the SDK by fetching institution details. Must be called before using other methods.

const result = await client.initialize();
// result = { data: {...}, status: 200 } or { error: {...}, status: 404 }

Returns:

{
  data: {
    id: number,
    name: string,
    slug: string,
    domain: string,
    active_year: string
  },
  status: number
}

Response Format

All API methods return an object with either data or error:

// Success
{
  data: {...},      // Response data
  status: 200       // HTTP status code
}

// Error
{
  error: {
    error: string,     // Error type
    message: string,   // Human-readable message
    status: number     // HTTP status code
  },
  status: 400
}

Always check for errors:

const result = await client.searchStudents({ search: 'Smith' });

if (result.error) {
  console.error('Error:', result.error.message);
  return;
}

// Safe to use result.data
console.log(result.data.students);

Institution Methods

listInstitutions()

Get list of all accessible institutions.

const result = await client.listInstitutions();
// result.data.institutions = [{ id, name, slug }, ...]

getInstitutionBySlug(slug)

Get institution details by slug.

const result = await client.getInstitutionBySlug('springfield');
// result.data = { id, name, slug, domain, active_year }

Student Methods

searchStudents(params)

Search for students by name, ID, or email.

/**
 * @param {Object} params
 * @param {string} params.search - Search term (required)
 * @param {number} [params.skip=0] - Results to skip
 * @param {number} [params.limit=10] - Results to return (max 100)
 */

const result = await client.searchStudents({
  search: 'Smith',
  skip: 0,
  limit: 20
});

if (result.data) {
  console.log('Total found:', result.data.total_count);
  result.data.students.forEach(student => {
    console.log(student.full_name, student.email);
  });
}

Response:

{
  data: {
    institution: number,
    search: string,
    skip: number,
    limit: number,
    total_count: number,
    students: [
      {
        id: number,
        sid: string,
        full_name: string,
        email: string,
        current_programs: [{id, name, code}],
        current_gpa: number,
        credits_earned: number,
        credits_attempted: number
      }
    ],
    _meta: {
      has_more: boolean,
      next_skip: number,
      returned_count: number
    }
  }
}

getStudent(studentId)

Get detailed information for a specific student.

const result = await client.getStudent(123);

if (result.data) {
  const { student, academic_summary, programs, degrees } = result.data;
  console.log('Student:', student.full_name);
  console.log('GPA:', academic_summary.gpa);
  console.log('Credits:', academic_summary.credits_earned);
}

Response:

{
  data: {
    student: {
      id, sid, first_name, last_name, full_name,
      email, phone, address: {...}
    },
    academic_summary: {
      credits_attempted, credits_earned,
      gpa_credits, gpa
    },
    programs: [
      {id, program_id, name, code, type, start_date}
    ],
    degrees: [
      {id, degree_id, name, code, type, start_date}
    ]
  }
}

getStudentTranscript(studentId)

Get complete academic transcript for a student.

const result = await client.getStudentTranscript(123);

if (result.data) {
  const { transcript } = result.data;

  console.log('Courses taken:', transcript.courses.length);
  console.log('Total credits:', transcript.totals.credits_earned);
  console.log('GPA:', transcript.totals.gpa);

  transcript.courses.forEach(course => {
    console.log(
      `${course.term_code}: ${course.course_subject} ${course.course_number} - ${course.grade_letter}`
    );
  });
}

Response:

{
  data: {
    student: {id, sid, full_name},
    transcript: {
      courses: [
        {
          enrollment_id, course_id, course_subject,
          course_number, course_title, section_id,
          section_number, term_id, term_code,
          term_name, academic_year, credits_attempted,
          credits_earned, grade_id, grade_letter,
          grade_quality_points, grade_gpa
        }
      ],
      totals: {
        credits_attempted, credits_earned,
        gpa_credits, gpa_quality_points, gpa
      },
      asof: string
    }
  }
}

getStudentSchedule(studentId, termId)

Get student schedule for a specific term.

const result = await client.getStudentSchedule(123, 15);

if (result.data) {
  const { term, schedule } = result.data;

  console.log('Term:', term.name);
  console.log('Courses:', schedule.courses.length);
  console.log('Term GPA:', schedule.totals.term_gpa);

  schedule.courses.forEach(course => {
    console.log(
      `${course.course_subject} ${course.course_number} - ${course.grade_letter || 'In Progress'}`
    );
  });
}

Course Methods

searchCourses(params)

Search for courses by subject, number, or title.

const result = await client.searchCourses({
  search: 'MATH',
  ay: '2023',  // optional: filter by academic year
  skip: 0,
  limit: 20
});

if (result.data) {
  result.data.courses.forEach(course => {
    console.log(`${course.subject} ${course.number}: ${course.title}`);
  });
}

Term Methods

searchTerms(params)

Search for terms by code or name.

const result = await client.searchTerms({
  search: 'Spring',
  skip: 0,
  limit: 10
});

if (result.data) {
  result.data.terms.forEach(term => {
    console.log(`${term.code}: ${term.short_name}`);
  });
}

Note: The ay parameter is currently not supported by the terms search endpoint.

Section Methods

searchSections(params)

Search for course sections within a term.

const result = await client.searchSections({
  search: 'CS',
  term: 15,  // required: term ID
  skip: 0,
  limit: 20
});

if (result.data) {
  result.data.sections.forEach(section => {
    console.log(
      `${section.course_subject} ${section.course_number}-${section.section_number}: ${section.enrollment}/${section.max_capacity}`
    );
  });
}

Utility Methods

getInstitutionId()

Get the current institution ID (available after initialization).

const institutionId = client.getInstitutionId();

isInitialized()

Check if the client has been initialized.

if (!client.isInitialized()) {
  await client.initialize();
}

Complete Example

import { createClient } from 'appiversity-sdk';

async function main() {
  // Create and initialize client
  const client = createClient();

  const initResult = await client.initialize();
  if (initResult.error) {
    console.error('Initialization failed:', initResult.error.message);
    process.exit(1);
  }

  console.log(`Connected to ${initResult.data.name}\n`);

  // Search for students
  console.log('Searching for students named "Smith"...');
  const searchResult = await client.searchStudents({
    search: 'Smith',
    limit: 5
  });

  if (searchResult.error) {
    console.error('Search failed:', searchResult.error.message);
    return;
  }

  console.log(`Found ${searchResult.data.total_count} students:\n`);

  // Get details for first student
  const students = searchResult.data.students;
  if (students.length > 0) {
    const student = students[0];
    console.log(`Getting details for ${student.full_name}...`);

    const detailResult = await client.getStudent(student.id);
    if (detailResult.error) {
      console.error('Failed to get details:', detailResult.error.message);
      return;
    }

    const details = detailResult.data;
    console.log('\nStudent Details:');
    console.log('  Name:', details.student.full_name);
    console.log('  SID:', details.student.sid);
    console.log('  Email:', details.student.email);
    console.log('  GPA:', details.academic_summary.gpa);
    console.log('  Credits:', details.academic_summary.credits_earned);

    if (details.programs.length > 0) {
      console.log('  Programs:');
      details.programs.forEach(program => {
        console.log(`    - ${program.name} (${program.code})`);
      });
    }

    // Get transcript
    console.log('\nGetting transcript...');
    const transcriptResult = await client.getStudentTranscript(student.id);

    if (transcriptResult.error) {
      console.error('Failed to get transcript:', transcriptResult.error.message);
      return;
    }

    const transcript = transcriptResult.data.transcript;
    console.log(`\nTranscript (${transcript.courses.length} courses):`);
    console.log('  Total Credits:', transcript.totals.credits_earned);
    console.log('  GPA:', transcript.totals.gpa);

    // Show most recent courses
    console.log('\n  Recent Courses:');
    transcript.courses.slice(0, 5).forEach(course => {
      console.log(
        `    ${course.term_code}: ${course.course_subject} ${course.course_number} - ${course.grade_letter || 'N/A'}`
      );
    });
  }
}

main().catch(console.error);

Error Handling

The SDK never throws errors. All methods return an object with either data or error.

Checking for Errors

const result = await client.searchStudents({ search: 'Smith' });

if (result.error) {
  // Handle error
  console.error(`Error (${result.status}):`, result.error.message);
  return;
}

// Use data
console.log(result.data.students);

Common Error Types

| Error Type | Status | Description | |------------|--------|-------------| | Network Error | 0 | Network connectivity issue | | Unauthorized | 401 | Invalid or missing API key | | Forbidden | 403 | Insufficient permissions | | Not Found | 404 | Resource doesn't exist | | Bad Request | 400 | Invalid parameters | | Internal Server Error | 500 | Server-side error |

Error Object Structure

{
  error: {
    error: string,     // Error type (e.g., "Unauthorized")
    message: string,   // Human-readable message
    status: number     // HTTP status code
  },
  status: number       // Same as error.status
}

Pagination

Endpoints that return lists support pagination via skip and limit parameters:

const fetchPage = async (skip, limit) => {
  const result = await client.searchStudents({
    search: 'Smith',
    skip,
    limit
  });

  if (result.error) return [];

  return result.data.students;
};

// Fetch first 100 students
const page1 = await fetchPage(0, 50);
const page2 = await fetchPage(50, 50);
const allStudents = [...page1, ...page2];

Checking for More Results

const result = await client.searchStudents({ search: 'Smith', limit: 20 });

if (result.data) {
  console.log('Returned:', result.data._meta.returned_count);
  console.log('Total:', result.data.total_count);

  if (result.data._meta.has_more) {
    console.log('More results available');
    console.log('Next skip:', result.data._meta.next_skip);
  }
}

TypeScript Support

While the SDK is written in JavaScript, it includes comprehensive JSDoc annotations for excellent TypeScript IntelliSense support.

If you're using TypeScript, your IDE will provide full autocomplete and type checking:

import { createClient } from 'appiversity-sdk';

const client = createClient();
await client.initialize();

// TypeScript will infer types from JSDoc
const result = await client.searchStudents({ search: 'Smith' });

if (result.data) {
  // TypeScript knows result.data.students is an array
  result.data.students.forEach(student => {
    // Autocomplete available for student properties
    console.log(student.full_name);
  });
}

Best Practices

1. Initialize Once

Initialize the client once and reuse it:

// Good: Initialize once
const client = createClient();
await client.initialize();

async function getStudents() {
  return client.searchStudents({ search: 'Smith' });
}

// Bad: Initializing multiple times
async function getStudents() {
  const client = createClient();
  await client.initialize();  // Unnecessary API call
  return client.searchStudents({ search: 'Smith' });
}

2. Always Check for Errors

// Good: Check error field
const result = await client.searchStudents({ search: 'Smith' });
if (result.error) {
  console.error(result.error.message);
  return;
}
console.log(result.data.students);

// Bad: Assuming success
const result = await client.searchStudents({ search: 'Smith' });
console.log(result.data.students);  // May crash if error

3. Use .env File for Sensitive Data

// Good: .env file for sensitive data
// .env file: APPIVERSITY_SDK_API=your-key
const client = createClient();

// Acceptable: Environment variables
export APPIVERSITY_SDK_API="your-key"
const client = createClient();

// Bad: Hardcoding API keys
const client = createClient({ apiKey: 'abc123...' });  // Never commit this!

4. Limit API Calls

// Good: Reasonable limits
const result = await client.searchStudents({ search: 'Smith', limit: 20 });

// Avoid: Requesting maximum every time
const result = await client.searchStudents({ search: 'Smith', limit: 100 });

Troubleshooting

"API key is required" Error

Make sure you've:

  1. Created a .env file with APPIVERSITY_SDK_API=your-key, OR
  2. Set the APPIVERSITY_SDK_API environment variable, OR
  3. Passed apiKey to createClient({ apiKey: '...' })

"Institution slug is required" Error

Make sure you've:

  1. Created a .env file with APPIVERSITY_SDK_INSTITUTION=slug, OR
  2. Set the APPIVERSITY_SDK_INSTITUTION environment variable, OR
  3. Passed institutionSlug to createClient({ institutionSlug: '...' })

"Client not initialized" Error

You must call await client.initialize() before using any other methods.

Network Errors

const result = await client.searchStudents({ search: 'Smith' });

if (result.status === 0) {
  console.error('Network error:', result.error.message);
  // Check network connectivity and baseUrl configuration
}

401 Unauthorized

Your API key is invalid or has been revoked. Generate a new key at /account/api-keys.

403 Forbidden

Your API key doesn't have the required permissions. Check your user privies.

Contributing

Contributions are welcome! Please open an issue or pull request on GitHub.

License

MIT License - see LICENSE file for details.

Support

Changelog

1.0.0 (2025-10-29)

  • Initial release
  • Support for all Appiversity REST API v1 endpoints
  • Student search and detail methods
  • Student transcript and schedule methods
  • Course, term, and section search methods
  • Institution discovery methods
  • Comprehensive JSDoc annotations
  • Zero dependencies
  • Node.js 18+ native fetch support

Made with ❤️ by the Appiversity team