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.