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

  1. Register your App and obtain OAuth credentials. See Level 0: Register Your App and Authentication.

  2. 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.

  3. 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 403 on 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.