Connect Bodhih to the rest of your stack
A REST API for people, enrolments, completions and certificates. Signed webhooks when something happens. A Learning Record Store that speaks xAPI 1.0.3. An LTI 1.3 Advantage tool so Bodhih courses open inside your own LMS. Plus SSO and SCIM, described on the platform page.
Authentication
A company administrator creates API keys in Settings → Integrations → API keys, choosing what each key may do. The secret is shown once; Bodhih keeps only a fingerprint. Every key belongs to one company and sees only that company's data.
# REST API: bearer token = <key id>.<secret>
curl https://bodhih-lms-production.up.railway.app/api/v1 \
-H "Authorization: Bearer bdh_k7Qm2x9aB3cD.3Jx…secret…"
# xAPI clients: HTTP Basic with the key id as user name and the secret as password
curl https://bodhih-lms-production.up.railway.app/api/xapi/statements?limit=10 \
-u "bdh_k7Qm2x9aB3cD:3Jx…secret…" \
-H "X-Experience-API-Version: 1.0.3"| Scope | Allows |
|---|---|
| users:read | Read people and groups |
| users:write | Create, update and deactivate people |
| learning:read | Read courses, pathways, enrolments, completions and certificates |
| learning:write | Enrol people on courses and on your company’s own pathways |
| webhooks:manage | Create and remove webhook endpoints |
| xapi:read | Read statements and documents from the LRS |
| xapi:write | Send statements and documents to the LRS |
REST API v1
Base URL https://bodhih-lms-production.up.railway.app/api/v1. JSON in and out. Lists return { data, nextCursor }; pass ?cursor= to get the next page and ?limit= (1–200, default 50).
| Method and path | Scope | What it does |
|---|---|---|
| GET /v1 | any | Your company, this key’s scopes, the event list |
| GET /v1/users | users:read | People. Filters: email, externalId, active, updatedSince |
| GET /v1/users/{id} | users:read | One person, with group ids |
| POST /v1/users | users:write | Add a learner, trainer or manager (email, firstName, lastName, role, externalId, attributes). Seat limits apply |
| PATCH /v1/users/{id} | users:write | Change names, externalId, attributes, or active: false to deactivate |
| GET /v1/groups | users:read | Groups |
| GET /v1/groups/{id}/members | users:read | Members of a group |
| GET /v1/courses | learning:read | Published courses your company can assign |
| GET /v1/pathways | learning:read | Your pathways and the Bodhih pathways your people are on |
| GET /v1/enrollments | learning:read | Course enrolments. Filters: userId, courseId, status, updatedSince |
| POST /v1/enrollments | learning:write | Assign a course: { courseId, userIds[], dueDate? } |
| GET /v1/pathway-enrollments | learning:read | Pathway enrolments with progress and the 90-day re-measure date |
| POST /v1/pathway-enrollments | learning:write | Put people on one of your company’s own pathways |
| GET /v1/completions | learning:read | Course and pathway completions since a date: ?since= |
| GET /v1/certificates | learning:read | Certificates with number, status and public verify link |
| GET /v1/events | any | Webhook event types |
| GET, POST, DELETE /v1/webhooks | webhooks:manage | Manage webhook endpoints from code |
# Create a learner from your HR system, then assign a course
curl -X POST https://bodhih-lms-production.up.railway.app/api/v1/users \
-H "Authorization: Bearer $BODHIH_KEY" -H "Content-Type: application/json" \
-d '{"email":"ravi@acme.com","firstName":"Ravi","lastName":"K","externalId":"E-1042","attributes":{"department":"Operations"}}'
curl -X POST https://bodhih-lms-production.up.railway.app/api/v1/enrollments \
-H "Authorization: Bearer $BODHIH_KEY" -H "Content-Type: application/json" \
-d '{"courseId":"<course id>","userIds":["<user id>"],"dueDate":"2026-12-31"}'Bodhih catalogue pathways are paid for with course credits and are assigned by a person in Licences; the API does not spend credits. People created through the API sign in with your single sign-on or set a password through “Forgot password”.
Webhooks
Add endpoints in Settings → Integrations → Webhooks (or with the API). Bodhih sends a POST with a JSON body when an event happens. Endpoints must be public https:// addresses; redirects are not followed. A failed delivery (any answer other than 2xx, or no answer in 10 seconds) is retried with back-off for about a day, 8 attempts in all. After 50 failures in a row the endpoint is switched off and shown as such.
| Event | When |
|---|---|
| user.created | A person was added (dashboard, SSO, SCIM, LTI or API) |
| enrollment.created | A person was put on a course |
| learning.started | First real progress on a course or pathway |
| course.completed | A course was completed |
| quiz.submitted | A quiz or exam was submitted, with score and pass or fail |
| pathway.enrolled | A person was put on a pathway |
| pathway.stage_completed | A stage of a pathway was closed |
| pathway.completed | Every required stage of a pathway is done |
| certificate.issued | A certificate or credential was issued (includes the verify link) |
POST /your/endpoint
Content-Type: application/json
Bodhih-Event-Id: 4f1c… # the same on every retry: de-duplicate on it
Bodhih-Event-Type: course.completed
Bodhih-Signature: t=1794043399,v1=5d2a… # HMAC-SHA256 of "<t>.<raw body>" with your signing secret
{
"id": "4f1c…",
"type": "course.completed",
"createdAt": "2026-11-07T10:00:00.000Z",
"tenantId": "…",
"data": {
"person": { "id": "…", "email": "asha@acme.com", "firstName": "Asha", "lastName": "Rao", "externalId": "E-1001" },
"course": { "id": "…", "title": "Safety basics" },
"enrollment": { "id": "…" },
"completedAt": "2026-11-07T10:00:00.000Z"
}
}Verify every delivery before trusting it. Reject a timestamp more than five minutes old.
// Node.js
const crypto = require('crypto');
function verify(secret, rawBody, header) {
const p = Object.fromEntries(header.split(',').map((x) => x.split('=')));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(p.t + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
# Python
import hmac, hashlib, time
def verify(secret, raw_body: bytes, header: str) -> bool:
p = dict(x.split('=', 1) for x in header.split(','))
if abs(time.time() - int(p['t'])) > 300: return False
expected = hmac.new(secret.encode(), f"{p['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, p['v1'])xAPI Learning Record Store
Every company has its own LRS at https://bodhih-lms-production.up.railway.app/api/xapi/ (xAPI 1.0.3). Point any xAPI content, simulator or authoring tool at it with a key that has xapi:write. Supported resources: statements (GET, PUT, POST; voiding; filters agent, verb, activity, registration, since, until, limit, ascending; paging through more), about, activities, agents, activities/state, activities/profile and agents/profile (with ETags, and JSON merge on POST). Batches of up to 500 statements per request.
Bodhih writes statements of its own as learning happens, with the learner as an mbox agent and activity ids under https://www.bodhih.org/xapi/activities/:
| Verb | When |
|---|---|
| http://adlnet.gov/expapi/verbs/registered | Enrolled on a course or pathway |
| http://adlnet.gov/expapi/verbs/initialized | First progress on a course or pathway |
| http://adlnet.gov/expapi/verbs/completed | Course, pathway stage or pathway completed |
| http://adlnet.gov/expapi/verbs/passed | failed | Quiz or exam submitted, with a scaled score |
| http://id.tincanapi.com/verb/earned | Certificate or credential issued |
Already run Watershed, Learning Locker, Veracity or another LRS? Turn on forwarding in Settings → Integrations → xAPI and every statement is copied there as it is stored. Administrators can also download all statements as JSON lines.
LTI 1.3 Advantage
Bodhih is an LTI 1.3 tool with Deep Linking, Assignment and Grade Services and Names and Role Provisioning, built for Canvas, Moodle, Blackboard, D2L and any other LTI 1.3 platform. A teacher adds Bodhih from the LMS's activity picker and chooses a course, a pathway, a micro-simulation or a team tournament. Learners are signed in through the LMS (no second password). Your company's own courses and pathways enrol the learner on first launch; Bodhih catalogue pathways open for people your company has assigned. When a learner completes a course or pathway, a score of 100 is sent to the LMS gradebook; simulations send their score.
| Setting | Value |
|---|---|
| OIDC login initiation URL | https://bodhih-lms-production.up.railway.app/api/lti/login |
| Target link / redirect URL | https://bodhih-lms-production.up.railway.app/api/lti/launch |
| Deep linking URL | https://bodhih-lms-production.up.railway.app/api/lti/launch |
| Public keyset (JWKS) | https://bodhih-lms-production.up.railway.app/api/lti/jwks |
| Canvas JSON configuration | https://bodhih-lms-production.up.railway.app/api/lti/config |
| Custom parameter (optional) | target=course:<id> | pathway:<id> | micro:<key> | cohort:<id> |
LTI is part of the Enterprise plan. Send your platform's issuer, client id, deployment id and keyset URL to solutions@bodhih.com and we register it for your company.
Limits and errors
300 requests a minute per key (headers X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries Retry-After). Errors are JSON with a plain-English message: 400 bad input, 401 missing or invalid key, 403 missing scope or inactive company, 404 not found in your company, 409 conflict, 429 too many requests. Uptime and support commitments are in the SLA; live status is at /status; where data is held is on Security and data residency.