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

@genixsuitecom/elearning-sdk

v0.3.1

Published

GenixSuite Learn API TypeScript SDK

Readme

eLearning API v1 – TypeScript SDK

Install

npm i @genixsuitecom/elearning-sdk

Rename .env.example to .env Enter .env variables from https://app.genixsuite.com/settings/api

  • Create a client
  • Name the client and select the scope
  • Copy client ID and client secret to the .env file

OAuth quickstart

Client Credentials grant (server-to-server):

import { ElearningApiClient } from '@genixsuitecom/elearning-sdk'

async function getClientCredentialsToken() {
  const url = 'https://app.genixsuite.com/api/oauth2/token'
  const body = new URLSearchParams({
    grant_type: 'client_credentials',
    scope: 'jobs:read',
    client_id: process.env.GENIXSUITE_CLIENT_ID!,
    client_secret: process.env.GENIXSUITE_CLIENT_SECRET!,
  })
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
  })
  if (!res.ok) throw new Error(`token error: ${res.status}`)
  const json = await res.json() as { access_token: string }
  return json.access_token
}

const token = await getClientCredentialsToken()
const client = new ElearningApiClient({
  baseUrl: 'https://app.genixsuite.com',
  token,
  verbose: true,            // optional logging
  validateResponses: false, // optional Zod validation
})

Elite Quickstart (PowerShell – Windows)

Prereqs

  • OAuth client with scopes you need: jobs:write jobs:read artifacts:read ingest:write curriculums:write
  • Base URL: https://app.genixsuite.com

PowerShell session setup

$BASE = "https://app.genixsuite.com"
$CLIENT_ID = "ci_xxx"
$CLIENT_SECRET = "cs_xxx"
$SCOPE = "jobs:write jobs:read artifacts:read ingest:write curriculums:write"

# Get access token (Client Credentials)
$TOKEN = (curl -s -X POST "$BASE/api/oauth2/token" `
  -H "Content-Type: application/x-www-form-urlencoded" `
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=$SCOPE" | ConvertFrom-Json).access_token

# Helpers
$IDEMP = [guid]::NewGuid().ToString()
$Headers = @{ "Authorization" = "Bearer $TOKEN" }
  1. Create a curriculum
curl -s -X POST "$BASE/api/v1/curriculums" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -d '{"title":"Demo Curriculum"}' | ConvertFrom-Json
  1. List curriculums
curl -s -X GET "$BASE/api/v1/curriculums" -H "Authorization: Bearer $TOKEN" | ConvertFrom-Json
  1. Get a specific curriculum
curl -s -X GET "$BASE/api/v1/curriculums/<curriculumId>" -H "Authorization: Bearer $TOKEN" | ConvertFrom-Json
  1. Subjects: create + process (single-call orchestration)
$body = @{
  subject = @{ title = "House Committees" }
  sources = @(@{ url = "https://clerk.house.gov/committee_info/scsoal.pdf" })
  outputs = @("pptx","pdf")
} | ConvertTo-Json

$resp = curl -s -X POST "$BASE/api/v1/subjects/process" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -H "Idempotency-Key: $IDEMP" `
  -d $body | ConvertFrom-Json

$JOB = $resp.jobId
$JOB
  1. Exports: create export job (subjectId OR curriculumId)
# From subjectId
$body = @{ subjectId = "$JOB"; outputs = @("pptx","pdf") } | ConvertTo-Json
$resp = curl -s -X POST "$BASE/api/v1/exports" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -d $body | ConvertFrom-Json
$JOB = $resp.jobId

# From curriculumId
$body = @{ curriculumId = "<curriculumId>"; outputs = @("pptx") } | ConvertTo-Json
$resp = curl -s -X POST "$BASE/api/v1/exports" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -d $body | ConvertFrom-Json
$JOB = $resp.jobId
  1. Jobs: status and cancel (idempotent)
# Status
curl -s "$BASE/api/v1/jobs/$JOB" -H "Authorization: Bearer $TOKEN" | ConvertFrom-Json

# Cancel
$IDEMP2 = [guid]::NewGuid().ToString()
curl -i -X POST "$BASE/api/v1/jobs/$JOB/cancel" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Idempotency-Key: $IDEMP2"
  1. Artifacts: list (ETag) and resolve download URL
# List artifacts (200)
$arts = curl -s "$BASE/api/v1/jobs/$JOB/artifacts" -H "Authorization: Bearer $TOKEN" | ConvertFrom-Json
$ARTIFACT = $arts.items[0].id

# (Optional) capture ETag for conditional GET
$ETAG = (curl -sI "$BASE/api/v1/jobs/$JOB/artifacts" -H "Authorization: Bearer $TOKEN" | Select-String '^ETag:' | ForEach-Object { $_.Line.Split(':',2)[1].Trim() })
if ($ETAG) {
  curl -i "$BASE/api/v1/jobs/$JOB/artifacts" -H "Authorization: Bearer $TOKEN" -H "If-None-Match: $ETAG"
}

# Resolve download URL
curl -s "$BASE/api/v1/artifacts/$ARTIFACT" -H "Authorization: Bearer $TOKEN" | ConvertFrom-Json
  1. Ingest (canonical prefix)
# 1) Presign upload
$sha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"  # 64 hex
$body = @{ filename = "doc.pdf"; mimeType = "application/pdf"; sizeBytes = 123456; sha256 = $sha } | ConvertTo-Json
$IDEMP3 = [guid]::NewGuid().ToString()
$upload = curl -s -X POST "$BASE/api/v1/ingest/uploads" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -H "Idempotency-Key: $IDEMP3" `
  -d $body | ConvertFrom-Json
$S3URL = $upload.uploadUrl
$FIELDS = $upload.fields
$SOURCE_ID = $upload.sourceId

# 2) Upload to S3 using returned form fields (adjust according to returned keys)
Invoke-RestMethod -Method Post -Uri $S3URL -Form @{
  "key"              = $FIELDS.key
  "policy"           = $FIELDS.policy
  "x-amz-algorithm"  = $FIELDS."x-amz-algorithm"
  "x-amz-credential" = $FIELDS."x-amz-credential"
  "x-amz-date"       = $FIELDS."x-amz-date"
  "x-amz-signature"  = $FIELDS."x-amz-signature"
  "Content-Type"     = "application/pdf"
  "file"             = Get-Item .\doc.pdf
}

# 3) Register the uploaded source
$body = @{ sourceId = $SOURCE_ID; filename = "doc.pdf"; mimeType = "application/pdf"; sizeBytes = 123456; sha256 = $sha } | ConvertTo-Json
$IDEMP4 = [guid]::NewGuid().ToString()
curl -s -X POST "$BASE/api/v1/ingest/sources" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -H "Idempotency-Key: $IDEMP4" `
  -d $body | ConvertFrom-Json

Optional debug (if enabled by your deployment)

curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/_debug/whoami"
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/_debug/ping"

Idempotency and ETag cheat‑sheet

  • Idempotent POSTs support Idempotency-Key header. Replaying with the same key and identical body returns the original result with Idempotency-Replayed: true. Replaying with a different body returns 409 Conflict.
  • Artifacts list supports ETag. First call returns ETag header. Send If-None-Match: <etag> for efficient polling; server returns 304 Not Modified if unchanged.

Troubleshooting

| Symptom | Likely cause | What to do | |---|---|---| | 401 Unauthorized | Missing/expired token; wrong audience/issuer | Re-fetch token from $BASE/api/oauth2/token; ensure aud=elearning-api and clock is correct | | 403 Forbidden | Missing scope or tenant mismatch | Add required scope(s) to your client; ensure resource belongs to your client | | 404 Not Found | Endpoint not deployed (e.g., /api/v1/subjects/process); wrong ID | Verify the route exists in your environment; check IDs | | 409 Conflict | Idempotency body mismatch; AV pending/failed | Reuse same body for same Idempotency-Key; wait for AV scan or fix input | | 413 Payload Too Large | File too big during ingest | Lower sizeBytes or upload a smaller file | | 429 Rate limit | Too many requests | Back off and retry with jitter | | 304 Not Modified | Conditional GET matched | Use cached response; no change detected | | TLS/DNS errors | Firewall, proxy, or wrong base URL | Verify $BASE, corporate proxy settings, and outbound access |

Tip: On corporate Windows networks with system proxies, native Node fetch does not use proxies by default. Run npx elearning-sdk doctor for diagnostics and proxy hints.

Scopes per endpoint (summary)

  • /api/v1/subjects/process: jobs:write
  • /api/v1/exports: jobs:write
  • /api/v1/jobs/{id} (GET): jobs:read
  • /api/v1/jobs/{id}/cancel (POST): jobs:write
  • /api/v1/jobs/{id}/artifacts (GET): artifacts:read
  • /api/v1/artifacts/{id} (GET): artifacts:read
  • /api/v1/ingest/* (POST): ingest:write
  • /api/v1/curriculums*: curriculums:write for create, jobs:read not required for list/get

Canonical ingest prefix

Ingest endpoints are under /api/v1/ingest/*.

Run the TypeScript examples (local)

Requirements

  • Node 18+ (global fetch available)
  • Optional: tsx or ts-node to run .ts directly

Environment

  • GENIXSUITE_BASE=https://app.genixsuite.com
  • GENIXSUITE_API_TOKEN=<bearer> (or GENIXSUITE_CLIENT_ID/GENIXSUITE_CLIENT_SECRET + GENIXSUITE_SCOPES)

Run

# with tsx
npx tsx examples/quickstart.ts

# or with ts-node (ESM)
node --loader ts-node/esm examples/quickstart.ts

The examples log verbosely with sensitive fields redacted and surface RFC7807 problem details on errors.

Refresh Token grant:

async function refreshAccessToken(refreshToken: string) {
  const url = 'https://app.genixsuite.com/api/oauth2/token'
  const body = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
  })
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
  })
  if (!res.ok) throw new Error(`refresh error: ${res.status}`)
  return res.json() as Promise<{ access_token: string; refresh_token?: string }>
}

Usage

import { ElearningApiClient } from '@genixsuitecom/elearning-sdk'

// Option A: static bearer token (e.g., client_credentials or SSO access token)
const client = new ElearningApiClient({
  baseUrl: 'https://app.genixsuite.com',
  token: process.env.GENIXSUITE_API_TOKEN!,
  verbose: true,            // logs method/url/status with redacted headers
  validateResponses: true,  // Zod-validate responses at runtime
  requestInterceptors: [    // optional request interceptors
    ({ url, init }) => ({ url, init }),
  ],
  responseInterceptors: [],
})

// Option B: async token resolver (rotate/refresh as needed)
// const client = new ElearningApiClient({
//   baseUrl: 'https://app.genixsuite.com',
//   token: async () => process.env.GENIXSUITE_API_TOKEN!,
// })

// Step 1: presign upload
const upload = await client.createUpload({ filename: 'doc.pdf', mimeType: 'application/pdf', sizeBytes: 1024, sha256: '...' }, 'idem-123')

// Step 2: register source
await client.registerSource({ sourceId: upload.sourceId, filename: 'doc.pdf', mimeType: 'application/pdf', sizeBytes: 1024, sha256: '...' }, 'idem-123')

// Optional: create a curriculum (first-class entity)
const { curriculumId } = await client.createCurriculum({
  title: 'Onboarding Training v1',
  description: 'New hire onboarding curriculum'
})

// Single-POST orchestration (creates subject and starts export job)
// Associate the subject to a curriculum by passing curriculum_id
const job = await client.processSubject({
  subject: { title: 'Safety Training' },
  curriculum_id: curriculumId,
  sources: [{ sourceId: upload.sourceId }],
  outputs: ['pptx', 'pdf']
}, 'idem-456')

// Export from existing subject (supports all output types: pptx, pdf, jira, confluence, image, video)
const exportJob = await client.createExport({
  subjectId: '550e8400-e29b-41d4-a716-446655440000',
  outputs: ['pptx', 'pdf', 'jira'],
  options: { includeImages: true }
})

// Poll job status
const status = await client.getJob(exportJob.jobId)

// List artifacts when job completes
const artifacts = await client.listJobArtifacts(exportJob.jobId)

// Download artifact
const link = await client.getArtifact(artifacts.items[0].id)

Advanced options

  • validateResponses: When true, responses are parsed/validated with Zod schemas. Throws on shape mismatch.
  • verbose: Adds request/response interceptors that log method, URL, status with sensitive headers redacted.
  • requestInterceptors / responseInterceptors: Provide arrays of interceptor functions to observe/mutate requests and observe responses.
  • Interceptors (advanced): You can also attach interceptors directly to the generated client:
    import { OpenAPI, type RequestInterceptor, type ResponseInterceptor } from '@genixsuitecom/elearning-sdk'
    const req: RequestInterceptor = ({ url, init }) => ({ url, init })
    const res: ResponseInterceptor = ({ response }) => { /* metrics */ }
    OpenAPI.INTERCEPTORS = { request: [req], response: [res] }

Environment and settings

  • base URL: https://app.genixsuite.com (production)
  • issuer: https://app.genixsuite.com
  • audience: elearning-api
  • token endpoint: https://app.genixsuite.com/api/oauth2/token
  • access tokens are short-lived; use refresh or client credentials for server jobs

Note: The domain learn.genixsuite.com may be used for specific partner integrations or staging environments. Use app.genixsuite.com for standard API access.

Notes

  • Enable verbose: true to get debug logs with sensitive fields redacted.
erDiagram
    CURRICULUM ||--|{ SUBJECT : contains
    SUBJECT    ||--|{ TOPIC  : includes
    TOPIC      ||--|{ POINT  : comprises

    CURRICULUM {
      string name
    }
    SUBJECT {
      string name
    }
    TOPIC {
      string title
    }
    POINT {
      string detail
    }