Signals Feedback
Tell TimeBack what you think of the insights and actions it raised about your students, straight from your own systems.
Note: This guide covers the integration flow. See the API Reference for complete endpoint documentation, all parameters, and response schemas.
What feedback is
TimeBack watches a learning session and draws conclusions from it.
- An insight is a conclusion, for example "away from seat".
- An action is what a student was shown because of insights, for example a warning message.
Feedback is what a person says about one insight or one action. It goes to the people who tune the rules that produce insights. It never changes the insight or the action it is about, and nothing waits for it.
Feedback arrives from two places and lands in the same store:
- A person in a TimeBack product (StudyFilm, the TimeBack App), under their own sign-in.
- Your integration, using your school credential and naming a staff member as the person giving the feedback.
Prerequisites
-
Register your App and obtain OAuth credentials. See Level 0: Register Your App and Authentication.
-
Request the Signals scopes for your M2M client:
- Read:
https://timeback-platform.trilogy.com/signals/scope/insights.readonly - Write:
https://timeback-platform.trilogy.com/signals/scope/feedback.write
A client that lacks either scope requests it as an extra scope through Getting access.
- Read:
-
Have an organization grant. Your credential reaches the students of the organizations it is granted, and the organizations under them. A credential with no organization grant receives
403on every call.
Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/signals/1.0/sessions/{sessionId}/insights |
insights.readonly |
List a session's insights, to get insight ids |
PUT |
/signals/1.0/insights/{insightId}/feedback |
feedback.write |
Rate an insight, or replace an earlier rating |
GET |
/signals/1.0/sessions/{sessionId}/feedback |
insights.readonly |
Read one person's ratings on a session's insights |
GET |
/signals/1.0/sessions/{sessionId}/actions |
insights.readonly |
List a session's actions, to get action ids |
PUT |
/signals/1.0/actions/{actionId}/feedback |
feedback.write |
Respond to an action, or replace an earlier response |
GET |
/signals/1.0/actions/{actionId}/feedback |
insights.readonly |
Read every response on an action that you may see |
sessionId is the session id you already know from Caliper and from Level 4: Insights API. URL-encode it when it contains characters such as :.
Who may be named as submitter
Every write carries a submitterId: the platform user id of the person whose opinion this is. The platform checks that person against your credential.
- Your school credential may name a staff member who holds a role in one of your granted organizations. It may not name a student. A student's feedback only arrives from the student, signed in to a TimeBack product.
- A person signed in to a TimeBack product is always the submitter of their own feedback.
The insight or action must also belong to a student in your scope. Either check failing returns 403.
Resubmitting replaces
One person holds at most one rating per insight. Sending a new rating for the same insight and the same submitterId replaces the earlier rating and note.
On an action, one person holds at most one response of each kind. A staff member can acknowledge an action and also dispute it. Each is its own row. Sending the same kind again replaces that row's reason and note.
This makes every write safe to retry after a timeout.
Rate an insight
1. Find the insight id
const sessionId = encodeURIComponent('urn:uuid:7c1f0a52-3c3b-4d6e-9f0e-2b8a1f4c5d6e');
const response = await fetch(`https://platform.timeback.com/signals/1.0/sessions/${sessionId}/insights?limit=100`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const { insights, total } = await response.json();
{
"insights": [
{
"id": "3f2b9c1e-8a4d-4f6b-b2e1-9d7c5a3e1f20",
"type": "AwayFromSeat",
"version": "1",
"rule": "Converted from old Insights",
"ruleVersion": "1",
"startedAt": "2026-10-14T09:05:00.000Z",
"endedAt": "2026-10-14T09:07:30.000Z"
}
],
"offset": 0,
"limit": 100,
"total": 1
}
The insight id is the same id that GET /insights/1.0/sessions/{sessionId} returns, so an insight you already hold from Level 4 can be rated directly. This list also returns insights that Level 4 does not serve, such as suppressed ones. limit is at most 100. Page with offset until you have read total rows.
2. Submit the rating
const response = await fetch(`https://platform.timeback.com/signals/1.0/insights/${insightId}/feedback`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
submitterId: guideUserId,
rating: 'negative',
note: 'The student left to help a sibling, and the absence was approved.',
}),
});
| Field | Type | Required | Description |
|---|---|---|---|
submitterId |
uuid | Yes | The staff member whose opinion this is |
rating |
string | Yes | positive (the insight is right) or negative |
note |
string | No | Why, in free text. At most 5000 characters |
A 200 returns the stored row:
{
"insightId": "3f2b9c1e-8a4d-4f6b-b2e1-9d7c5a3e1f20",
"submitterId": "a1b2c3d4-0000-4000-8000-000000000001",
"kind": "feedback",
"rating": "negative",
"note": "The student left to help a sibling, and the absence was approved.",
"dateCreated": "2026-10-14T10:00:00.000Z",
"dateLastModified": "2026-10-14T10:00:00.000Z"
}
3. Read the ratings back
const response = await fetch(
`https://platform.timeback.com/signals/1.0/sessions/${sessionId}/feedback?submitterId=${guideUserId}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
{
"feedbacks": [
{
"insightId": "3f2b9c1e-8a4d-4f6b-b2e1-9d7c5a3e1f20",
"rating": "negative",
"note": "The student left to help a sibling, and the absence was approved.",
"dateCreated": "2026-10-14T10:00:00.000Z"
}
]
}
Rows come oldest first. An unknown session returns 404. A revoked session returns an empty list.
The two list calls never return 404: an unknown or revoked session lists no insights and no actions.
Respond to an action
1. Find the action id
const response = await fetch(`https://platform.timeback.com/signals/1.0/sessions/${sessionId}/actions?limit=100`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const { actions } = await response.json();
Each action carries actionId, the message the student was shown, its state (OPEN until the student's app displayed it, then SEEN), and startedAt and endedAt. Page with limit (at most 100) and offset.
2. Submit the response
await fetch(`https://platform.timeback.com/signals/1.0/actions/${actionId}/feedback`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
submitterId: guideUserId,
kind: 'dispute',
reason: 'Approved absence',
note: 'The guide approved the break before the test.',
}),
});
| Field | Type | Required | Description |
|---|---|---|---|
submitterId |
uuid | Yes | The staff member responding |
kind |
string | Yes | feedback, dispute, or acknowledgement |
reason |
string | No | A short reason. Stored as you send it; the platform has no fixed list |
note |
string | No | Free text. At most 5000 characters |
A 200 returns the stored row with actionId, submitterId, kind, reason, note, dateCreated, and dateLastModified.
3. Read the responses back
GET /signals/1.0/actions/{actionId}/feedback returns { "feedback": [...] }: every response on that action whose submitter is in your scope, including the student's own responses from the TimeBack App.
Errors
| Status | When |
|---|---|
400 |
The body or query fails validation, for example a missing rating or an unknown kind |
401 |
The token is missing, expired, or lacks the scope the endpoint needs |
403 |
The insight's or action's student is outside your scope, the submitter is outside your scope or is a student, or your credential has no organization grant |
404 |
On a feedback call: the insight, action, or session does not exist, or its session was revoked |
Error bodies carry error and message.
