Developers

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"
ScopeAllows
users:readRead people and groups
users:writeCreate, update and deactivate people
learning:readRead courses, pathways, enrolments, completions and certificates
learning:writeEnrol people on courses and on your company’s own pathways
webhooks:manageCreate and remove webhook endpoints
xapi:readRead statements and documents from the LRS
xapi:writeSend 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 pathScopeWhat it does
GET /v1anyYour company, this key’s scopes, the event list
GET /v1/usersusers:readPeople. Filters: email, externalId, active, updatedSince
GET /v1/users/{id}users:readOne person, with group ids
POST /v1/usersusers:writeAdd a learner, trainer or manager (email, firstName, lastName, role, externalId, attributes). Seat limits apply
PATCH /v1/users/{id}users:writeChange names, externalId, attributes, or active: false to deactivate
GET /v1/groupsusers:readGroups
GET /v1/groups/{id}/membersusers:readMembers of a group
GET /v1/courseslearning:readPublished courses your company can assign
GET /v1/pathwayslearning:readYour pathways and the Bodhih pathways your people are on
GET /v1/enrollmentslearning:readCourse enrolments. Filters: userId, courseId, status, updatedSince
POST /v1/enrollmentslearning:writeAssign a course: { courseId, userIds[], dueDate? }
GET /v1/pathway-enrollmentslearning:readPathway enrolments with progress and the 90-day re-measure date
POST /v1/pathway-enrollmentslearning:writePut people on one of your company’s own pathways
GET /v1/completionslearning:readCourse and pathway completions since a date: ?since=
GET /v1/certificateslearning:readCertificates with number, status and public verify link
GET /v1/eventsanyWebhook event types
GET, POST, DELETE /v1/webhookswebhooks:manageManage 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.

EventWhen
user.createdA person was added (dashboard, SSO, SCIM, LTI or API)
enrollment.createdA person was put on a course
learning.startedFirst real progress on a course or pathway
course.completedA course was completed
quiz.submittedA quiz or exam was submitted, with score and pass or fail
pathway.enrolledA person was put on a pathway
pathway.stage_completedA stage of a pathway was closed
pathway.completedEvery required stage of a pathway is done
certificate.issuedA 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/:

VerbWhen
http://adlnet.gov/expapi/verbs/registeredEnrolled on a course or pathway
http://adlnet.gov/expapi/verbs/initializedFirst progress on a course or pathway
http://adlnet.gov/expapi/verbs/completedCourse, pathway stage or pathway completed
http://adlnet.gov/expapi/verbs/passed | failedQuiz or exam submitted, with a scaled score
http://id.tincanapi.com/verb/earnedCertificate 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.

SettingValue
OIDC login initiation URLhttps://bodhih-lms-production.up.railway.app/api/lti/login
Target link / redirect URLhttps://bodhih-lms-production.up.railway.app/api/lti/launch
Deep linking URLhttps://bodhih-lms-production.up.railway.app/api/lti/launch
Public keyset (JWKS)https://bodhih-lms-production.up.railway.app/api/lti/jwks
Canvas JSON configurationhttps://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.