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

@edpire/sdk

v0.6.4

Published

Embeddable JS SDK for Edpire assessments

Readme

@edpire/sdk

Embeddable assessment SDK for Edpire. Deliver, render, and grade assessments from your own application.

📚 Full documentation lives in the Edpire docs site under SDK Reference — complete option/prop/type tables for the Embedded Player, React components, Custom Flow, server client, and branding. This README is a condensed quick-start.


Which approach fits you?

| Approach | Learner stays on your page? | You build the UI? | Entry point | |---|---|---|---| | Hosted Redirect | No | No | Share URL only | | Embedded Player | Yes | No | EdpireAssessment.mount() | | Custom Flow | Yes | Yes | renderQuestion + flattenAssessment |

Server Client (EdpireClient) is a backend utility used alongside any of the above — not a separate approach. Jump to Server Client →


Installation

Published to the public npm registry — no auth token required.

npm install @edpire/sdk
# or
pnpm add @edpire/sdk

License: the package is free to install but carries a proprietary, integrate-with-Edpire-only license (see LICENSE). It is safe to make public because the bundle is only the question renderer — answer keys, grading, and API keys never ship in it; they stay on the server.


Hosted Redirect

The simplest integration — no SDK required on the frontend. Redirect learners to Edpire's hosted assessment UI. After completion, Edpire redirects back to your return_url with results.

https://{your-slug}.edpire.com/take/{shareCode}?learner_ref={userId}&return_url={yourUrl}

| Parameter | Required | Description | |-----------|----------|-------------| | shareCode | Yes | Assessment share code from the dashboard | | learner_ref | Recommended | Your stable internal user ID | | return_url | Recommended | Where to send the learner after completion |

Edpire appends ?submission_id=...&score=...&max_score=... to the return URL.

When to use: Fast adoption, no custom UI needed, comfortable with a browser redirect.


Embedded Player

Mount Edpire's full assessment player inline inside your page. The SDK fetches the assessment, handles all interactions, grades the submission, and shows per-question feedback — all automatically. The learner never leaves your app.

Server (Node.js / API route):

import { EdpireClient } from "@edpire/sdk/client"

const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })

// Mint a short-lived single-use token for this learner + assessment
const { token } = await client.mintEmbedToken(assessmentId, userId)
// Pass `token` to the browser (e.g. via server-rendered props or an API response)

Browser:

import { EdpireAssessment } from "@edpire/sdk"

const embed = EdpireAssessment.mount({
  token,                           // from your server
  container: "#assessment-root",   // CSS selector or HTMLElement
  onComplete: (result) => {
    console.log(`Score: ${result.score}/${result.max_score}`)
    console.log(`Passed: ${result.passed}`)
  },
  onError: (err) => console.error(err.message),
})

// Later, when done:
embed.unmount()

CDN (no bundler):

<script src="https://cdn.jsdelivr.net/npm/@edpire/[email protected]/dist/umd/index.global.js"></script>
<script>
  EdpireSDK.EdpireAssessment.mount({ token, container: "#root", onComplete: handleDone })
</script>

The UMD build is served straight from npm by jsDelivr (and unpkg) — no CDN to run yourself. Pin a version for production; @latest works for prototyping.

When to use: You want the full assessment experience inside your page with a single function call. Zero UI code required.


Custom Flow

Build your own question-by-question UX — Duolingo-style flows, flashcard drills, practice modes, custom navigation. The SDK handles question rendering and answer collection; you own everything else.

How it works:

  1. Your server fetches the assessment (keeping your API key server-side)
  2. flattenAssessment() converts it into a flat array of question steps
  3. renderQuestion() (or <EdpireQuestion> in React) renders each step into a container you control
  4. Your server grades each answer via the /check endpoint
  5. After all questions, your server submits the full attempt

Server — fetch the assessment:

// GET /api/edpire/assessment/[id]/route.ts
import { EdpireClient } from "@edpire/sdk/client"

const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })
const assessment = await client.getAssessment(assessmentId)
return NextResponse.json({ data: assessment })

Browser — flatten and render:

// React
import { flattenAssessment } from "@edpire/sdk"
import { EdpireQuestion } from "@edpire/sdk/react"
import type { RuntimeAnswer } from "@edpire/sdk"

const steps = flattenAssessment(assessment)
// steps[i] = { exerciseId, questionId, content, points, sequenceNumber, index }

const [currentIndex, setCurrentIndex] = useState(0)
const [answers, setAnswers] = useState<RuntimeAnswer[]>([])
const [feedback, setFeedback] = useState(null)
const step = steps[currentIndex]

<EdpireQuestion
  content={step.content}
  onAnswersChange={setAnswers}
  feedback={feedback}   // null = no feedback shown; pass result of /check to show it
  dir="ltr"             // or "rtl" for Arabic
/>
// Vanilla JS
import { flattenAssessment, renderQuestion } from "@edpire/sdk"

const steps = flattenAssessment(assessment)
const step = steps[currentIndex]

const question = renderQuestion({
  container: document.getElementById("question-root"),
  content: step.content,
  onAnswersChange: (answers) => { /* store answers */ },
})

// Later, update feedback:
question.setFeedback(feedbackFromCheckEndpoint)

// Move to next question:
question.setContent(steps[nextIndex].content)

Server — grade each question via your proxy:

// POST /api/edpire/check/[id]/route.ts  (recommended — type-safe, handles URL construction)
import { EdpireClient } from "@edpire/sdk/client"

const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })

export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const body = await req.json()
  // Always set include_correct_answers server-side — never trust the client to send it
  const result = await client.checkQuestion(id, { ...body, include_correct_answers: true })
  return Response.json(result)  // result: CheckResult = { correct, score, max_score, feedback }
}
// Note: assessmentId goes in the URL path, not the request body
const result = await fetch(`https://edpire.com/api/v1/assessments/${assessmentId}/check`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EDPIRE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    exercise_id: exerciseId,
    question_id: questionId,
    answers,
    learner_ref: userId,
    session_id: sessionId,
    include_correct_answers: true,
  }),
}).then((r) => r.json())
// Response envelope: { data: CheckResult, error: null, meta: null }

Server — submit the full attempt when done:

import { EdpireClient } from "@edpire/sdk/client"
import type { StoredAnswer } from "@edpire/sdk"

const stored: StoredAnswer[] = []
// push { exerciseId, questionId, answers } for each step as the learner answers

const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })
const result = await client.submit(assessmentId, {
  learner_ref: userId,
  answers: stored,   // StoredAnswer[] — the client handles nesting automatically
})
console.log(`Final score: ${result.score}/${result.max_score}`)

Each (session_id, question_id) pair is limited to 3 checks per hour (configurable per org).

Always pass a session_id — generate a UUID once at the start of each attempt:

const sessionId = crypto.randomUUID()  // generate once per attempt, not per question

If omitted, rate limiting falls back to learner_ref, which accumulates across all of a learner's attempts and can cause unexpected 429s on repeat attempts.

Required API key scopes: read:assessments (for fetching and checking) + write:submissions (for the final submit).

When to use: You want full control over pacing, navigation, animations, or scoring UX. You're building a Duolingo-style flow, a timed drill, a practice mode, or any experience that goes beyond a linear "answer all → submit" form.


Server Client

EdpireClient is a backend-only utility for calling the Edpire REST API from your server. It is not a rendering pattern — it is used alongside Embedded Player and Custom Flow.

import { EdpireClient } from "@edpire/sdk/client"

const client = new EdpireClient({
  apiKey: process.env.EDPIRE_API_KEY!,
  baseUrl: "https://edpire.com",  // optional, defaults to edpire.com
})

Common operations:

// List published assessments
const { items } = await client.getAssessments({ status: "published" })

// Fetch a single assessment with full content
const assessment = await client.getAssessment(assessmentId)

// Mint an embed token (for Embedded Player)
const { token } = await client.mintEmbedToken(assessmentId, learnerRef)

// Submit a full attempt (for Custom Flow)
const result = await client.submit(assessmentId, { learner_ref: userId, answers: stored })

// Grade a single question without recording (for Custom Flow /check)
const check = await client.checkQuestion(assessmentId, {
  exercise_id, question_id, answers, learner_ref, session_id,
  include_correct_answers: true,
})

// Register a webhook
const webhook = await client.registerWebhook("https://yourapp.com/webhooks/edpire", [
  "submission.graded",
])
// Store webhook.secret securely — shown only once

Mobile & WebView integration

Flutter, React Native, and native iOS/Android apps cannot import this npm package directly. The integration uses two parts:

  1. Your backend mints an embed token via client.mintEmbedToken() (the API key stays server-side).
  2. A WebView in your mobile app loads a minimal HTML page that calls the CDN UMD build with the token.

The UMD build is served from npm via jsDelivr — no CDN to host yourself.

Universal WebView HTML template:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>* { margin: 0; padding: 0; box-sizing: border-box; } #root { min-height: 100vh; }</style>
</head>
<body>
  <div id="root"></div>
  <script src="https://cdn.jsdelivr.net/npm/@edpire/[email protected]/dist/umd/index.global.js"></script>
  <script>
    EdpireSDK.EdpireAssessment.mount({
      token: "{{EMBED_TOKEN}}",   // replace server-side before loading
      container: "#root",
      onComplete: function(r) {
        if (window.ReactNativeWebView)
          window.ReactNativeWebView.postMessage(JSON.stringify({ type: "complete", result: r }))
        if (window.flutter_inappwebview)
          window.flutter_inappwebview.callHandler("onComplete", r)
      },
      onError: function(e) {
        if (window.ReactNativeWebView)
          window.ReactNativeWebView.postMessage(JSON.stringify({ type: "error", error: e }))
        if (window.flutter_inappwebview)
          window.flutter_inappwebview.callHandler("onError", e)
      }
    })
  </script>
</body>
</html>

Ionic / Capacitor apps run in a WebView — you can use the full npm SDK directly. No HTML template needed.

For React Native, Flutter, and native iOS/Android code examples, see the Mobile Integration guide.


CSS

Embedded Player (EdpireAssessment.mount()) — all CSS is injected automatically. No imports needed.

Custom Flow (EdpireQuestion / renderQuestion) — styles are bundled into the SDK's JavaScript and injected when the component first renders. In most setups this is also automatic. If styles appear missing (e.g. in a sandboxed iframe or a strict CSP environment that blocks inline styles), import the utility stylesheet explicitly:

import "@edpire/sdk/styles/runtime-utilities.css"

TypeScript types

All commonly needed types are available from the main entry:

import type {
  // Answer types
  RuntimeAnswer,
  ChoiceSetAnswer,
  BlankAnswer,
  TypedBlankAnswer,
  BlankChoiceAnswer,
  MatchingSetAnswer,
  AiAnswer,
  OpenResponseAnswer,
  MathResponseAnswer,

  // Custom Flow helpers
  StoredAnswer,    // { exerciseId, questionId, answers: RuntimeAnswer[] }
  FlatStep,        // one step from flattenAssessment()

  // Embedded Player
  MountOptions,
  EmbedInstance,
  EmbedResult,
  EmbedError,
} from "@edpire/sdk"

Types specific to the Server Client come from @edpire/sdk/client:

import type { Assessment, SubmitResult, CheckResult, EdpireClientOptions } from "@edpire/sdk/client"

Webhooks

const webhook = await client.registerWebhook("https://yourapp.com/api/webhooks/edpire", [
  "submission.graded",
  "assessment.published",
])
// Store webhook.secret securely — it is only shown once.

Verify incoming signatures:

import { createHmac, timingSafeEqual } from "crypto"

export async function POST(req: Request) {
  const rawBody = await req.text()
  const signature = req.headers.get("x-edpire-signature") ?? ""

  const expected = "sha256=" + createHmac("sha256", process.env.EDPIRE_WEBHOOK_SECRET!)
    .update(rawBody)
    .digest("hex")

  if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return new Response("Unauthorized", { status: 401 })
  }

  const event = JSON.parse(rawBody)
  // event.event, event.submission_id, event.score, event.passed, ...
}

Available events: submission.graded, submission.pending_review, assessment.published, assessment.archived


Error handling

EdpireClient throws EdpireError on non-2xx responses:

import { EdpireError } from "@edpire/sdk/client"

try {
  await client.submit(assessmentId, options)
} catch (err) {
  if (err instanceof EdpireError) {
    console.error(err.status, err.message)
  }
}

Common status codes: 400 bad request · 401 invalid API key · 404 not found · 409 max attempts reached · 422 grading failed · 429 rate limit exceeded