@edpire/sdk/client exports EdpireClient, a typed wrapper over the Edpire REST API. It runs server-side only (your API key must never reach the browser), has zero browser dependencies, and uses native fetch.
It is not a rendering pattern — it’s the backend utility used alongside the Embedded Player and Custom Flow.
Setup
Create the client once at module level and reuse it — don’t instantiate it per request.
import { EdpireClient } from "@edpire/sdk/client"
// module level — one instance, reused across all requests
const client = new EdpireClient({
apiKey: process.env.EDPIRE_API_KEY!, // starts with edp_live_
baseUrl: "https://edpire.com", // optional, this is the default
})
EdpireClientOptions
| Option | Type | Required | Description |
|---|
apiKey | string | Yes | Your Edpire API key (edp_live_…). Server-side only. |
baseUrl | string | No | API base URL. Defaults to https://edpire.com. |
fetch | typeof fetch | No | Custom fetch — for testing or edge runtimes (e.g. Cloudflare Workers). |
Method reference
All methods are async, hit /api/v1 on baseUrl with Authorization: Bearer <apiKey>, and throw EdpireError on non-2xx.
Assessments
| Method | Returns | Notes |
|---|
getAssessments(params?) | PaginatedResponse<AssessmentSummary> | params: status, ids (bulk, max 50), page, limit. No exercises in summaries. |
getAssessment(id) | Assessment | Full content with exercises[].questions[].content_ast. Never includes answer keys. |
const { items, total } = await client.getAssessments({ status: "published", page: 1, limit: 20 })
// Bulk fetch up to 50 IDs in one call (passing more than 50 throws a 400)
const { items: batch } = await client.getAssessments({ ids: ["id1", "id2", "id3"] })
// Single assessment with exercises + questions
const assessment = await client.getAssessment("assessment-uuid")
Submit & check
| Method | Returns | Notes |
|---|
submit(assessmentId, options) | GradeResult | Records + grades a full attempt. Fires submission.graded. Accepts flat StoredAnswer[] or nested answers. |
checkQuestion(assessmentId, options) | CheckResult | Grades one question. Stateless (no record). Rate-limited per (session_id, question_id). |
// Full submission — answers can be a flat StoredAnswer[] (auto-nested) ...
const result = await client.submit("assessment-uuid", {
learner_ref: "user-123",
answers: stored,
})
// ... or the nested format directly:
const result2 = await client.submit("assessment-uuid", {
learner_ref: "user-123",
answers: {
exerciseAnswers: [
{ exerciseId: "ex-1", questionAnswers: [
{ questionId: "q-1", answers: [{ nodeId: "n1", type: "choiceSet", value: ["opt_a"] }] },
]},
],
},
})
// Single-question check (Custom Flow)
const check = await client.checkQuestion("assessment-uuid", {
exercise_id: "ex-1",
question_id: "q-1",
answers: [{ nodeId: "n1", type: "choiceSet", value: ["opt_a"] }],
learner_ref: "user-123",
session_id: "attempt-uuid", // generate once per attempt
include_correct_answers: true, // reveal correct answers (set server-side)
})
SubmitOptions also accepts allow_draft (submit against draft assessments for testing) and metadata (consumer-side data, not stored by Edpire).
Submissions & learner history
| Method | Returns | Notes |
|---|
getSubmission(id) | Submission | Full submission with per-question results. |
getLearnerResults(learnerRef, params?) | PaginatedResponse<Submission> | All submissions for a learner. params: page, limit. |
const submission = await client.getSubmission("submission-uuid")
const { items } = await client.getLearnerResults("user-123", { page: 1, limit: 50 })
Collections
| Method | Returns | Notes |
|---|
getCollections(params?) | PaginatedResponse<Collection> | List collections (with item_count). |
getCollection(id) | CollectionDetail | Includes ordered items[].assessment. |
getCollectionResults(id, params?) | PaginatedResponse<Submission & { assessment_title }> | Flat results across every assessment in the collection. |
const { items: collections } = await client.getCollections()
const detail = await client.getCollection("collection-uuid") // detail.items[].assessment
const { items: results } = await client.getCollectionResults("collection-uuid")
Embed tokens
| Method | Returns | Notes |
|---|
mintEmbedToken(assessmentId, learnerRef) | EmbedToken | Single-use token (expires 1h) for the Embedded Player. |
const { token, expires_at } = await client.mintEmbedToken("assessment-uuid", "user-123")
Webhooks
| Method | Returns | Notes |
|---|
registerWebhook(url, events) | WebhookWithSecret | Secret shown once — store it. http://localhost allowed for dev. |
listWebhooks() | Webhook[] | Secrets not included. |
deleteWebhook(id) | void | — |
const webhook = await client.registerWebhook(
"https://yourplatform.com/webhooks/edpire",
["submission.graded", "assessment.published", "assessment.content_updated"],
)
console.log(webhook.secret) // store securely — shown once
See Webhooks for the full event list and signature verification.
Error handling
Every method throws EdpireError on a non-2xx response:
import { EdpireError } from "@edpire/sdk/client"
try {
await client.getAssessment("bad-id")
} catch (err) {
if (err instanceof EdpireError) {
console.log(err.status) // e.g. 404
console.log(err.message) // e.g. "Assessment not found"
}
}
Common status codes: 400 bad request · 401 invalid API key · 403 forbidden / origin not allowed · 404 not found · 409 max attempts reached · 422 grading failed · 429 rate limit exceeded.
Custom fetch (testing / edge runtimes)
const client = new EdpireClient({
apiKey: "test-key",
baseUrl: "http://localhost:3001",
fetch: customFetch, // injectable for tests or Cloudflare Workers
})
Type reference
All response and option types (Assessment, GradeResult, CheckResult, Submission, Collection, etc.) are exported from @edpire/sdk/client. See the type catalog.