Level 1: Student Onboarding

Programmatically create and manage organizations, students, users, and their relationships in TimeBack's roster.

What Student Onboarding Provides

Student onboarding enables your learning app to:

  • Query organizations (schools, districts) in TimeBack
  • Fetch users and students belonging to a specific organization
  • Create and update student records in TimeBack's roster
  • Manage user accounts with roles and profiles
  • Link students to agents (parents, guardians, relatives)
  • Query agent relationships for a user

This is foundational for apps that need to manage student data programmatically rather than relying solely on LTI authentication or manual registration.

Prerequisites

Before implementing student onboarding:

  1. Register your app (see Level 0: Registration)
  2. Request OAuth credentials with the roster.createput scope (see Authentication Guide)

Implementation Guide

Step 1: Obtain Access Token

Request an access token with roster write permissions:

const response = await fetch('https://platform.timeback.com/auth/1.0/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
  },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    scope: 'https://purl.imsglobal.org/spec/or/v1p2/scope/roster.createput',
  }),
});

const { access_token } = await response.json();

Step 2: Fetch Organizations

Retrieve all organizations (schools, districts) available in TimeBack:

GET https://platform.timeback.com/rostering/1.0/orgs

Example request:

const response = await fetch('https://platform.timeback.com/rostering/1.0/orgs?limit=100', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

const { orgs, total } = await response.json();

Example response:

{
  "orgs": [
    {
      "sourcedId": "org-uuid-123",
      "status": "active",
      "dateLastModified": "2025-01-02T10:00:00Z",
      "name": "Springfield Elementary",
      "type": "school",
      "identifier": "SPR-001",
      "parent": {
        "sourcedId": "district-uuid-456",
        "type": "org"
      },
      "children": []
    }
  ],
  "offset": 0,
  "limit": 100,
  "total": 1
}

Key response fields:

Field Type Description
sourcedId string Unique identifier for the organization
name string Display name of the organization
type string school, district, department, local, state, national
identifier string External identifier (e.g., school code)
parent object Reference to parent organization (null for top-level)
children array References to child organizations

Step 3: Fetch a Specific Organization

Retrieve details for a single organization by ID:

GET https://platform.timeback.com/rostering/1.0/orgs/{sourcedId}

Example request:

const orgId = 'org-uuid-123';
const response = await fetch(`https://platform.timeback.com/rostering/1.0/orgs/${orgId}`, {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

const { organization } = await response.json();

Step 4: Fetch Users by Organization

Retrieve all users belonging to a specific organization using filtering:

GET https://platform.timeback.com/rostering/1.0/users?filter=primaryOrg.sourcedId='{orgId}'

Example request:

const orgId = 'org-uuid-123';
const filter = encodeURIComponent(`primaryOrg.sourcedId='${orgId}'`);

const response = await fetch(`https://platform.timeback.com/rostering/1.0/users?filter=${filter}&limit=100`, {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

const { users, total } = await response.json();

Filtering by role:

To fetch only students for an organization, combine filters:

const filter = encodeURIComponent(`primaryOrg.sourcedId='${orgId}' AND roles='student'`);

See OneRoster API Conventions for more filtering, pagination, and sorting options.

Step 5: Create or Update a Student

Use the upsertStudent endpoint to create a new student or update an existing one (requires the organization sourcedId from Step 2 or 3):

PUT https://platform.timeback.com/rostering/1.0/students

Example request:

const studentData = {
  student: {
    sourcedId: 'student-uuid-here', // Your unique identifier for this student
    status: 'active',
    username: 'john.doe',
    enabledUser: 'true',
    givenName: 'John',
    familyName: 'Doe',
    middleName: null,
    email: 'john.doe@example.com',
    phone: null,
    grades: ['5'],
    primaryOrg: {
      sourcedId: 'organization-uuid', // The school/org this student belongs to
      type: 'org',
    },
  },
};

const response = await fetch('https://platform.timeback.com/rostering/1.0/students', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify(studentData),
});

// Returns 200 OK on success

Required fields:

Field Type Description
sourcedId string Unique identifier for the student
status string active, inactive, or tobedeleted
username string Login username
enabledUser string "true" or "false" (as strings)
givenName string Student's first name
familyName string Student's last name
primaryOrg object Reference to the student's primary organization
grades string[] Array of grade levels (e.g., ["5", "6"])

Step 6: Create or Update a User

For non-student users (teachers, parents, administrators), use the upsertUser endpoint:

PUT https://platform.timeback.com/rostering/1.0/users/{sourcedId}

Example request:

const userData = {
  user: {
    sourcedId: 'user-uuid-here',
    status: 'active',
    enabledUser: 'true',
    givenName: 'Jane',
    familyName: 'Smith',
    email: 'jane.smith@example.com',
    grades: [],
    primaryOrg: {
      sourcedId: 'organization-uuid',
      type: 'org',
    },
    roles: [
      {
        roleType: 'primary',
        role: 'parent',
        org: {
          sourcedId: 'organization-uuid',
          type: 'org',
        },
      },
    ],
  },
};

const response = await fetch(`https://platform.timeback.com/rostering/1.0/users/${userData.user.sourcedId}`, {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify(userData),
});

// Returns 201 Created on success

Available roles:

  • student, teacher, parent, guardian, relative
  • aide, counselor, principal, proctor
  • districtAdministrator, siteAdministrator, systemAdministrator

Create relationships between students and their agents (parents, guardians):

PUT https://platform.timeback.com/rostering/1.0/students/{sourcedId}/agents/{agentId}

Path parameters:

Parameter Type Description
sourcedId string Unique identifier of the student
agentId string A client-generated UUID that uniquely identifies this agent relationship. Use this same ID to update or delete the relationship later.

Example request:

const agentData = {
  user: {
    sourcedId: 'parent-user-uuid', // The agent's user ID
    type: 'user',
  },
  relationshipType: 'parent', // free-form string describing the relationship
};

// Generate a UUID for the agent relationship (client-generated)
const agentRelationshipId = crypto.randomUUID();

const response = await fetch(
  `https://platform.timeback.com/rostering/1.0/students/${studentId}/agents/${agentRelationshipId}`,
  {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify(agentData),
  },
);

// Returns 201 Created on success

Relationship type:

The relationshipType is a free-form string. Use any value that describes the relationship in your system.

Step 8: Query Linked Users

Retrieve bidirectional agent relationships for a user. This endpoint shows both:

  • agentsAsSource: Users who are agents of the queried user (e.g., a student's parents/guardians)
  • agentsAsAgent: Users for whom the queried user is an agent (e.g., a parent's children)
GET https://platform.timeback.com/rostering/1.0/users/{sourcedId}/linked-users

Example request:

const response = await fetch(`https://platform.timeback.com/rostering/1.0/users/${userId}/linked-users`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
});

const data = await response.json();

Example response:

{
  "agentsAsSource": [
    {
      "agentId": "agent-relationship-uuid",
      "relationshipType": "parent",
      "userId": "parent-user-uuid"
    }
  ],
  "agentsAsAgent": [
    {
      "agentId": "agent-relationship-uuid-2",
      "relationshipType": "guardian",
      "userId": "child-user-uuid"
    }
  ]
}

Response fields:

Field Type Description
agentsAsSource array Users who are agents of the queried user (e.g., parents)
agentsAsAgent array Users for whom the queried user is an agent (e.g., children)
agentId string Unique identifier for this agent relationship (use for update/delete)
relationshipType string The type of relationship (e.g., "parent", "guardian")
userId string The sourcedId of the linked user

Step 9: Query User Agents

Retrieve all agents associated with a user (e.g., get a student's parents):

GET https://platform.timeback.com/rostering/1.0/users/{sourcedId}/agents

Example request:

const response = await fetch(`https://platform.timeback.com/rostering/1.0/users/${userId}/agents`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
});

const data = await response.json();
// Returns: { agents: [{ agentId, user: { sourcedId, type }, relationshipType }] }

Example response:

{
  "agents": [
    {
      "agentId": "agent-relationship-uuid",
      "user": {
        "sourcedId": "parent-user-uuid",
        "type": "user"
      },
      "relationshipType": "parent"
    }
  ]
}

Response fields:

Field Type Description
agentId string Unique identifier for this agent relationship (use for update/delete)
user object Reference to the agent user with sourcedId and type
relationshipType string The type of relationship (e.g., "parent", "guardian")

Step 10: Remove an Agent Relationship

Delete an agent relationship when it's no longer valid:

DELETE https://platform.timeback.com/rostering/1.0/students/{sourcedId}/agents/{agentId}

Path parameters:

Parameter Type Description
sourcedId string Unique identifier of the student
agentId string The UUID of the agent relationship to delete (same ID used when creating the relationship)

Example request:

// Use the agentId from getUserAgents or getUserLinkedUsers response
const response = await fetch(
  `https://platform.timeback.com/rostering/1.0/students/${studentId}/agents/${agentRelationshipId}`,
  {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  },
);

// Returns 204 No Content on success

Required Scopes

Operation Required Scope
getAllOrganizations https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly
getOrganization https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly
getAllUsers https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly
upsertStudent https://purl.imsglobal.org/spec/or/v1p2/scope/roster.createput
upsertUser https://purl.imsglobal.org/spec/or/v1p2/scope/roster.createput
upsertStudentAgent https://purl.imsglobal.org/spec/or/v1p2/scope/roster.createput
deleteStudentAgent https://purl.imsglobal.org/spec/or/v1p2/scope/roster.createput
getUserAgents https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly
getUserLinkedUsers https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly

Common Pitfalls

Not matching sourcedId in path and body

// ❌ Bad: Path and body sourcedId don't match
await fetch('/rostering/1.0/users/user-123', {
  body: JSON.stringify({ user: { sourcedId: 'user-456', ... } }),
});

// ✅ Good: Path and body sourcedId match
await fetch('/rostering/1.0/users/user-123', {
  body: JSON.stringify({ user: { sourcedId: 'user-123', ... } }),
});

Using boolean instead of string for enabledUser

// ❌ Bad: Boolean value
{
  enabledUser: true;
}

// ✅ Good: String value
{
  enabledUser: 'true';
}

Missing required organization reference

// ❌ Bad: Missing primaryOrg
{ student: { sourcedId: '...', givenName: 'John', ... } }

// ✅ Good: Include primaryOrg
{
  student: {
    sourcedId: '...',
    givenName: 'John',
    primaryOrg: { sourcedId: 'org-uuid', type: 'org' },
    ...
  }
}

Register your app first to get OAuth credentials for API access.

Implement seamless authentication so students can access your app without credentials.

Send learning activity events to unlock coaching insights and unified analytics.

Filtering, pagination, sorting, and field selection for roster APIs.

Detailed guide on obtaining and managing OAuth access tokens.