Rubric Practice lets your authors test a rubric against sample responses directly inside the Rubric Editor, before it is ever deployed to learners. When enabled, a Test tab appears in the editor where users can paste sample responses, compare their expected scores against Feedback Aide's results, and refine their rubric until they are satisfied.
This guide walks you through the steps needed to enable and configure Rubric Practice in your integration.
Prerequisites
You must have an active Feedback Aide consumer account. You will also need a working Rubric Editor integration - see Getting started with the Rubric Editor API if you have not created one yet.
1. Generate a practice token on your server
Rubric Practice uses short-lived bearer tokens issued by the Feedback Aide token endpoint. Each token is scoped to a single practice UUID, a UUID v4 that identifies one practice session and tracks its usage against the credit limit. The same practice UUID is used for the duration of that session.
The practice_uuid must be generated on your server and returned to the frontend. Never generate it in the browser. The frontend passes the value through to the Rubric Editor exactly as received, without modifying it.
Make this request from your server, not from the browser, so your client_secret is never exposed.
POST https://feedbackaide.learnosity.com/api/token
Authorization: Basic Base64(<client_id>:<client_secret>)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=api:feedbackaide rubric_practice_uuid:<practice_uuid>:RW rubric_practice_limit:<n>A successful response returns a bearer access token:
{
"access_token": "a1b2c3d4...",
"token_type": "bearer",
"expires_in": 3600
}The two practice-specific scopes are:
-
rubric_practice_uuid:<uuid>:RW- Required. Ties the token to one practice UUID. -
rubric_practice_limit:<n>- Optional. Sets the maximum number of evaluation requests (practice credits) allowed with this token. Valid range: 1-1000. The default value is 100.
2. Expose a token endpoint in your application
The Rubric Editor calls your rubric_practice.security_provider callback whenever it needs a fresh token - on first load, when the current token expires, and when the credit limit is exhausted. Your callback must call your server, which in turn calls the Feedback Aide token endpoint and returns the result.
Your server generates the practice_uuid for the session and returns the same value on every call, whether the initial load or a token refresh.
An example of a minimal server-side handler (using Node.js with Express):
// server.js
app.post('/your/path/rubric-practice-token-endpoint', async (req, res) => {
// reuse the session's practice UUID, or generate one on the first request
req.session.practiceUuid ??= crypto.randomUUID();
const practiceUuid = req.session.practiceUuid;
const creditLimit = 100;
const clientId = process.env.FA_CLIENT_ID;
const clientSecret = process.env.FA_CLIENT_SECRET;
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const response = await fetch('https://feedbackaide.learnosity.com/api/token', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
scope: [
'api:feedbackaide',
`rubric_practice_uuid:${practiceUuid}:RW`,
`rubric_practice_limit:${creditLimit}`,
].join(' '),
}),
});
const accessToken = await response.json();
res.json({
practice_uuid: practiceUuid,
access_token: accessToken.access_token,
token_type: accessToken.token_type,
expires_in: accessToken.expires_in,
});
});3. Pass a security provider when creating the editor
Rubric Practice is configured via the rubric_practice option in createEditor(). This single object holds both the security_provider callback and any initial practice data such as the stimulus, sources, and pre-filled responses.
The security_provider and stimulus are required. Without them, Rubric Practice will not be enabled and the Test tab will not appear.
Set security_provider to an async function that calls your token endpoint. Your endpoint returns both the access_token and the practice_uuid, then the frontend callback passes them straight through and returns them to the editor without modification.
const editor = await rubricEditorApp.createEditor({
rubric_json: existingRubric,
rubric_practice: {
security_provider: async (context) => {
const response = await fetch('https://yourdomain/your/path/rubric-practice-token-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
// context.reason is one of: 'initial' | 'expired' | 'limit_exhausted' | 'unauthorized'
// you can log or handle each reason differently if required
// pass the practice_uuid and access token exactly as received from your server
return {
practice_uuid: data.practice_uuid,
security: {
access_token: data.access_token,
token_type: 'bearer',
expires_in: data.expires_in,
},
};
},
stimulus: '<p>Describe the water cycle.</p>',
},
});The editor fetches an initial access token on load to confirm the security provider works, then shows the Test tab once a valid token is returned. The editor caches the access token and automatically calls the provider again with an appropriate context.reason when it expires or the credit limit is reached. No manual access token management is needed beyond implementing the callback.
4. Attach the editor to the page
Attach the editor as you normally would. The Test tab appears automatically once a valid security_provider has been supplied and an initial access token has been successfully returned.
await editor.attach(document.querySelector('.rubric-editor-container'));At this point the integration is complete. Users will see the Test tab alongside Edit, Preview, and View Source.
5. (Optional) Pre-populate the practice context
Sources and sample responses can be passed directly in the rubric_practice option alongside the stimulus and security_provider, so they are all set up in one place at initialization.
rubric_practice: {
security_provider: mySecurityProvider,
stimulus: '<p>Explain the causes of the First World War.</p>',
sources: [
{ title: 'Background reading', content: '<p>...</p>' },
],
responses: [
{ content: '<p>The war was caused by a combination of...</p>' },
],
},You can also set or update the practice context at any time after initialization using editor.setPractice(). For a full description of each field, see Rubric Practice API reference.
6. (Optional) Read back practice results
After a user runs one or more tests, you can read the full practice state, including Feedback Aide's scores and the user's expected scores, using getPractice().
const practice = await editor.getPractice();
console.log(practice.sessionState.isExhausted);
// returns true when the access token's limit is reached
for (const resp of practice.responses) {
console.log('---------');
// The full response content can be accessed from `resp.content`
console.log('Response (truncated):', resp.content.slice(0, 80));
console.log('Feedback Aide generated scores:', resp.scoring.autoScores);
console.log('User selected scores:', resp.scoring.manualComparison);
console.log('Credits used:', resp.scoring.creditsUsed);
if (resp.scoring.error) {
console.warn('Evaluation error:', resp.scoring.error.message);
}
}For a full description of the getPractice() return shape, see Rubric Practice API reference.
Putting it all together
The following is a minimal frontend example combining all of the steps above:
<!-- 1. Load the Rubric Editor script -->
<script src="https://feedbackaide.learnosity.com/js/rubric-editor"></script>
<div class="rubric-editor-container"></div>
<script type="module">
// 2. Init the app
const rubricEditorApp = await LearnosityRubricEditor.init();
// 3. Create the editor with Rubric Practice enabled
const editor = await rubricEditorApp.createEditor({
rubric_json: existingRubric,
rubric_practice: {
security_provider: async (context) => {
const response = await fetch('https://yourdomain/your/path/rubric-practice-token-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
return {
practice_uuid: data.practice_uuid,
security: {
access_token: data.access_token,
token_type: 'bearer',
expires_in: data.expires_in,
},
};
},
stimulus: '<p>Describe the water cycle.</p>',
responses: [{ content: '<p>Water evaporates from the ocean...</p>' }],
},
});
// 4. Attach to the DOM - Test tab is now visible
await editor.attach(document.querySelector('.rubric-editor-container'));
</script>Handling credit exhaustion
Each evaluation request consumes practice credits from the access token. When a token's credits run out, the next test run fails and the editor automatically calls your security_provider again with context.reason === 'limit_exhausted'. The test run stays in its loading state and no warning or error shown to the user while the refresh is in flight.
If your endpoint and callback returns a new access token, the editor retries the pending request and practice continues as normal, so requesting a new access token for more credits is seamless from the user's point of view. There is nothing else to disable or re-enable.
If you do not want to grant more credits, you can throw an error from the callback or let it reject. You may want to do this in the case where an author has run an unreasonably high number of tests, for instance. The editor then disables the Run test buttons and shows a warning that the session's practice credits have been exhausted.
The security_provider example in step 3 already covers returning a token. The only addition for credit exhaustion is deciding, when context.reason === 'limit_exhausted', whether to issue a fresh token or stop:
security_provider: async (context) => {
if (context.reason === 'limit_exhausted' && !shouldGrantMoreCredits()) {
// Stop here: the editor disables the Run test buttons and shows
// the "credits exhausted" warning. No token is returned.
throw new Error('Practice credit limit reached');
// or handle it gracefully in your application
}
// Otherwise return a fresh token exactly as in step 3 and practice
// resumes seamlessly.
// ...
},