Locate once, read it many ways.
A ClassGrade knowledge space is a learned coordinate system for a subject. You locate a learner in it once — for your tenant, resolved from your key — then read that one position through whichever framework views you ask for. Three things stay separate: which subject (the knowledge space), how you read the position (the framework views), and whose private layer you see (the tenant). The position is computed once; the views are just different readings of it.
You locate a learner once, in a knowledge space, for your tenant — then read that one position through one or more framework views. Three axes, never conflated:
Knowledge space
Which subject?
Framework view
Read the position how?
Tenant
Whose private layer?
We don’t score children. We locate them.
What a position means, how the enrolled grade sets the prior, and why we read the same point two ways — band against grade, and depth within the band.
Base URLhttps://api.classgrade.ai/v1
/v1/locateBundledPlace a learner in a knowledge space from evidence, and read that one position through the framework views you ask for.
space picks the coordinate system — english is live; maths and science are accepted values, coming (requesting one now returns a 400). grade is required (1–10): the enrolled grade is the prior the measured position is read against. evidence is coordinates on the space’s axes; producing those from raw work is a separate upstream step, held out here. views default to the tenant’s default and are scoped per tenant. The position is decay-adjusted to call time, so as_of is stamped; an unsupplied axis is honestly null, never 0.
{
"space": "english",
"grade": 6,
"evidence": [
{
"axis": "accuracy",
"value": -0.15
},
{
"axis": "fluency",
"value": -0.2
},
{
"axis": "vocabulary",
"value": -0.1
}
],
"views": [
"cefr",
"school"
]
}{
"space": "english",
"tenant": "blueberry",
"position": {
"axes": {
"accuracy": {
"value": -0.15,
"uncertainty": 0.49
},
"pronunciation": {
"value": null,
"uncertainty": null
},
"fluency": {
"value": -0.2,
"uncertainty": 0.49
},
"vocabulary": {
"value": -0.1,
"uncertainty": 0.49
},
"content": {
"value": null,
"uncertainty": null
}
},
"as_of": "2026-06-03T08:13:13Z"
},
"views": {
"cefr": {
"band": "B1",
"detail": "around grade-expected (B1)"
},
"school": {
"enrolled_grade": 6,
"expected_level": "B1",
"measured_level": "B1",
"gap": 0,
"detail": "on track",
"within_band": -0.034,
"within_band_state": "entry"
}
}
}The same position, read two ways. Unsupplied axes are honestly null (here pronunciation and content), never 0. The school view is grade-relative: gap reads the measured band against the grade’s expectation (here on track), and within_band_state reads depth inside that band on a four-state scale — while the surface is early in calibration, uncertainty is wide and most reads sit near entry. Where the two views diverge, the gap is the signal.
Same model, same call, every time. Three Grade 6 learners — only the evidence differs. Press each and the live API answers differently: where they are, and the neighbourhood it opens.
POST /v1/locate · english · grade 6evidence · acc -0.15, flu -0.20, voc -0.10 ×6The same low reads, but enough of them now. Still B1 — the right band for Grade 6, so a gradebook says on track and moves on. With the evidence to earn it, the depth resolves: struggling, at the floor of the band. Right band, wrong depth.
/v1/neighbourhoodBundledFor a located position, return the skills immediately behind and ahead — five back (what it rests on) and five forward (what comes next). The engine behind the home-page demo.
Centre on a position from /v1/locate, or a skill id. builds_on and comes_next are walked along prerequisite edges, not nearest-neighbour. Each neighbour carries its own per-view reading, so the same skill can read differently across views — that divergence is signal.
{
"space": "english",
"position": "bm.cefr.c_l_vc.B1.01",
"views": [
"cefr",
"school"
]
}{
"space": "english",
"tenant": "blueberry",
"centre": {
"id": "bm.cefr.c_l_vc.B1.01",
"label": "Use simple vocabulary on familiar topics"
},
"builds_on": [
{
"id": "bm.cefr.c_l_vc.A2.01",
"label": "Can use basic vocabulary for daily needs",
"views": {
"cefr": "A2",
"school": "Class 5"
}
},
{
"id": "bm.cefr.c_l_vc.B1.02",
"label": "Control elementary vocabulary",
"views": {
"cefr": "B1",
"school": "Class 6"
}
}
],
"comes_next": [
{
"id": "bm.cefr.c_l_ga.B1.01",
"label": "Use common routines with reasonable accuracy",
"views": {
"cefr": "B1",
"school": "Class 6"
}
},
{
"id": "bm.cefr.c_l_vc.B2.01",
"label": "Use appropriate vocabulary in familiar contexts",
"views": {
"cefr": "B2",
"school": "Class 9"
}
}
]
}Abbreviated — each side returns up to five, walked along prerequisite edges. The live call returns the full set.
/v1/fieldPremium · meteredComing soonField queries over the space — divergence, Jacobian, entropy, influence. The premium, metered tier: called deliberately, not on every turn.
Genuinely not built yet, documented so the surface is complete. Shape is indicative; these reads land once enough trajectory data is flowing to compute them reliably.
{
"space": "english",
"query": "influence",
"position": "bm.cefr.c_l_vc.B1.01"
}{
"space": "english",
"query": "influence",
"entropy": 1.84,
"influence": [
{
"id": "bm.cefr.c_l_ga.B1.01",
"weight": 0.41
},
{
"id": "bm.cefr.c_l_vc.A2.01",
"weight": 0.33
},
{
"id": "bm.cefr.c_l_vc.B2.01",
"weight": 0.27
}
]
}Auth
Every request carries an API key: Authorization: Bearer <api_key>. A key belongs to one tenant — the key is how the platform knows which proprietary layer to read. The tenant is never in the body.
Tenant isolation
A key sees two things: the shared seed — public and identical for everyone — and its own proprietary layer, read on top of the seed and private to that tenant. It never sees another tenant’s layer: not as a filtered field, not as a sibling key in the response. Isolation is structural — the response is assembled from your slice alone, and an unknown tenant gets a 404, never a fallback.
One locate, many views
Asking for several framework views is one locate read several ways — billed as one position lookup, not one per view. locate and neighbourhood are bundled; field is premium and metered. Adding a knowledge space (maths, science) is how the product line grows — same machinery, new axes.
This is an L2 English knowledge space. Need a different one — L1 English, another subject, another language? That’s what we build. Same machinery, new axes.
Talk to us