Integrations
Schole connects to the tools your organisation already uses — from your HR system to your Learning Record Store — keeping data in sync without manual work.
This integration lets a partner platform drop its already-signed-in users straight into a Schole lesson — no second registration, no second login. Your backend pre-registers each learner over the API, then mints a one-time link that opens the lesson in a new tab, already authenticated.
Ask your Schole administrator for an OAuth2 client-credentials application
scoped to your organisation. You receive a client_id and client_secret.
Every API call below is authenticated with a bearer token obtained from them:
curl -X POST https://app.schole.ai/oauth/token/ \
-d grant_type=client_credentials \
-d client_id=$SCHOLE_CLIENT_ID \
-d client_secret=$SCHOLE_CLIENT_SECRET
# → { "access_token": "…", "token_type": "Bearer", "expires_in": 43200 }
Call POST /api/v1/users/ when a user is onboarded on your side (and again
whenever their details change — it is an idempotent upsert). Identify each learner with
provider + provider_id (your stable user id) so re-syncs never
create duplicates.
curl -X POST https://app.schole.ai/api/v1/users/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "partner",
"provider_id": "ext-12345",
"email": "anna@acme.com",
"first_name": "Anna",
"last_name": "Keller",
"preferred_language": "de",
"user_summary": "Backend engineer, 3y; wants to learn Kubernetes.",
"job_titles": ["Backend Engineer"],
"department": "Platform",
"tools": ["Python", "Docker"],
"daily_tasks": ["Code review", "On-call"]
}'
# → 201 Created (new) or 200 OK (updated)
| Field | Why it matters |
|---|---|
provider + provider_id |
Stable identity for the learner. Required for the hand-off in Step 2 and for idempotent re-syncs. Also tells Schole this user is partner-managed, so they skip Schole's skills-selection onboarding. |
preferred_language |
Pass it if you know it (en, de, fr, it). Lessons are delivered in this language; if omitted the learner is asked to choose. |
user_summary |
A free-text description of the learner. Feeds Schole's AI personalization of lesson content. |
course + cohort |
The course the learner bought (its Schole slug, e.g. agentic-ai) and the intake they're joining (its published start date, YYYY-MM-DD). Enrolls the learner and grants any premium access the course carries. Optional, but sending one without the other is a 400. Re-sending a different cohort moves the learner (and their access window) to that intake. |
job_titles, department, tools, daily_tasks |
Optional. Improves AI personalization of lessons. Not required to reach the lesson — partner-provisioned learners skip Schole's onboarding automatically. |
Mint a login_token for the learner by calling
POST /api/v1/auth/partner-exchange/ from your backend with their id. The
token is single-use and valid for 1 hour — long
enough to embed directly in a lesson link rendered on your page, or to mint on click
(see the note in Step 3 on which to choose).
curl -X POST https://app.schole.ai/api/v1/auth/partner-exchange/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "external_id": "ext-12345" }'
# → { "login_token": "eyJ…", "expires_in": 3600 }
Open Schole's /auto-login route in a new tab, passing the token and a
next path. Schole redeems the token, establishes the session, and forwards
the learner to next.
To drop the learner straight into a ready lesson, point next
at the lesson launcher with a template_id. The launcher instantiates the lesson
for that learner (or reuses it if they've started it before) and opens it — content
generation begins automatically.
https://app.schole.ai/auto-login?token=eyJ…&next=%2Fstart-lesson%3Ftemplate_id%3D123
Get a template_id from the catalog: GET /api/v1/curricula/templates/.
You can also send the learner to any other relative Schole path (e.g. their home,
/home) if you don't want to target a specific lesson.
The login_token is single-use (burned the first time it's redeemed) and
valid for 1 hour. Two things to keep in mind when you embed links on a page:
- One token per link. A redeemed token can't be reused, so a
back-button or refresh re-click of the same baked-in link will fail. If you need
re-clickable links, mint on click instead (e.g. point the link at a small redirect
endpoint on your side that calls
partner-exchangeand forwards to/auto-login). - The token rides in the URL — so it appears in browser history and
the
Refererheader. For pages that stay open a long time, minting on click keeps the token fresh and its exposure brief.
next must be a relative Schole path; if it carries its own query string
(e.g. ?template_id=123) percent-encode it so it isn't parsed as part of the
outer URL.
Two options, both already available:
- Push (xAPI): point Schole at your Learning Record Store and receive
attempted/progressed/completedstatements in real time — see the xAPI section below. - Pull (analytics): read a learner's progress on demand with
GET /api/v1/analytics/users/<id>/summary/(and/progress/,/scores/).
If you run an LMS (Canvas, Moodle, Blackboard, D2L…), you can launch learners into Schole with LTI 1.3 instead of the custom hand-off above. Schole is the Tool; your LMS is the Platform. A learner clicks a Schole link in your LMS and lands in the lesson, already logged in — no pre-registration call required (accounts are created just-in-time from the launch).
issuer, the client_id issued to Schole, the deployment_id, your OIDC auth URL and your JWKS URL.| LMS setting | Schole URL |
|---|---|
| OIDC login init URL | https://app.schole.ai/api/v1/lti/login/ |
| Redirect / launch URL | https://app.schole.ai/api/v1/lti/launch/ |
- The learner clicks the Schole link in your LMS → the LMS hits Schole's login URL (OIDC third-party init).
- Schole redirects back to your platform's auth URL; your LMS returns a signed
id_token. - Schole verifies the
id_tokenagainst your JWKS, creates/updates the learner (identity fromsub; name,emailandlocalefrom standard claims), and opens the lesson — logged in.
No. With LTI the account is created just-in-time from the launch, so a learner can launch and start a lesson with no prior API call.
Pre-registering with POST /api/v1/users/ (see Partner SSO,
Step 1) is optional and only useful when you want something LTI doesn't
carry: richer AI personalization (user_summary, role, tools,
daily tasks) or pre-launch state (assigned lessons, dashboards)
before anyone clicks. If you do both, match on email so the API record and the LTI
launch resolve to the same learner — so send email in the launch claims.
Add a custom parameter template_id to the link's launch
configuration and the learner lands directly on that lesson (otherwise they land on
their home). Template ids come from GET /api/v1/curricula/templates/.
template_id=123
Launches open in a new tab (in-iframe embedding is not yet supported). Grade passback (AGS), roster sync (NRPS) and Deep Linking content selection are planned increments.
Kombo.dev connects Schole to your existing HR system so that user accounts and teams stay in sync automatically — no manual provisioning required.
Supported HRIS platforms include Workday, SAP SuccessFactors, Personio, BambooHR, and many others.
| Event in your HRIS | What happens in Schole |
|---|---|
| Employee joins | A Schole account is created automatically |
| Employee's name or job title changes | Their Schole profile is updated |
| Employee leaves | Their Schole account is deactivated |
| A group / team is created or updated | The corresponding Schole team is created or its members updated |
Kombo is configured per organisation. To enable it, contact your Schole administrator with:
- Your Kombo integration ID (obtained from the Kombo dashboard after connecting your HRIS)
- A webhook secret for secure delivery of real-time events
- A Kombo API key for the nightly reconciliation sync
xAPI 1.0.3 (also known as Experience API or Tin Can) is an industry standard for recording learning activity. Schole can send xAPI statements to your organisation's Learning Record Store (LRS), giving you a detailed, standardised record of how learners engage with content.
| Event | xAPI verb |
|---|---|
| Learner starts a subsection | attempted |
| Learner completes a subsection (course not yet finished) | progressed |
| Learner finishes the whole course | completed |
attempted is scoped to the specific subsection the learner just started —
its object.id is an opaque internal IRI keyed on that instantiated lesson
(not a public URL you can resolve or call), e.g.
https://app.schole.ai/lessons/482/subsections/931.
progressed and completed are scoped to the course
instead: object.id is
https://app.schole.ai/curricula/templates/<template_id> — the same
template_id you already use to launch a lesson — so every statement about one
course, from every learner and every re-instantiation, shares a single stable Activity your
LRS can aggregate against. They fire once per subsection completed (a checkpoint, not
per-message), carry the learner's overall percentage across all sections of the
course under result.extensions (the
cmi5 progress extension
IRI, 0–100), and a result.completion boolean (false until the
course is done, true on the one completed statement).
Verb and extension keys are IRIs too, e.g. http://adlnet.gov/expapi/verbs/completed
or https://app.schole.ai/xapi/extensions/user_id — none of these are meant to be
fetched, they're just unique names. Learners are identified by email
(actor.mbox); statements also carry the learner's Integration API user id
and, when set, your external_id as context.extensions, so you can
correlate statements with the Users and Analytics endpoints.
progressed and completed are delivered at least once: a failed send
is retried automatically with backoff, and anything still failing (e.g. your LRS
momentarily unreachable, or rejecting a learner it hasn't synced yet) is retried again on a
short interval until it succeeds — nothing is silently dropped. attempted is
best-effort — a failed send is logged and not retried, since nothing depends on it arriving.
A retry re-sends the exact same statement — same id, same body — so your
receiver can safely dedupe or reprocess on that id without special-casing
retried deliveries.
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"actor": {
"objectType": "Agent",
"mbox": "mailto:alice@example.com",
"name": "Alice Smith"
},
"verb": { "id": "http://adlnet.gov/expapi/verbs/attempted", "display": { "en-US": "attempted" } },
"object": {
"objectType": "Activity",
"id": "https://app.schole.ai/lessons/482/subsections/931",
"definition": { "type": "http://adlnet.gov/expapi/activities/lesson", "name": { "en-US": "Intro to Negotiation" } }
},
"result": { "extensions": { "https://w3id.org/xapi/cmi5/result/extensions/progress": 0 } },
"context": { "extensions": { "https://app.schole.ai/xapi/extensions/user_id": 102, "https://app.schole.ai/xapi/extensions/external_id": "clientA-12345" } },
"timestamp": "2026-06-23T14:32:07.123456+00:00"
}
{
"id": "5b1f6a2e-9d3c-4a1b-8e2f-7c4d6a9b3e10",
"actor": {
"objectType": "Agent",
"mbox": "mailto:alice@example.com",
"name": "Alice Smith"
},
"verb": { "id": "http://adlnet.gov/expapi/verbs/progressed", "display": { "en-US": "progressed" } },
"object": {
"objectType": "Activity",
"id": "https://app.schole.ai/curricula/templates/57",
"definition": { "type": "http://adlnet.gov/expapi/activities/course", "name": { "en-US": "Negotiation Fundamentals" } }
},
"result": { "completion": false, "extensions": { "https://w3id.org/xapi/cmi5/result/extensions/progress": 60 } },
"context": { "extensions": { "https://app.schole.ai/xapi/extensions/user_id": 102, "https://app.schole.ai/xapi/extensions/external_id": "clientA-12345" } },
"timestamp": "2026-06-23T14:33:42.654321+00:00"
}
{
"id": "8e2f7c4d-6a9b-3e10-5b1f-6a2e9d3c4a1b",
"actor": {
"objectType": "Agent",
"mbox": "mailto:alice@example.com",
"name": "Alice Smith"
},
"verb": { "id": "http://adlnet.gov/expapi/verbs/completed", "display": { "en-US": "completed" } },
"object": {
"objectType": "Activity",
"id": "https://app.schole.ai/curricula/templates/57",
"definition": { "type": "http://adlnet.gov/expapi/activities/course", "name": { "en-US": "Negotiation Fundamentals" } }
},
"result": { "completion": true, "extensions": { "https://w3id.org/xapi/cmi5/result/extensions/progress": 100 } },
"context": { "extensions": { "https://app.schole.ai/xapi/extensions/user_id": 102, "https://app.schole.ai/xapi/extensions/external_id": "clientA-12345" } },
"timestamp": "2026-06-23T14:35:10.987654+00:00"
}
xAPI is configured per organisation. To enable it, provide your Schole administrator with:
- Your LRS endpoint URL (e.g.
https://lrs.example.com/xapi/) - The authentication method your LRS expects — Schole supports both:
- HTTP Basic Auth — a username/password (key & secret) sent on each request.
- OAuth2 client credentials (Bearer) — a token endpoint URL, client ID, client secret, and optional scope. Schole obtains a short-lived Bearer token via the
client_credentialsgrant, caches it, and refreshes it automatically.
Once configured, Schole will begin sending statements to your LRS automatically. Statements follow the standard xAPI format and include the learner's identity, the activity, and a timestamp.