Skip to main content
Edpire is the source of truth for assessment content. Your platform maintains its own catalog with metadata that Edpire does not manage (categories, difficulty levels, prerequisites, etc.).

Your consumer database schema

Store the Edpire IDs alongside your own metadata:
CREATE TABLE assessments (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  edpire_id       TEXT NOT NULL UNIQUE,     -- Edpire assessment UUID
  edpire_share_code TEXT,                   -- for building take URLs
  title           TEXT NOT NULL,            -- synced from Edpire
  category        TEXT,                     -- your own metadata
  difficulty      TEXT,                     -- your own metadata
  is_visible      BOOLEAN DEFAULT true,     -- hide archived assessments
  synced_at       TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE submissions (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  edpire_id       TEXT NOT NULL UNIQUE,     -- Edpire submission UUID
  assessment_id   UUID REFERENCES assessments(id),
  learner_ref     TEXT NOT NULL,
  score           NUMERIC,
  max_score       NUMERIC,
  percentage      INTEGER,
  passed          BOOLEAN,
  submitted_at    TIMESTAMPTZ
);

Initial sync

On first setup, pull the full catalog:
import { EdpireClient } from "@edpire/sdk/client"

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

// Fetch all published assessments
let page = 1
let hasMore = true
while (hasMore) {
  const { items, total } = await client.getAssessments({
    status: "published",
    page,
    limit: 100,
  })
  for (const a of items) {
    await db.upsertAssessment({
      edpireId: a.id,
      edpireShareCode: a.share_code,
      title: a.title,
    })
  }
  hasMore = page * 100 < total
  page++
}

Staying in sync with webhooks

Register for all catalog events once:
await client.registerWebhook("https://yourplatform.com/webhooks/edpire", [
  "assessment.published",
  "assessment.content_updated",
  "assessment.archived",
  "collection.created",
  "collection.updated",
  "collection.deleted",
  "collection.item_added",
  "collection.item_removed",
  "collection.reordered",
  "submission.graded",
])
Handle events in your webhook endpoint:
app.post("/webhooks/edpire", express.raw({ type: "application/json" }), async (req, res) => {
  verifySignature(req, process.env.EDPIRE_WEBHOOK_SECRET)
  res.sendStatus(200) // respond immediately

  const { event } = req.body

  switch (event) {
    case "assessment.published":
      await db.upsertAssessment({
        edpireId: req.body.assessment_id,
        title: req.body.title,
        edpireShareCode: req.body.share_code,
      })
      break

    case "assessment.content_updated":
      // Content changed — re-fetch if you cache exercise data
      break

    case "assessment.archived":
      await db.hideAssessment(req.body.assessment_id)
      break

    case "collection.item_added":
      await db.addToCollection(req.body.collection_id, {
        assessmentId: req.body.assessment_id,
        position: req.body.position,
      })
      break

    case "submission.graded":
      if (await db.submissionExists(req.body.submission_id)) break // idempotency
      await db.saveSubmission({
        edpireId: req.body.submission_id,
        learnerRef: req.body.learner_ref,
        score: req.body.score,
        maxScore: req.body.max_score,
        percentage: req.body.percentage,
        passed: req.body.passed,
      })
      break
  }
})

Bulk refresh

If your local data gets out of sync (e.g., missed webhooks during downtime), fetch specific assessments by ID:
// Fetch up to 50 IDs in one call
const { items } = await client.getAssessments({
  ids: ["id1", "id2", "id3"],
})
Or via REST:
GET /api/v1/assessments?ids=id1,id2,id3
Use bulk fetch for periodic reconciliation — no need to refetch the entire catalog.