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:
- Register your app (see Level 0: Registration)
- Request OAuth credentials with the
roster.createputscope (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,relativeaide,counselor,principal,proctordistrictAdministrator,siteAdministrator,systemAdministrator
Step 7: Link a Student to an Agent
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' },
...
}
}
Related Docs
Level 0: Registration
Register your app first to get OAuth credentials for API access.
Level 2: LTI Launch
Implement seamless authentication so students can access your app without credentials.
Level 3: Caliper Events
Send learning activity events to unlock coaching insights and unified analytics.
OneRoster API Conventions
Filtering, pagination, sorting, and field selection for roster APIs.
Authentication
Detailed guide on obtaining and managing OAuth access tokens.