Reseller integration — end-to-end
A step-by-step recipe for embedding Schole's course catalogue inside your own platform — an LMS, a marketplace, or a corporate portal — so your clients can browse Schole content, launch lessons single-sign-on, and see completion flow back into their reporting.
A reseller integration reuses building blocks documented elsewhere in this portal — the Partner SSO hand-off, the course catalogue, and xAPI tracking. This guide stitches them into one linear flow. Each step links to the authoritative reference.
Authenticate
Ask your Schole contact for an OAuth2 client-credentials application
scoped to the organisation you are reselling for. You receive a client_id
and client_secret. Exchange them for a bearer token; every call in this
guide carries that token.
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 }
Use the trailing slash on /oauth/token/ — some HTTP clients
drop the body when following the redirect on a slash-less POST.
Resellers typically manage several end-clients, each with their own user base. Two models:
client_id / client_secret and its own
tenant. Cleanest separation of users, catalogue and reporting. Provisioned manually today,
so best when you onboard clients gradually.provider_id (e.g. clientA-12345). Simpler to start, but mixes
users — use only if you need a single account from day one.There is no self-service provisioning yet, so coordinate with your Schole contact on the model and on creating each client's credentials.
Sync the catalogue
GET /api/v1/curricula/templates/ is the backbone of the browsing experience —
it returns every template visible to your organisation. Mirror each one as a course (or an
activity) on your side. The endpoint is filterable, sortable and paginated, so a nightly
delta sync is cheap.
# Full first pull (page through until `next` is null)
curl -G https://app.schole.ai/api/v1/curricula/templates/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
--data-urlencode "page=1" \
--data-urlencode "page_size=100"
# Nightly delta sync — only what changed since the last run
curl -G https://app.schole.ai/api/v1/curricula/templates/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
--data-urlencode "modified_after=2026-06-01T00:00:00Z" \
--data-urlencode "sort=-modified_date,title"
| Query param | Use |
|---|---|
search |
Keyword match on title or description. |
language |
Language code (en, de, fr, it). |
lesson_type |
mandatory, premade, or custom. |
sort |
Comma-separated; prefix - for descending. Allowed: title, creation_date, modified_date, lesson_type, language, category, participant_count. Default title. |
modified_after |
ISO-8601 timestamp. Returns only templates changed since then — the basis of delta sync. |
The response is the standard paginated envelope (count / next /
previous / results) — see
Pagination. For the exact, always-current
field list of a template object, explore the live schema in
Swagger UI rather than hard-coding fields.
Templates can be retired (soft-deleted). A retired template is hidden from this list by default and can no longer be picked for new lessons, but existing learners who already started it keep working. Because retired rows simply drop out of the default list, your nightly reconciliation should treat a template that has disappeared as removed.
- Pass
?include_inactive=trueto also return retired templates, then read each row'sis_activeboolean to tell them apart.
Provision learners
Upsert each learner with POST /api/v1/users/ when they are onboarded on your side
(and again whenever their details change — it is idempotent). Key every learner on
provider + provider_id (your stable id) so re-syncs never
create duplicates and so the SSO hand-off in Step 4 can find them.
curl -X POST https://app.schole.ai/api/v1/users/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "totara",
"provider_id": "clientA-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"
}'
# → 201 Created (new) or 200 OK (updated). The response carries Schole's internal user id.
provideris a free label you choose and use consistently (e.g."totara"). It marks these users as partner-managed, so they skip Schole's own skills-selection onboarding.- Richer profile = better lessons. Because each lesson is personalised on the
fly, the more context you pass (
user_summary,job_titles,department,tools,daily_tasks), the more tailored the content. All optional. - IDs in responses are Schole-internal integers. Assume internal IDs everywhere
unless a field is explicitly your
provider_id.
Launch a lesson (single sign-on)
When a learner clicks a course in your UI, hand them off into Schole already logged in. Mint a
one-time login token for them, then open Schole's /auto-login route pointed at the
lesson launcher. This is the Partner SSO
flow — summarised here for the reseller path.
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": "clientA-12345" }'
# → { "login_token": "eyJ…", "expires_in": 3600 }
Open the launcher in a new tab, passing the token and a percent-encoded next that
targets the template you mirrored in Step 2:
https://app.schole.ai/auto-login?token=eyJ…&next=%2Fstart-lesson%3Ftemplate_id%3D123
login_token is single-use and lives ~1 hour. In an LMS a
learner reopens a course page many times, so a token baked into the rendered link breaks on the
second click. Instead point each course link at a small redirect endpoint on your side
that calls partner-exchange on every click and forwards to /auto-login.
No change is required on Schole's side — this is the intended pattern.
Launching a template_id instantiates the lesson on the fly, so you do not need
to pre-create anything. If you'd rather seed a learner's home with assigned courses ahead of time,
create the curriculum explicitly with POST /api/v1/curricula/:
curl -X POST https://app.schole.ai/api/v1/curricula/ \
-H "Authorization: Bearer $SCHOLE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "user_id": 4567, "template_id": 123 }' # user_id = Schole internal id from Step 3
Track completion
Completion fires per lesson. You have two ways to get it back into your platform — use either or both.
attempted / progressed /
completed statements to your Learning Record Store as they happen.xAPI: give your Schole contact your LRS endpoint and its auth. Schole supports both HTTP Basic Auth and OAuth2 client-credentials (Bearer) — for an LMS, provide the token endpoint, client id and secret, and Schole obtains and refreshes the Bearer token itself. Full setup in the xAPI section.
Analytics pull: GET /api/v1/analytics/users/<id>/summary/
(also /progress/ and /scores/), where <id> is Schole's
internal user id. For a richer event feed, see
Webhooks & Events.
- Credentials issued and a token can be obtained (Step 1).
- Catalogue sync runs and returns templates; nightly delta uses
modified_after(Step 2). - User upsert is idempotent on
provider_id(Step 3). - Course links mint a fresh token per click and land the learner in the lesson (Step 4).
- Completion is flowing back via xAPI and/or the analytics API (Step 5).