Developer guide
This is the evergreen, generic getting-started for the TimeBack Partner API: authentication, base URLs, and the end-to-end integration flow.
This public guide contains no secrets. Your real clientId / clientSecret, edApp ID, scopes, and SSO config are in the provisioning bundle you download from the Partner Portal. The bundle also ships a partner-specific, agent-ready gettingStartedMarkdown that references those values by their exact JSON field names. Hand the bundle to your engineers (or your coding agent); treat it like an API key.
Base URLs
| Environment | API base URL | Docs |
|---|---|---|
| Production | https://api.alpha-1edtech.ai | https://docs.alpha-1edtech.ai |
The Partner API is production-only for partners. Every example below uses the public CloudFront origin https://api.alpha-1edtech.ai — there is no /prod path prefix. CloudFront routes /auth/1.0/*, /rostering/1.0/*, /events/1.0/*, etc. to the ext-dev-api gateway, so you always hit the public domain, never a raw gateway URL.
Authenticate
Exchange your client credentials for a Bearer access token at the public proxy token endpoint:
POST https://api.alpha-1edtech.ai/auth/1.0/token
Two authentication methods are supported — pick one:
- HTTP Basic auth: send
Authorization: Basic <base64(client_id:client_secret)>plusgrant_type=client_credentialsin the form body. - Request body: send
client_idandclient_secretin the body (application/jsonorapplication/x-www-form-urlencoded).
Basic-auth example
curl -X POST https://api.alpha-1edtech.ai/auth/1.0/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic <BASE64_CLIENT_ID_COLON_SECRET>" \
-d "grant_type=client_credentials"
Request-body example
curl -X POST https://api.alpha-1edtech.ai/auth/1.0/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
The response includes an access_token (a JWT, token_type: "Bearer") valid for 1 hour (expires_in: 3600). Cache and reuse it; re-request when it expires. Send it as Authorization: Bearer <access_token> on every other endpoint.
The client secret is provided once, in your bundle. Store it securely. If lost, re-provision to generate a new secret (the old one is revoked).
Your edApp identity
Your bundle contains an edApp.id URL of the form https://api.alpha-1edtech.ai/applications/1.0/{YOUR_APP_SOURCED_ID}. Use it as actor.id and edApp.id in all Caliper events you send.
The integration flow
A minimal happy path, top to bottom. During integration testing, use partner-sandbox as your org.sourcedId and always set metadata.isTestUser: true on test users so they're excluded from production reporting.
- Get a token —
POST /auth/1.0/token(above). - Create a test student —
PUT /rostering/1.0/users/{sourcedId}. - Create a course —
PUT /rostering/1.0/courses/{sourcedId}. - Create a class —
PUT /rostering/1.0/classes/{sourcedId}. - Enroll the student —
PUT /rostering/1.0/enrollments/{sourcedId}. - Send your first event —
POST /events/1.0(a GradeEvent reporting XP). - Verify —
GET /xp/1.0/users/{sourcedId}/entriesafter the materialization window.
Full request/response shapes and curl examples for every endpoint are in the API reference. The rostering writes (users, classes, enrollments) are testing-only scaffolding — in production, Alpha owns them.
Step 2: Create a test student
Use the OneRoster v1.2 User schema. Generate a UUID for sourcedId — the same ID is embedded in Caliper event URLs.
STUDENT_ID=$(uuidgen) # or crypto.randomUUID() in JS
curl -X PUT https://api.alpha-1edtech.ai/rostering/1.0/users/$STUDENT_ID \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"user": {
"sourcedId": "'$STUDENT_ID'",
"dateLastModified": "2026-01-01T00:00:00.000Z",
"enabledUser": "true",
"givenName": "Test",
"familyName": "Student",
"email": "test-student@yourapp.com",
"roles": [{
"role": "student",
"roleType": "primary",
"org": { "sourcedId": "partner-sandbox" }
}],
"metadata": { "isTestUser": true }
}
}'
isTestUser on test accountsSet metadata.isTestUser: true on every student you create while testing. This flag excludes the student from all production reporting, analytics, dashboards, XP leaderboards, and mastery rollups. There is no server-side mechanism that sets it for you — omit it and your test activity contaminates real dashboards. For real students on a live deployment, omit the flag (or set it to false).
Step 6: Send your first event
A GradeEvent reports XP earned. extensions.course.id is required and must resolve to a course you own.
curl -X POST https://api.alpha-1edtech.ai/events/1.0 \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"@context": "http://purl.imsglobal.org/ctx/caliper/v1p2",
"id": "urn:uuid:YOUR_UNIQUE_EVENT_ID",
"type": "GradeEvent",
"profile": "GradingProfile",
"action": "Graded",
"eventTime": "2026-01-01T12:00:00.000Z",
"actor": { "id": "https://api.alpha-1edtech.ai/applications/1.0/YOUR_APP_SOURCED_ID", "type": "SoftwareApplication" },
"edApp": { "id": "https://api.alpha-1edtech.ai/applications/1.0/YOUR_APP_SOURCED_ID", "type": "SoftwareApplication" },
"object": {
"id": "https://yourapp.com/activities/example",
"type": "Attempt",
"count": 1,
"assignee": { "id": "https://api.alpha-1edtech.ai/rostering/1.0/users/YOUR_STUDENT_ID", "type": "Person" },
"assignable": { "id": "https://yourapp.com/resources/lesson-1", "type": "DigitalResource", "mediaType": "application/json" },
"extensions": { "activityName": "Example Lesson" }
},
"generated": {
"id": "https://yourapp.com/scores/example",
"type": "Score",
"scoreGiven": 100,
"maxScore": 100,
"extensions": { "scoreType": "XP" }
},
"extensions": {
"subject": "Math",
"course": { "id": "https://api.alpha-1edtech.ai/rostering/1.0/courses/YOUR_COURSE_ID" }
}
}'
For the full event reference, including AssessmentEvent and heartbeats, see API reference → Events.
Scopes
Every partner receives the same scope set on day one. These authorize the surfaces you'll use:
| Scope | Authorizes |
|---|---|
events.write | POST /events/1.0 (send Caliper events) |
events.readonly | GET on /analytics/1.0/*, /xp/1.0/*, /insights/1.0/* (read processed event data). There is no raw event read-back endpoint; use XP/analytics to verify events were processed. |
roster.readonly | GET on /rostering/1.0/* (read users, courses, classes, enrollments, orgs) |
roster.createput | PUT on /rostering/1.0/{entity}/{sourcedId} (create/update roster data) |
gradebook.readonly | GET on /gradebook/1.0/* (read assessment results and line items) |
standards.read | GET on /standards/1.0/* (read frameworks and objectives) |
There is no dedicated analytics.readonly scope today; events.readonly authorizes everything under /analytics/1.0/*, /xp/1.0/*, and /insights/1.0/*.
Event processing requirements
After you POST an event:
- Validated — schema checked;
400if invalid. - Stored — written to the Caliper activity trail (Parquet/S3).
- Processed — resolved to student, app, course → analytics tables (30–120 seconds).
- Available — visible in dashboards, XP leaderboards, coaching insights.
On every GradeEvent / AssessmentEvent / AssessmentItemEvent:
- The student (
actororobject.assignee) must exist —PUT /rostering/1.0/users/{id}. extensions.subjectis required (enum:Math,Reading,Language, etc.).extensions.course.idis required and must resolve to a course you own. Events pinning a course you don't own are rejected after acceptance — the202response stands but no ALI / AR / processed_fact rows are written; logs capture the rejected payload.
Attribution: if the student has an active enrollment in the pinned course, the resulting processed_fact row carries its enrollment_id. If not, the fact lands with enrollment_id = null — activity is still recorded and attributed to the course, but only rolls up into enrollment-scoped analytics once the student is enrolled.
Events are send-only. POST /events/1.0 is the only event-write entry point; there is no read-back endpoint at /events/*. To verify an event was processed, query the matching analytics / XP endpoint after the 30–120 s window. See API reference → Analytics.
Filtering list endpoints
All collection endpoints (GET without a sourcedId) support OneRoster filtering via the filter query parameter. Custom query parameters like ?userId= are not supported.
# Filter assessment results for a specific student
GET /gradebook/1.0/assessment-results?filter=student.sourcedId='STUDENT_ID'
# Filter courses by subject
GET /rostering/1.0/courses?filter=subjects='Math'
# Combine filters with AND
GET /rostering/1.0/enrollments?filter=role='student' AND class.sourcedId='CLASS_ID'
Supported operators: =, !=, >, >=, <, <=, ~ (LIKE), @ (IN). Logical: AND, OR. Other query params: limit, offset, sort, orderBy (asc/desc), fields (comma-separated selection). List endpoints are automatically scoped to resources owned by your client application — you don't need to send any ownership filter.
Next steps
- Browse the full API reference — one page per functional group.
- Read the Product overview for the conceptual model (XP, ownership, the integration shape).
- Download your provisioning bundle and hand its
gettingStartedMarkdownto your coding agent.