Skip to main content
Custom Flow gives you full control over pacing, navigation, and scoring UX. The SDK renders questions and collects answers; you own everything else — progress bars, hearts/lives, animations, “next” buttons. The shape of a Custom Flow:
1

Fetch the assessment (server)

Your server calls client.getAssessment() — the API key never reaches the browser.
2

Flatten into steps

flattenAssessment() turns the nested assessment into a flat array of questions.
3

Render each question

<EdpireQuestion> (React) or renderQuestion() (vanilla) renders one step at a time.
4

Check answers per question

Your server proxies POST /check for immediate per-question feedback. Stateless — no record created.
5

Submit the full attempt

When done, your server calls client.submit() once to record + grade the whole attempt.

1. Fetch (server)

Keep the API key server-side — never call Edpire directly from the browser. Expose a thin proxy your browser can call:

2. Flatten — flattenAssessment()

Returns an ordered FlatStep[] — one entry per question, in the order a learner would see them (exercise order, then question order).
exerciseId and questionId are required for the /check call — that’s why flattenAssessment preserves them on every step. Use index for progress (index + 1 of steps.length).

3. Render — <EdpireQuestion> or renderQuestion()

Generate your session_id now, before rendering the first question. You’ll need it for every /check call and it must be the same across all questions in one attempt. Create it once at attempt start:
If you wait until step 4 and generate one per question, rate limiting accumulates across retakes and causes surprise 429s.
See EdpireQuestionProps.

renderQuestion() reference

RenderQuestionOptions — same fields as EdpireQuestionProps: container, content, onAnswersChange?, feedback?, initialAnswers?, dir?. QuestionInstance:

4. Check per question — POST /check

Grade a single question for immediate feedback. Stateless — no submission is recorded. Proxy it through your server so the API key and include_correct_answers flag stay trusted:
The browser then passes result.feedback straight into EdpireQuestion’s feedback prop (or q.setFeedback()).
Rate limiting: 3 checks per question, per session, per rolling hour (configurable per org), keyed on (api key, assessment, question, session_id) and falling back to learner_ref if session_id is omitted. Use the session_id you generated in step 3 — one per attempt, not per question.

How feedback maps onto the question

The /check response’s feedback is keyed by node ID → feedback state (correct/partial/incorrect, score, message, and — when include_correct_answers is true — the correct answer fields). EdpireQuestion consumes that object directly and renders the right per-node visual state. Set feedback back to null (or call setContent) before showing the next question to reset it. When include_correct_answers is true, feedback also includes: correctChoiceIds (choice/drag-drop), correctPairings (matching), and displayAnswer (typed blanks).

5. Submit the full attempt — client.submit()

/check records nothing. After the last question, submit the whole attempt once to create the graded submission record (and fire the submission.graded webhook). Collect answers as a flat StoredAnswer[] during the session:
client.submit() accepts either a flat StoredAnswer[] or the nested { exerciseAnswers: [...] } format. If you need the nested form explicitly (e.g. constructing it elsewhere), convert with buildSubmitPayload(stored).
Required API key scopes: read:assessments (fetch + check) and write:submissions (the final submit).
If the learner abandons mid-flow: /check is stateless, so nothing is recorded. If they close the tab before step 5, their attempt is lost. Consider saving answers to sessionStorage after each question so you can restore them on reload, and warn users before they navigate away during an active attempt.

Full example

The steps above map directly to a complete implementation — see the Custom Flow example for a reference build that wires all five steps together end-to-end.