Edu-V API Referentie
Integratiedocumentatie voor interoperabiliteit in het Nederlandse onderwijs
https://api.samiolearning.com/v1/eduvOverzicht
Samio neemt deel aan Edu-V (release NR26) als voortgangsdashboard voor leerkrachten. Samio is afnemer van administratiegegevens — onderwijsinrichting, onderwijsdeelnemers, onderwijsmedewerkers en groepen — uit Nederlandse leerlingadministratiesystemen zoals ParnasSys en ESIS, en verzender van de leerresultaten die in de gratis Samio-leerlingapp ontstaan. Samio is geen portaal: er komen geen aanspraken, catalogusgegevens of resultaten binnen.
Samio's rollen
| Referentiecomponent | Gegevensdienst | Richting | Release |
|---|---|---|---|
| Dienst met SSO | Identiteitsverklaring | Ontvanger | NR26 |
| Dashboard voortgang | Onderwijsinrichting | Afnemer | NR26 |
| Dashboard voortgang | Onderwijsdeelnemer | Afnemer | NR26 |
| Dashboard voortgang | Onderwijsmedewerkers | Afnemer | NR26 |
| Dashboard voortgang | Onderwijsdeelname (groepen) | Afnemer | NR26 |
| Dashboard voortgang | Leerresultaten | Verzender | NR26 |
Inbound — koppelvlakken Samio exposes
/v1/eduv/
├── .well-known/ # OAuth metadata + participation statement
├── oauth/ # Token endpoint + JWKS
├── consent/ # Consent lifecycle (supporting)
├── dpa/ # DPA verification (supporting)
└── notifications/ # Roster-change notifications (we subscribe)
Outbound — koppelvlakken Samio calls at the partner (v1 base)
├── GET /organisations, /studyofferings/school # Education 1.1.1
├── GET /schoolperiods/school, /groups/school,
│ /enrollments/school, /assignments/school # Association 1.1.0
├── GET /students/school POST /students # Students 1.1.0
├── GET /employees/school POST /employees # Employees 1.1.0
└── POST {v2 base}/results # Results 2.1.0 (verzender)Authenticatie
Alle endpoints vereisen een Bearer-token verkregen via OAuth 2.0 met het NL GOV-profiel (private_key_jwt client-authenticatie).
Token-endpoint
/v1/eduv/oauth/token| Parameter | Type | Beschrijving | |
|---|---|---|---|
grant_type | string | Vereist | Must be "client_credentials" |
client_assertion_type | string | Vereist | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
client_assertion | string | Vereist | Signed JWT (RS256, PKIoverheid key) |
scope | string | Optioneel | Space-separated scopes |
curl -X POST https://api.samiolearning.com/v1/eduv/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=eyJhbGciOiJSUzI1NiIs..." \
-d "scope=eduv.student.basic eduv.association"{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "eduv.student.basic eduv.association"
}JWKS-endpoint
/v1/eduv/oauth/.well-known/jwks.jsonOne RSA key (kid samio-eduv-1, RS256). The x5c array carries Samio's PKIoverheid certificate followed by its chain, so your authorisation server can check validity, chain, CRL and the OIN in Subject.serialNumber — 00000003730066370000 (Geekcow Creative Studio, KvK 73006637). Discovery: https://api.samiolearning.com/.well-known/oauth-authorization-server.
{
"keys": [{
"kty": "RSA", "kid": "samio-eduv-1", "use": "sig", "alg": "RS256",
"n": "…", "e": "AQAB",
"x5c": ["<leaf certificate>", "<issuing CA>", "<intermediate>", "<root>"],
"x5t": "…", "x5t#S256": "…"
}]
}Samio as client — the assertion you receive
When Samio calls your SIS or Results API it authenticates at your token endpoint with the same NL GOV profile. iss and sub are the client_id your authorisation server issued to Samio, aud is your token endpoint, jti has 256 bits of entropy and the assertion lives 5 minutes. The header carries kid and x5c; the public key is the one published in Samio's JWKS above.
// Header
{ "alg": "RS256", "typ": "JWT", "kid": "samio-eduv-1", "x5c": ["<leaf>", "<issuing CA>", "<intermediate>", "<root>"] }
// Payload
{
"iss": "<client_id you issued to Samio>",
"sub": "<client_id you issued to Samio>",
"aud": "https://auth.example.nl/realms/x/protocol/openid-connect/token",
"jti": "…256-bit random…",
"iat": 1761040000,
"exp": 1761040300
}
// Form body
grant_type=client_credentials
client_id=<client_id you issued to Samio>
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion=<JWT>
scope=eduv.education eduv.student.basic eduv.employee.basic eduv.associationToken Properties
Foutcodes
{
"error": "Human-readable error message",
"error_code": "1001",
"error_description": "Optional additional details"
}| Code | HTTP-status | Bericht |
|---|---|---|
0021 | 401 | Not authorized for this scope |
0011 | 400 | Schema validation failed |
0012 | 400 | schemaVersion not supported |
0022 | 403 | Consent required for this data service |
0023 | 400 | Unknown education organisation |
0099 | 400 | Other rejection (reason in statusMessage) |
0099 | 500 | Internal server error |
Paginering
Gepagineerde endpoints accepteren start- en limietparameters.
| Parameter | Type | Standaard | Max |
|---|---|---|---|
start | integer | 0 | — |
limit | integer | 20 | 100 |
{
"data": [ ... ],
"total": 142,
"start": 0,
"limit": 20,
"hasMore": true
}Consent
/v1/eduv/consentList all consent records. Filter by schoolId or status.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolId | string | Optioneel | Filter by school |
status | string | Optioneel | Filter: requested, granted, revoked, denied |
Respons
{
"data": [{
"_id": "665a1b2c3d4e5f6789012345",
"schoolId": { "_id": "64a...", "name": "Basisschool De Regenboog" },
"partnerId": { "_id": "665b...", "name": "ParnasSys" },
"dataService": "eduv.student.basic",
"status": "granted",
"grantedAt": "2026-03-15T10:30:00.000Z"
}]
}/v1/eduv/consent/:idGet a single consent record by ID.
/v1/eduv/consent/requestRequest consent from a school for a data service.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolId | string | Vereist | Target school's ObjectId |
dataService | string | Vereist | Edu-V scope (e.g. eduv.student.basic) |
dpaReference | string | Optioneel | Reference to DPA agreement |
Respons
{
"message": "Consent requested",
"consent": {
"_id": "665a1b2c...",
"status": "requested",
"initiatedBy": "partner"
}
}/v1/eduv/consent/:id/confirmConfirm (grant) a consent request.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
expiresAt | ISO date | Optioneel | When the consent expires |
/v1/eduv/consent/:id/revokeRevoke a previously granted consent.
/v1/eduv/consent/check/:schoolId/:dataServiceQuick boolean check if consent is granted.
Respons
{ "granted": true }VWO
/v1/eduv/dpa/agreementsGet DPA agreement status for a school.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolBrinCode | string | Vereist | School's BRIN code |
/v1/eduv/dpa/verifyVerify a Data Processing Agreement (verwerkersovereenkomst).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolBrinCode | string | Vereist | School's BRIN code |
dpaReference | string | Optioneel | Agreement reference number |
agreementDate | ISO date | Optioneel | Date of agreement |
Respons
{
"verified": true,
"partner": { "oin": "0000000123...", "name": "ParnasSys" },
"schoolBrinCode": "01AB",
"verificationType": "local"
}Onderwijs
/organisationsEducation API 1.1.1, relative to your v1 base (e.g. https://sis.example/eduv/v1/{tenant}). Samio reads the school's organisation record (name, boards, locations) to confirm the RIO identity of a school that enabled Edu-V.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
boardMasterId | string | Optioneel | Bestuur master id |
boardId | string | Optioneel | Bestuur id, with boardIdType=BGE_CODE |
name | string | Optioneel | School name |
Respons
[{
"organisationMasterIdentifier": "777A000",
"organisationIds": [
{ "organisationId": "98DU00", "organisationIdType": "V_ID" },
{ "organisationId": "98DU", "organisationIdType": "OIE_CODE" }
],
"name": "St Grogory",
"boards": [{ "organisationMasterIdentifier": "042B317", "organisationIds": [], "name": "Wizarding School Authority" }],
"locations": [],
"status": "active",
"dateCreated": "2026-09-22T01:44:37Z",
"dateLastModified": "2026-09-23T01:44:37Z"
}]/studyofferings/schoolStudy offerings (opleidingen, e.g. primary education) of the school in a school year. Samio uses the studyOfferingId to scope pupil and enrollment reads.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
studyCode | string | Optioneel | Filter on study code |
/studyofferings/school/:idOne study offering by id.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
Leerlingen
/students/schoolStudents API 1.1.0. All pupils of the school in the school year. Samio matches every pupil to a Samio student by ECK iD (userMasterIdentifier), then by a secondary id (ASI, BPI), then by name; it stores the Edu-V names and ids on the student, and a pupil with status tobedeleted is deactivated. Samio never creates pupils from the SIS. Daily, per school with granted consent.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
studyOfferingId | string | Optioneel | Limit to one study offering |
Respons
[{
"userMasterIdentifier": "https://ketenid.nl/pilot/2b3c…4102",
"userIds": [
{ "userId": "87654321", "userIdType": "ASI" },
{ "userId": "124999", "userIdType": "BPI" }
],
"givenName": "Piers",
"familyName": "Polkiss",
"status": "active",
"dateCreated": "2026-09-22T01:44:37Z",
"dateLastModified": "2026-09-23T01:44:37Z"
}]/studentsSearch one pupil by ECK iD or secondary id (used for the ECK iD mapping page and spot checks).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
school | SchoolReference | Vereist | { organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] } |
student | UserReference | Vereist | { userMasterIdentifier } or { userIds[{userId, userIdType}] } |
Medewerkers
/employees/schoolEmployees API 1.1.0. Staff of the school in the school year. Samio matches staff to its school users by userMasterIdentifier, secondary id (NEPPI, ASI) or e-mail and records the Edu-V names, roles and status. A teacher's Samio access is never changed by the SIS.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
Respons
[{
"userIds": [
{ "userId": "3e784620-c251-4c69-ba1a-3b30116ac288", "userIdType": "NEPPI" },
{ "userId": "11111231", "userIdType": "ASI" }
],
"givenName": "Arabella",
"familyName": "Figg",
"organisationRoles": [{ "organisation": { "organisationMasterIdentifier": "777A000", "organisationIds": [] }, "organisationRole": "teacher", "beginDate": "2026-08-01" }],
"status": "active",
"dateCreated": "2026-09-22T01:44:37Z",
"dateLastModified": "2026-09-23T01:44:37Z"
}]/employeesSearch one staff member by secondary id.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
school | SchoolReference | Vereist | { organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] } |
employee | UserReference | Vereist | { userIds[{userId, userIdType}] } (NEPPI, ASI) |
Associatie
/schoolperiods/schoolAssociation API 1.1.0. School periods of the school; Samio picks the schoolYear period that contains today and uses its schoolPeriodId for every other read.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
Respons
[{
"schoolPeriodId": "2026-2027",
"title": "Schooljaar 2026-2027",
"type": "schoolYear",
"startDate": "2026-08-01",
"endDate": "2027-07-31",
"status": "active",
"dateCreated": "2026-06-01T00:00:00Z",
"dateLastModified": "2026-06-01T00:00:00Z"
}]/schoolperiods/school/:idOne school period by id.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
/groups/schoolClasses (groupType class) with their members. Samio links each SIS class to a Samio class by the stored groupId or by name and records the SIS name, status and member count; pupils are not moved between classes automatically.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
groupType | string | Optioneel | class | lesson-group (Samio asks for class) |
studyOfferingId | string | Optioneel | Limit to one study offering |
Respons
[{
"groupId": "St Grogory year eight",
"groupName": "Year Eight",
"groupType": "class",
"students": [{ "userMasterIdentifier": "https://ketenid.nl/pilot/2b3c…4102" }],
"assignments": [],
"schoolPeriod": "2026-2027",
"beginDate": "2026-08-01",
"status": "active",
"dateCreated": "2026-09-22T01:44:37Z",
"dateLastModified": "2026-09-23T01:44:37Z"
}]/groups/school/:groupIdOne group by id (groupId is a free string, URL-encoded).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
/enrollments/schoolEnrollments of pupils in a study (enrollmentType study) or subject.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
studyOfferingId | string | Optioneel | Limit to one study offering |
enrollmentType | string | Optioneel | study | subject |
/enrollments/school/:idOne enrollment by id.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
/enrollments/school/studentEnrollments of one pupil at the school.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
studyOfferingId | string | Optioneel | Limit to one study offering |
enrollmentType | string | Optioneel | study | subject |
| Parameter | Type | Beschrijving | |
|---|---|---|---|
school | SchoolReference | Vereist | { organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] } |
student | UserReference | Vereist | { userMasterIdentifier } or { userIds[] } |
/assignments/schoolStaff assignments (class-teacher, teacher, coach) to groups and pupils.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
assignmentType | string | Optioneel | class-teacher | teacher | coach |
/assignments/school/:idOne assignment by id.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
orgMasterId | string | Optioneel | RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one |
orgId | string | Optioneel | Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId |
orgIdType | string | Optioneel | OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID |
/assignments/school/employeeAssignments of one staff member at the school.
| Parameter | Type | Beschrijving | |
|---|---|---|---|
schoolPeriodId | string | Optioneel | School year, e.g. 2026-2027 (Samio always sends the current one) |
assignmentType | string | Optioneel | class-teacher | teacher | coach |
| Parameter | Type | Beschrijving | |
|---|---|---|---|
school | SchoolReference | Vereist | { organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] } |
employee | UserReference | Vereist | { userIds[] } |
Resultaten
/resultsResults API 2.1.0, on your v2 base (e.g. https://sis.example/eduv/v2/{tenant}). Samio sends the learning results produced in its pupil app as one AssessmentScoresAndResults message per school and batch: one assessment definition ("Samio Learning oefenresultaten") with a part per subject (rekenen-wiskunde, lezen, taalverzorging, …) and, per pupil, the scores PercentageCorrect and NumberItems plus a PassOrFail result per part, aggregated over the batch window. Hourly on the worker, retried after 1 min, 5 min and 1 h. Expected responses: 201 (processed) or 202 (accepted for processing).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
id | uuid | Vereist | Message id, stable across retries |
timestamp | ISO datetime | Vereist | When the message was composed |
assessmentDateTime | ISO datetime | Vereist | End of the batch window |
assessmentDefinition | Assessment | Vereist | { id: "samio-app-exercises", name, version, subjects[{subjectName}], parts[{id, name, index}] } |
school | SchoolReference | Vereist | { organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] } |
schoolPeriod | string | Vereist | eejj-eejj, e.g. 2026-2027 |
toolName | string | Optioneel | Samio Learning |
studentScoresAndResults | StudentScoresAndResults[] | Optioneel | Per pupil: { id, student: StudentReference, status: Final, scores[{scoreValue, scoreType, scoreMaximum, assessmentId, assessmentPartId}], results[{resultValue, resultType, assessmentId, assessmentPartId}], dateCreated, dateLastModified } |
Respons
HTTP/1.1 202 Accepted
API-Version: 2.1.0Notificaties
/v1/eduv/notifications/subscribeSubscribe to change notifications (webhooks).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
callbackUrl | string | Vereist | URL to receive webhooks |
notificationTypes | string[] | Vereist | Event types to subscribe to |
schoolId | string | Optioneel | Scope to specific school |
/v1/eduv/notifications/subscribe/:idUnsubscribe from notifications.
/v1/eduv/notifications/subscriptionsList active subscriptions.
/v1/eduv/notifications/receiveReceive inbound notification from a partner (webhook).
| Parameter | Type | Beschrijving | |
|---|---|---|---|
notificationType | string | Vereist | Event type |
objectType | string | Vereist | student, employee, group, etc. |
objectId | string | Optioneel | Object identifier |
school | object | Optioneel | { brinCode } |
url | string | Optioneel | URL to fetch the updated object |
isDeleteNotification | boolean | Optioneel | Whether the object was deleted |
Scopes
Beschikbare OAuth 2.0 scopes voor de Edu-V integratie.
| Scope | API's | Beschrijving |
|---|---|---|
eduv.consent | Consent | Manage consent lifecycle (supporting) |
eduv.dpa | DPA | Verify Data Processing Agreements (supporting) |
eduv.notifications | Notifications | Subscribe to roster-change notifications |
eduv.education | Education | Onderwijsinrichting — organisations, studies, subjects (afnemer) |
eduv.student.basic | Students | Onderwijsdeelnemer — identity and name (afnemer) |
eduv.employee.basic | Employees | Onderwijsmedewerkers — identity and name (afnemer) |
eduv.association | Association | Onderwijsdeelname — groups and enrollments (afnemer) |
eduv.result | Results | Leerresultaten — results sent by Samio (verzender) |
Datamodellen
| Veld | Type | Beschrijving |
|---|---|---|
schoolId | ObjectId → School | Target school |
partnerId | ObjectId → EduVPartner | Partner |
dataService | string | Edu-V scope |
status | enum | requested | granted | revoked | denied |
initiatedBy | enum | school | partner |
grantedAt | Date | When consent was granted |
expiresAt | Date | Consent expiry |
| Veld | Type | Beschrijving |
|---|---|---|
name | string | Partner name |
oin | string | Your OIN (20 digits) |
jwksUri | string | Your JWKS |
tokenEndpoint | string | Your token endpoint (Samio authenticates here with private_key_jwt) |
outboundClientId | string | client_id you issued to Samio (iss/sub of Samio's assertion) |
baseUrl | string | Your v1 base for Students / Employees / Education / Association |
resultsBaseUrl | string | Your v2 base for Results (derived from baseUrl when empty) |
clientId | string | Your client_id at Samio's token endpoint (when you call Samio) |
allowedScopes | string[] | Scopes you may request at Samio |
status | enum | pending | active | inactive |
| Veld | Type | Beschrijving |
|---|---|---|
eduv.oaId | string | RIO onderwijsaanbieder id — sent as orgMasterId |
eduv.brinCode | string | BRIN — sent as orgId with orgIdType V_ID (6) or OIE_CODE (4) when there is no oaId |
eduv.oinNumber | string | OIN of the school board |
eduv.boardId | string | Bestuur ID |
eduv.bestuursnummer | string | Board number |
eduv.enabled | boolean | Whether Edu-V is active |
| Veld | Type | Beschrijving |
|---|---|---|
eduv.eckId | string | ECK iD (userMasterIdentifier) — primary pupil identifier |
eduv.externalIds | array | [{ type: ASI | BPI | …, value }] — the SIS userIds |
eduv.givenName | string | First name as in the SIS |
eduv.preferredFirstName | string | Roepnaam |
eduv.familyName | string | Last name |
eduv.familyNamePrefix | string | Tussenvoegsel ("van", "de") |
eduv.alias | string | Alias |
eduv.sisStatus | enum | active | tobedeleted — last status from the SIS; tobedeleted also deactivates the pupil in Samio |
eduv.lastSyncedAt | Date | Last roster sync that touched this pupil |
| Veld | Type | Beschrijving |
|---|---|---|
eduv.userMasterIdentifier | string | Primary identifier from the SIS, when present |
eduv.externalIds | array | [{ type: NEPPI | ASI | …, value }] |
eduv.preferredFirstName | string | Roepnaam |
eduv.familyNamePrefix | string | Tussenvoegsel |
eduv.organisationRoles | array | [{ organisationId, role, beginDate, endDate }] |
eduv.sisStatus | enum | active | tobedeleted — recorded only; never changes the user's Samio access |
eduv.lastSyncedAt | Date | Last roster sync |
| Veld | Type | Beschrijving |
|---|---|---|
eduv.groupId | string | SIS groupId this class mirrors |
eduv.groupName | string | SIS group name (the teacher's own class name stays untouched) |
eduv.groupType | enum | class | lesson-group |
eduv.schoolPeriodId | string | School year of the group |
eduv.sisStatus | enum | active | tobedeleted |
eduv.memberCount | number | Members reported by the SIS |
eduv.lastSyncedAt | Date | Last roster sync |
Snel starten
Volledig integratieproces van partnerregistratie tot het uitlezen van resultaten.
Registration, both ways — Samio registers its jwks_uri (https://api.samiolearning.com/v1/eduv/oauth/.well-known/jwks.json) and OIN 00000003730066370000 at your authorisation server and receives a client_id; your authorisation server, token endpoint and v1/v2 base URLs are stored on the partner record at Samio
The school enables Edu-V in Samio for Schools (RIO onderwijsaanbieder id or BRIN) and grants consent per data service (Consent API or the Edu-V consent UI)
Samio obtains a token at your token endpoint → client_credentials + private_key_jwt (RS256, header kid + x5c with the PKIoverheid chain, aud = your token endpoint, scopes eduv.education eduv.student.basic eduv.employee.basic eduv.association)
Samio reads the school year → GET /schoolperiods/school?orgMasterId=…
Samio reads pupils, staff and classes → GET /students/school, /employees/school, /groups/school (schoolPeriodId=…)
Pupils are matched by ECK iD (then secondary id, then name); tobedeleted pupils are deactivated; nothing is created from the SIS
Pupils use Samio → (normal app usage)
Samio sends results → POST {v2 base}/results (scope eduv.result), hourly with retries
Optional: you notify Samio of roster changes → POST /v1/eduv/notifications/receive, and Samio re-reads the changed objects
Agent-tools (WebMCP)
Tools die AI-agents in de browser op samiolearning.com kunnen aanroepen: vier alleen-lezen tools op elke openbare pagina (prijzen per land, pagina's zoeken, productfeiten, app-links) en, voor een ingelogde leerkracht op het schooldashboard, dezelfde twaalf leerkrachttools als de MCP-server — uitgevoerd met de sessie van de leerkracht.
WebMCP is the W3C-incubated standard (Google + Microsoft) that lets a web page register tools an AI agent running in the browser can call — the page-side counterpart of an MCP server. Every public page on samiolearning.com registers the four tools below once the page has loaded, in browsers that exposenavigator.modelContext. Other browsers pay nothing: the module is loaded at idle and only when the API exists. The tools are deliberately read-only. Nothing submits a form, starts a trial or touches payment; an agent that wants to register a school must hand the person the registration page.
samio_get_pricing_for_countryGET /v1/schools/pricing/resolve (public) · static tier table as fallbackSamio for Schools price for a country: equity-pricing tier (World Bank income group), discount, and the monthly USD price of the Homeschool, Classroom and School plans.
| Input | Type | Required | Description |
|---|---|---|---|
country | string | yes | ISO 3166-1 alpha-2 code, e.g. NL, MX, IN |
Returns
{
"country": "MX",
"pricingTier": "tier_b",
"pricingTierLabel": "Equity — Upper-middle income",
"discountPercent": 50,
"currency": "USD",
"billing": "monthly via PayPal; every plan starts with a 14-day free trial",
"plans": [
{ "id": "homeschool", "priceUsdPerMonth": 2.49, "standardPriceUsdPerMonth": 4.99, "limits": { "students": 10, "teachers": 1, "classes": 1 } },
{ "id": "classroom", "priceUsdPerMonth": 9.0, "standardPriceUsdPerMonth": 19.0, "limits": { "students": 60, "teachers": 1, "classes": 4 } },
{ "id": "school", "priceUsdPerMonth": 79.0, "standardPriceUsdPerMonth": 159.0, "limits": { "students": 1500, "teachers": "unlimited", "classes": "unlimited" } }
],
"resolvedBy": "api",
"explanation": "https://samiolearning.com/schools/equity-pricing",
"register": "https://samiolearning.com/schools/register"
}samio_find_pagein-page index of public routesFind the samiolearning.com page that answers a question. Returns up to 5 URLs with titles and descriptions, localized to the requested language where a translation exists.
| Input | Type | Required | Description |
|---|---|---|---|
query | string | yes | Free-text question or keywords |
language | string | no | en (default), es, nl, de, fr, it, pt |
audience | string | no | parents | schools | developers |
Returns
{
"query": "how do i connect google classroom",
"results": [
{ "title": "Plug Samio into your LMS in 10 minutes", "url": "https://samiolearning.com/schools/integrations", "audience": "schools", "relevance": 7, "description": "…" }
]
}samio_get_product_factsGET /facts.jsonCanonical facts about Samio Learning, Samio for Schools or Askimo (features, platforms, compliance, pricing, comparisons) plus entity disambiguation for the names "Samio" and "Askimo".
| Input | Type | Required | Description |
|---|---|---|---|
product | string | no | samio | schools | askimo — omit for all three |
Returns
{ "products": [ { "name": "Askimo", "category": "Free educational Wikipedia app for kids ages 4–12", "…": "…" } ], "entityDisambiguation": { "samio": "…", "askimo": "…" }, "source": "https://samiolearning.com/facts.json" }samio_get_app_linksstaticOfficial App Store, Google Play and web links for the Askimo or Samio Learning apps, with price and age range.
| Input | Type | Required | Description |
|---|---|---|---|
app | string | no | askimo (default) | samio |
Returns
{ "app": "Askimo", "appStore": "https://apps.apple.com/app/askimo/id6760259046", "googlePlay": "…", "web": "https://samiolearning.com/askimo", "price": "Free, no ads, no accounts", "ages": "4–12", "languages": ["en","es","nl","de","fr","it","pt"] }Dashboard tools for signed-in teachers
On /schools/dashboard/*, and only while a teacher is signed in, the page additionally registers the twelve teacher tools of the remote MCP server — same names, same input schemas — so a browser agent (Chrome, Gemini in Chrome, Edge Copilot) can do the same work without OAuth: every call runs through the dashboard's own endpoints with the teacher's session, under the same plan limits and consent checks as a click in the UI. The set is withdrawn on logout or when leaving the dashboard; public pages never see it. Generation tools return the new id with status: "generating" and the matching get_ tool polls, exactly as on the MCP server. The schemas are published in the manifest under dashboardTools.
list_my_classeslist_material_typesgenerate_teaching_materialget_teaching_materiallist_teaching_materialscreate_topic_planget_topic_planget_topic_plan_weeklist_topic_plansgenerate_challengesearch_knowledge_factsget_class_curriculum_progressDiscovery and testing
- Static manifest of the same tools: /agent-tools.json. Plain-text brief for LLMs: /llms.txt; machine-readable facts: /facts.json.
- In a WebMCP-capable browser (Chrome early preview; enable the WebMCP flag in
chrome://flags), open any samiolearning.com page and the tools appear in the browser's agent tool list after load. - Non-browser agents (an MCP server, a script) do not need WebMCP: the two network-backed tools wrap plain HTTP endpoints you can call directly, shown below.
# Country → pricing tier (public, no auth)
GET https://api.samiolearning.com/v1/schools/pricing/resolve?country=MX
→ { "success": true, "data": { "country": "MX", "pricingTier": "tier_b", "pricingTierLabel": "Equity — Upper-middle income", "pricingDiscountPercent": 50, "currency": "USD" } }
# Canonical product facts
GET https://samiolearning.com/facts.json
# Tool manifest
GET https://samiolearning.com/agent-tools.jsonStatus: the WebMCP API surface is still changing. The page registers with whichever ofregisterTool() orprovideContext() the browser exposes and silently skips registration otherwise. Tool names and schemas are stable; report problems to info@samiolearning.com.
Remote MCP-server (Samio voor Scholen)
Een remote MCP-server waarmee elke AI-assistent verbinding kan maken — Claude, ChatGPT, Gemini, Claude Code of je eigen agent. Vier alleen-lezen tools voor curriculumdoelen van het basisonderwijs in 50 landen (56 curricula) en de prijzen van Samio voor Scholen werken zonder login; dertien tools voor leerkrachten (materiaal, jaarplannen, uitdagingen, kennisfeiten, dekking op klasniveau) komen beschikbaar zodra een leerkracht zijn of haar Samio voor Scholen-account koppelt met OAuth 2.1. Geen leerlinggegevens, bij geen enkele tool.
https://mcp.samiolearning.com/mcphttps://mcp.samiolearning.com/healthThe server speaks the Model Context Protocol over Streamable HTTP and is stateless: every request carries what it needs, so it works with any current MCP client. The server lists seventeen tools. The four below need no account: they read the same curriculum spine that grounds every exercise in Samio (50 countries, 56 curricula, seven interface languages) and the public pricing resolver. The other fourteen are teacher tools: they become usable once a teacher connects their Samio for Schools account with OAuth 2.1 and act only inside that teacher's own classes. No tool on either tier returns a student name, score or result, and the gateway behind the server stores only hashed inputs and salted IP hashes for abuse control.
list_countries_grades_subjectsList curriculum coverageGET /v1/agent/curriculum/countriesWhich countries, grades (Samio stages 1–7 with age ranges) and subjects Samio holds official national-curriculum data for. Call this first when unsure of codes.
| Input | Type | Required | Description |
|---|---|---|---|
country | string | no | Optional ISO country code (e.g. NL, ES, US) to return one country only |
Returns
{
"countries": [
{ "country": "NL", "language": "nl",
"grades": [ { "gradeLevel": 1, "gradeName": "Groep 3", "ageRange": { "min": 6, "max": 7 } }, … ],
"subjects": ["math", "reading", "grammar", "science", "physics", "computing"] },
…
]
}search_curriculum_standardsSearch curriculum standardsGET /v1/agent/curriculum/standardsOfficial national-curriculum units, skills, competencies and assessment criteria for a country + subject + grade (or age), optionally ranked by a topic query. Returns standardId/unitId for follow-up calls.
| Input | Type | Required | Description |
|---|---|---|---|
country | string | yes | ISO country code, e.g. NL, ES, DE, FR, IT, PT, GB, US |
subject | enum | yes | math | reading | grammar | clock | science | physics | history | computing |
grade | integer 1–7 | no | Samio stage (1 ≈ age 4–5 … 7 ≈ age 11–13). Give grade OR age |
age | integer 3–18 | no | Child's age; the matching grade is chosen for you |
language | string | no | Language of the curriculum text (defaults to the country's) |
query | string ≤120 | no | Free-text topic to rank units by, e.g. 'fractions', 'breuken' |
limit | integer 1–50 | no | Max units to return (default 20) |
Returns
{
"standard": { "standardId": "…", "country": "NL", "language": "nl", "gradeLevel": 1, "gradeName": "Groep 3",
"ageRange": { "min": 6, "max": 7 }, "subject": "math",
"officialName": "Rekenen/Wiskunde — Kerndoelen 23–33 (SLO)",
"source": { "name": "SLO Kerndoelen Primair Onderwijs", "url": "https://www.slo.nl/…" },
"assessmentFocus": "Automatiseren van optellen en aftrekken tot 10" },
"units": [ { "unitId": "NL:math:g1:u:rrw96f", "name": "Optellen en aftrekken tot 20", "description": "…",
"skills": [ { "skillId": "NL:math:g1:s:lm8mtk", "name": "Sommen tot 10" } ], "matchScore": 0 } ],
"competencies": [ { "competencyId": "…", "name": "Getalbegrip en getalrelaties" } ],
"criteria": [ { "criterionId": "…", "text": "Leerlingen kunnen getallen tot 20 lezen, schrijven en ordenen", "area": "comprehension" } ]
}get_curriculum_unitGet one curriculum unitGET /v1/agent/curriculum/units/:unitId?standardId=Full detail of one curriculum unit (skills) plus the standard's competencies and assessment criteria.
| Input | Type | Required | Description |
|---|---|---|---|
standardId | string | yes | standardId from search_curriculum_standards |
unitId | string | yes | unitId from search_curriculum_standards |
Returns
{ "standard": { … }, "unit": { "unitId": "…", "name": "…", "description": "…", "skills": [ … ] }, "competencies": [ … ], "criteria": [ … ] }get_pricing_for_countryGet Samio for Schools pricingGET /v1/agent/pricing?country=XX (same JSON as /v1/schools/pricing/resolve)Plan prices, purchasing-power tier and limits for a school in a given country.
| Input | Type | Required | Description |
|---|---|---|---|
country | string | yes | Two-letter ISO country code of the school, e.g. MX, NL, US |
Returns
{ "success": true, "data": { "country": "MX", "countrySource": "query", "pricingTier": "tier_b", "pricingTierLabel": "Equity — Upper-middle income", "pricingDiscountPercent": 50, "currency": "USD" } }Connect it
- Claude (claude.ai, Desktop, mobile, Cowork): Customize → Connectors → Add custom connector → paste the endpoint, authentication “none”. On Team and Enterprise plans an Owner adds it once for the organisation.
- ChatGPT: Settings → Connectors → Create (developer mode) → paste the endpoint, no authentication.
- Claude Code, Gemini CLI, the Claude API and any other MCP client: snippets below. Listings in the Claude Connectors Directory and the ChatGPT plugin directory are being prepared; until they are live the custom-connector route above is the way in.
- Teachers: the non-technical walkthrough with example prompts is the announcement article Samio for Schools now works inside Claude and ChatGPT.
# Claude Code
claude mcp add --transport http samio https://mcp.samiolearning.com/mcp
# Gemini CLI — ~/.gemini/settings.json
{ "mcpServers": { "samio": { "httpUrl": "https://mcp.samiolearning.com/mcp", "oauth": { "enabled": true } } } }
# Any MCP client / plain HTTP: list the tools (JSON-RPC over Streamable HTTP)
curl -s https://mcp.samiolearning.com/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'# Claude API — let the model call the server directly (MCP connector, beta)
POST https://api.anthropic.com/v1/messages
anthropic-beta: mcp-client-2025-11-20
{
"model": "claude-opus-5",
"max_tokens": 1024,
"mcp_servers": [ { "type": "url", "url": "https://mcp.samiolearning.com/mcp", "name": "samio" } ],
"tools": [ { "type": "mcp_toolset", "mcp_server_name": "samio" } ],
"messages": [ { "role": "user", "content": "What does the Dutch curriculum expect in maths for a 7-year-old?" } ]
}Tools voor leerkrachten (OAuth 2.1)
Teachers can connect their Samio for Schools account to Claude, ChatGPT or any MCP client and let the assistant create teaching materials, topic plans and challenges in their own classes, read Samio's verified knowledge facts and see class-level curriculum coverage. The connection is an ordinary OAuth 2.1 grant: the teacher signs in with the same credentials as the dashboard, sees exactly which permissions the app asks for, and can revoke the app at any time under Settings → Connected apps. Generation runs on the same queues, plan limits and consent checks as the dashboard, and every generated artefact carries a provenance line naming the app.
How a teacher connects
- 1Connect. Add https://mcp.samiolearning.com/mcp in Claude (Customize → Connectors) or ChatGPT (Settings → Connectors). The curriculum tools work right away; the first teacher tool call answers 401 with the resource metadata, which makes the client start the login.
- 2Samio login. The browser opens samiolearning.com/oauth/consent. The teacher signs in with their school account — e-mail and password, Google, Microsoft or the school's SSO — exactly as on the dashboard.
- 3Approve permissions. The consent screen names the app and its publisher, the school and the signed-in teacher, and lists the requested scopes in the teacher's language, with the fixed line "This app never receives student names, scores or results." Approve or Deny.
- 4Tools appear. The client exchanges the code for a one-hour access token (plus a rotating 30-day refresh token) and the fourteen teacher tools are live. Revoking under Settings → Connected apps cuts them off within seconds.
Scopes
Space-separated in scope. Every consent grants at least openid email; a request without scopes is treated as openid email classes:read. The titles below are the ones the teacher sees on the consent screen (in all seven interface languages). students:read is reserved and never issued.
| Scope | What the teacher reads | Tools |
|---|---|---|
openid email | Know who you are — Name and e-mail of the connected teacher (OIDC id_token + userinfo). Always granted. | get_my_account |
classes:read | See your classes — Class names and student counts, never student names. | list_my_classes |
materials:write | Create teaching materials in your account — Mind maps, presentations, didactic materials, study cards. | list_material_types, generate_teaching_material, get_teaching_material, list_teaching_materials |
plans:write | Create topic plans and lessons for your classes — Curriculum-grounded plans on the school's academic calendar. | create_topic_plan, get_topic_plan, get_topic_plan_week, list_topic_plans |
challenges:write | Create challenges — Drafts unless the teacher asks to publish. | generate_challenge |
knowledge:read | Read Samio's verified knowledge facts — Sourced facts that ground generated content. | search_knowledge_facts |
analytics:read | See class-level curriculum coverage — Never per-student results. | get_class_curriculum_progress |
The fourteen teacher tools
Every Tier B call sends Authorization: Bearer <access token> to the gateway; a missing or expired token answers 401 with WWW-Authenticate: Bearer resource_metadata=…, a missing scope 403 AGENT_SCOPE_MISSING (with the required scope), a revoked connection 403 AGENT_GRANT_REVOKED. Generation tools are asynchronous: they return the new id with status: "generating" and the matching get_ tool polls (typically 1–5 minutes). Per-school generation quota: 50 calls per day (429 AGENT_SCHOOL_QUOTA_EXCEEDED + Retry-After); a subscription limit answers 402 PLAN_LIMIT_REACHED.
get_my_accountWho am IThe connected teacher, their school and the scopes this connection holds. Call it first to confirm which account the assistant is acting for.
| Input | Type | Required | Description |
|---|---|---|---|
| No input. | |||
Returns
{ "user": { "id": "…", "name": "Ana Ruiz", "email": "ana@school.example", "role": "teacher" },
"school": { "id": "…", "name": "Colegio Sol", "country": "ES", "language": "es", "plan": "school" },
"scopes": ["openid", "email", "classes:read", "materials:write"], "app": { "name": "Claude" } }list_my_classesList my classesYour classes with id, name, grade, subjects, language and student count — never student names. The classId feeds every other tool.
| Input | Type | Required | Description |
|---|---|---|---|
| No input. | |||
Returns
{ "classes": [ { "classId": "…", "name": "3º A", "gradeLevel": 3, "gradeName": "3.º de Primaria", "subjects": ["math", "reading", …], "language": "es", "studentCount": 24, "academicCalendarId": "…" } ] }list_material_typesList material typesThe four material types Samio generates, with a description of each.
| Input | Type | Required | Description |
|---|---|---|---|
| No input. | |||
Returns
{ "types": [ { "type": "mindmap", "title": "Mind map", "description": "…" }, { "type": "presentation", … }, { "type": "didactic_material", … }, { "type": "study_cards", … } ] }generate_teaching_materialGenerate a teaching materialStart generating a material on a topic for a class (or a subject + grade). Same validation, plan limits and queue as the dashboard.
Asynchronous — poll get_teaching_material until status is "ready" (typically 1–5 minutes).
| Input | Type | Required | Description |
|---|---|---|---|
materialType | enum | yes | mindmap | presentation | didactic_material | study_cards |
topic | string 2–200 | yes | Topic of the material |
classId | string | no | Class to create it for — give classId OR subject + gradeLevel |
subject | enum | no | math | reading | grammar | clock | science | physics | history | computing |
gradeLevel | integer 1–7 | no | Samio stage when no classId is given |
language | string | no | Content language (defaults to the school's) |
unitId | string | no | Curriculum unit to ground on (from search_curriculum_standards) |
standardId | string | no | Standard the unit belongs to |
options | object | no | Type-specific: slideCount + theme, branches, cardCount, materialKind + includeAnswerKey, includeImages |
Returns
{ "materialId": "…", "status": "generating", "title": "Fractions — mind map", "materialType": "mindmap", "provenance": "Created with Samio's AI assistant via Claude" }get_teaching_materialGet a teaching materialStatus and, once ready, the stored content of one material (markdown or structured JSON, as the dashboard receives it).
| Input | Type | Required | Description |
|---|---|---|---|
materialId | string | yes | From generate_teaching_material or list_teaching_materials |
Returns
{ "materialId": "…", "status": "ready", "title": "…", "materialType": "presentation", "subject": "science", "classId": "…", "content": { … }, "provenance": "…", "dashboardUrl": "https://samiolearning.com/schools/dashboard/teaching-materials" }list_teaching_materialsList teaching materialsYour materials, newest first (max 50), optionally for one class.
| Input | Type | Required | Description |
|---|---|---|---|
classId | string | no | Only this class |
limit | integer 1–50 | no | Default 20 |
Returns
{ "materials": [ { "materialId": "…", "status": "ready", "title": "…", "materialType": "study_cards", "createdAt": "2026-09-21T09:12:00Z" } ] }create_topic_planCreate a topic planStart a curriculum-grounded plan for a class and subject on the school's active academic calendar. mode "auto" also generates lessons and challenges for every week.
Asynchronous — poll get_topic_plan; its progress block reports the phase and percentage (typically 1–5 minutes, longer for "auto").
| Input | Type | Required | Description |
|---|---|---|---|
classId | string | yes | Class |
subject | enum | yes | math | reading | grammar | clock | science | physics | history | computing |
mode | enum | no | plan (default) | auto |
weeks | integer 1–52 | no | Advisory — the academic calendar sets the real span |
classProfile | object | no | { studentCount, energyLevel: calm | balanced | energetic, traits[], notes } |
language | string | no | Content language |
Returns
{ "planId": "…", "status": "generating", "mode": "plan", "title": "Matemáticas 3º A 2026–2027", "provenance": "…" }get_topic_planGet a topic planStatus, progress and the week list of one plan.
| Input | Type | Required | Description |
|---|---|---|---|
planId | string | yes | From create_topic_plan or list_topic_plans |
Returns
{ "planId": "…", "status": "active", "mode": "plan", "title": "…", "subject": "math", "classId": "…", "totalWeeks": 36,
"progress": { "phase": "weeks", "percent": 100, "message": "Done" },
"weeks": [ { "weekNumber": 1, "title": "Numbers to 1000", "topics": ["Place value", "Comparing"], "status": "pending", "hasLesson": true, "materialIds": [], "challengeIds": [] } ],
"dashboardUrl": "https://samiolearning.com/schools/dashboard/topic-planner?view=detail&planId=…" }get_topic_plan_weekGet one week of a planTopics, objectives, the lesson (when generated) and the materials and challenges linked to one week.
| Input | Type | Required | Description |
|---|---|---|---|
planId | string | yes | Plan |
weekNumber | integer ≥ 1 | yes | 1-based week |
Returns
{ "planId": "…", "weekNumber": 3, "title": "…", "topics": [ … ], "objectives": [ … ], "lesson": { … } | null,
"materials": [ { "materialId": "…", "title": "…", "materialType": "mindmap", "status": "ready" } ],
"challenges": [ { "challengeId": "…", "title": "…", "status": "draft" } ] }list_topic_plansList topic plansYour plans, optionally for one class.
| Input | Type | Required | Description |
|---|---|---|---|
classId | string | no | Only this class |
Returns
{ "plans": [ { "planId": "…", "status": "active", "title": "…", "subject": "reading", "classId": "…", "totalWeeks": 36, "createdAt": "…" } ] }generate_challengeGenerate a challengeCreate a quiz with generated exercises for a class and subject. Saved as a draft the teacher reviews in the dashboard unless publish is true (max 10 exercises).
Asynchronous: exercises are generated in the background; poll get_challenge.
| Input | Type | Required | Description |
|---|---|---|---|
classId | string | yes | Class |
subject | enum | yes | math | reading | grammar | clock | science | physics | history | computing |
count | integer 1–10 | no | Exercises (default 5) |
exerciseTypes | string[] | no | Exercise types of the subject, e.g. fractions, word_problems |
difficulty | string | no | easy | medium | hard |
unitId | string | no | Curriculum unit to target |
topic | string ≤200 | no | Free-text steer |
publish | boolean | no | Default false — drafts unless the teacher asks to publish |
Returns
{ "challengeId": "…", "status": "generating", "title": "Fractions — quiz", "exerciseCount": 0, "preview": [],
"publishOnReady": false, "dashboardUrl": "https://samiolearning.com/schools/dashboard/challenges?review=…", "provenance": "…" }get_challengeGet one challengeStatus and preview of a challenge created with generate_challenge: generating, draft, published or failed, with the generated questions once ready.
| Input | Type | Required | Description |
|---|---|---|---|
challengeId | string | yes | challengeId from generate_challenge |
Returns
{ "challengeId": "…", "status": "draft", "title": "Fractions — quiz", "subject": "math", "classId": "…", "exerciseCount": 5,
"preview": [ { "type": "fractions", "question": "Which fraction is larger: 2/3 or 3/5?" }, … ],
"dashboardUrl": "https://samiolearning.com/schools/dashboard/challenges?review=…" }search_knowledge_factsSearch knowledge factsSamio's verified, sourced facts on a topic — the same facts that ground generated lessons and materials (max 20).
| Input | Type | Required | Description |
|---|---|---|---|
subject | enum | yes | math | reading | grammar | clock | science | physics | history | computing |
topic | string 2–120 | yes | Topic, e.g. photosynthesis |
language | string | no | Defaults to the school's |
limit | integer 1–20 | no | Default 10 |
Returns
{ "facts": [ { "factId": "…", "statement": "Photosynthesis converts light energy into chemical energy.", "subject": "science", "topic": "photosynthesis", "language": "en", "sources": [ { "title": "…", "url": "…" } ], "verified": true } ] }get_class_curriculum_progressClass curriculum coverageWhich official curriculum units a class has mastered, is working on or has not started, for one subject. A class rollup — never per-student rows.
| Input | Type | Required | Description |
|---|---|---|---|
classId | string | yes | Class |
subject | enum | yes | math | reading | grammar | clock | science | physics | history | computing |
Returns
{ "classId": "…", "subject": "math", "standard": { "standardId": "…", "officialName": "…", "gradeName": "…" },
"coverage": { "percent": 42, "unitsMastered": 5, "unitsInProgress": 3, "unitsPending": 4 },
"units": [ { "unitId": "…", "name": "Fractions", "status": "in_progress", "percent": 61 } ] }Discovery
# Authorization server (RFC 8414; the same document is also served OIDC-shaped for ChatGPT)
GET https://api.samiolearning.com/oauth/.well-known/oauth-authorization-server
GET https://api.samiolearning.com/.well-known/oauth-authorization-server/oauth
GET https://api.samiolearning.com/oauth/.well-known/openid-configuration
GET https://api.samiolearning.com/oauth/jwks.json
# Protected resource (RFC 9728) — what the 401 from /mcp points at
GET https://mcp.samiolearning.com/.well-known/oauth-protected-resource
→ { "resource": "https://mcp.samiolearning.com/mcp",
"authorization_servers": ["https://api.samiolearning.com/oauth"],
"scopes_supported": ["openid", "email", "classes:read", "materials:write", "plans:write", "challenges:write", "knowledge:read", "analytics:read"],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://samiolearning.com/developers#mcp" }Notes for developers building a client
- Client registration: prefer a Client ID Metadata Document — your
client_idis an https URL serving JSON withredirect_uris,client_name,client_uri,logo_uri(fetched with a 3 s timeout, cached one hour). Dynamic Client Registration (RFC 7591,POST /oauth/register) is also accepted. Redirect-URI hosts must be on the allow-list (claude.ai, chatgpt.com, chat.openai.com, platform.openai.com, localhost, 127.0.0.1);http://localhostandhttp://127.0.0.1match port-agnostically (RFC 8252 §7.3). Both mechanisms produce public clients (token_endpoint_auth_method: "none"). - PKCE S256 is mandatory;
response_type=codeonly. Passresource=https://mcp.samiolearning.com/mcp(RFC 8707) — it becomes the token's audience. - Tokens:
POST /oauth/tokenaccepts form-urlencoded and JSON; access tokens are RS256 JWTs valid one hour; refresh tokens live 30 days and rotate on every use — presenting an already-rotated token revokes the whole family.POST /oauth/revokealways answers 200. - No
client_credentialsgrant: every connection is a teacher consenting on the screen above. Service-to-service access to the curriculum tools uses the app key below instead. - Same schemas in the browser: signed-in dashboard pages register the twelve teacher tools (all but
get_my_account) through WebMCP with identical names and input schemas, executed with the teacher's session — see Agent tools (WebMCP).
Prefer plain REST? The agent gateway
Every tool is a thin wrapper over one endpoint of the Samio API’s agent gateway. Developers who would rather call HTTP than MCP can use the curriculum endpoints directly with an app key sent asX-Samio-Agent-Key(request one at info@samiolearning.com). The OpenAPI 3.1 description is public.
GET https://api.samiolearning.com/v1/agent/openapi.json # public, no key
GET https://api.samiolearning.com/v1/agent/curriculum/countries
GET https://api.samiolearning.com/v1/agent/curriculum/standards?country=NL&subject=math&age=7&query=optellen
GET https://api.samiolearning.com/v1/agent/curriculum/units/{unitId}?standardId={standardId}
GET https://api.samiolearning.com/v1/agent/pricing?country=MX
# errors are { "error": "...", "code": "..." }
# 400 INVALID_COUNTRY | INVALID_SUBJECT | INVALID_GRADE | INVALID_AGE | INVALID_LANGUAGE | INVALID_QUERY | INVALID_LIMIT
# 401 AGENT_KEY_INVALID · 403 AGENT_APP_DISABLED · 404 NO_CURRICULUM | UNIT_NOT_FOUND
# 429 AGENT_QUOTA_EXCEEDED (+ Retry-After) · 503 AGENT_ACCESS_DISABLED
# Teacher endpoints (Tier B) take BOTH the app key and the teacher's OAuth bearer:
GET https://api.samiolearning.com/v1/agent/me
GET https://api.samiolearning.com/v1/agent/classes
POST https://api.samiolearning.com/v1/agent/teaching-materials # 202 { materialId, status: "generating" }
GET https://api.samiolearning.com/v1/agent/teaching-materials/{materialId}
POST https://api.samiolearning.com/v1/agent/topic-plans # 202 { planId, status: "generating" }
GET https://api.samiolearning.com/v1/agent/topic-plans/{planId}
GET https://api.samiolearning.com/v1/agent/topic-plans/{planId}/weeks/{n}
POST https://api.samiolearning.com/v1/agent/challenges
GET https://api.samiolearning.com/v1/agent/knowledge/facts?subject=&topic=
GET https://api.samiolearning.com/v1/agent/classes/{classId}/curriculum-progress?subject=
# 401 AGENT_TOKEN_INVALID · 403 AGENT_SCOPE_MISSING | AGENT_GRANT_REVOKED · 402 PLAN_LIMIT_REACHED
# 404 CLASS_NOT_FOUND | PLAN_NOT_FOUND | MATERIAL_NOT_FOUND | WEEK_NOT_FOUND · 409 GENERATION_IN_PROGRESS
# 429 AGENT_SCHOOL_QUOTA_EXCEEDED (+ Retry-After)- Quotas: 600 calls per hour per app key and 120 per hour per client IP for developer apps; the MCP endpoint itself allows 120 requests per hour per IP. Curriculum responses carry
Cache-Control: public, max-age=3600. - Audit: one row per call with the route template, status, duration, a hash of the inputs and a salted hash of the IP; no free text, no personal data, purged after 180 days.
- The tool names and input schemas are identical on the MCP server and in the gateway’s OpenAPI file, so a client can switch between the two without remapping.
Problems, feature requests or a developer key: info@samiolearning.com. The gateway can be switched off centrally; when it is, every call answers 503 AGENT_ACCESS_DISABLED.
Samio Learning — Edu-V API v1.0.0
