Samio
v1.0.0
OAuth 2.0
NL GOV Profile

Referencia API Edu-V

Documentación de integración para interoperabilidad educativa holandesa

URL base: https://api.samiolearning.com/v1/eduv
Público objetivo: Educación primaria (primair onderwijs / po)
Especificación: Edu-V v1.0 (Stichting Edu-V)

Descripción general

Samio participa en Edu-V (versión NR26) como panel de progreso para docentes. Es afnemer (consumidor) de datos de administración escolar —onderwijsinrichting, onderwijsdeelnemers, onderwijsmedewerkers y grupos— de sistemas SIS neerlandeses como ParnasSys y ESIS, y verzender (emisor) de los resultados de aprendizaje generados en la app gratuita de Samio. Samio no es un portal: no recibe derechos de uso, ni catálogo, ni resultados.

Roles de Samio

ReferentiecomponentGegevensdienstDirecciónRelease
Dienst met SSOIdentiteitsverklaringReceptor (ontvanger)NR26
Dashboard voortgangOnderwijsinrichtingConsumidor (afnemer)NR26
Dashboard voortgangOnderwijsdeelnemerConsumidor (afnemer)NR26
Dashboard voortgangOnderwijsmedewerkersConsumidor (afnemer)NR26
Dashboard voortgangOnderwijsdeelname (groepen)Consumidor (afnemer)NR26
Dashboard voortgangLeerresultatenEmisor (verzender)NR26
text
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)

Autenticación

Todos los endpoints requieren un token Bearer obtenido vía OAuth 2.0 con el perfil NL GOV (autenticación de cliente private_key_jwt).

Endpoint de token

POST/v1/eduv/oauth/token
Cuerpo de la solicitud
ParámetroTipoDescripción
grant_typestring
Requerido
Must be "client_credentials"
client_assertion_typestring
Requerido
urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionstring
Requerido
Signed JWT (RS256, PKIoverheid key)
scopestring
Opcional
Space-separated scopes
bash
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"
json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "eduv.student.basic eduv.association"
}

Endpoint JWKS

GET/v1/eduv/oauth/.well-known/jwks.json

One 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.

json
{
  "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.

json
// 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.association

Token Properties

Algorithm:RS256Vida útil del token:3600 segundosIssuer:https://api.samiolearning.comAudience:https://api.samiolearning.com/v1/eduv

Códigos de error

json
{
  "error": "Human-readable error message",
  "error_code": "1001",
  "error_description": "Optional additional details"
}
CódigoEstado HTTPMensaje
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

Paginación

Los endpoints paginados aceptan parámetros start y limit.

ParámetroTipoPor defectoMáx
startinteger0—
limitinteger20100
json
{
  "data": [ ... ],
  "total": 142,
  "start": 0,
  "limit": 20,
  "hasMore": true
}

DPA

Entrante — Samio lo expone
eduv.dpa
GET/v1/eduv/dpa/agreements

Get DPA agreement status for a school.

Parámetros
ParámetroTipoDescripción
schoolBrinCodestring
Requerido
School's BRIN code
POST/v1/eduv/dpa/verify

Verify a Data Processing Agreement (verwerkersovereenkomst).

Cuerpo de la solicitud
ParámetroTipoDescripción
schoolBrinCodestring
Requerido
School's BRIN code
dpaReferencestring
Opcional
Agreement reference number
agreementDateISO date
Opcional
Date of agreement

Respuesta

json
{
  "verified": true,
  "partner": { "oin": "0000000123...", "name": "ParnasSys" },
  "schoolBrinCode": "01AB",
  "verificationType": "local"
}

Educación

Saliente — Samio lo invoca en tu endpoint
eduv.education
GET/organisations

Education 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.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
boardMasterIdstring
Opcional
Bestuur master id
boardIdstring
Opcional
Bestuur id, with boardIdType=BGE_CODE
namestring
Opcional
School name

Respuesta

json
[{
  "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"
}]
GET/studyofferings/school

Study offerings (opleidingen, e.g. primary education) of the school in a school year. Samio uses the studyOfferingId to scope pupil and enrollment reads.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
studyCodestring
Opcional
Filter on study code
GET/studyofferings/school/:id

One study offering by id.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID

Estudiantes

Saliente — Samio lo invoca en tu endpoint
eduv.student.basic
Consentimiento requerido: eduv.student.basic
GET/students/school

Students 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.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
studyOfferingIdstring
Opcional
Limit to one study offering

Respuesta

json
[{
  "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"
}]
POST/students

Search one pupil by ECK iD or secondary id (used for the ECK iD mapping page and spot checks).

Cuerpo de la solicitud
ParámetroTipoDescripción
schoolSchoolReference
Requerido
{ organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] }
studentUserReference
Requerido
{ userMasterIdentifier } or { userIds[{userId, userIdType}] }

Empleados

Saliente — Samio lo invoca en tu endpoint
eduv.employee.basic
Consentimiento requerido: eduv.employee.basic
GET/employees/school

Employees 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.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)

Respuesta

json
[{
  "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"
}]
POST/employees

Search one staff member by secondary id.

Cuerpo de la solicitud
ParámetroTipoDescripción
schoolSchoolReference
Requerido
{ organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] }
employeeUserReference
Requerido
{ userIds[{userId, userIdType}] } (NEPPI, ASI)

Asociación

Saliente — Samio lo invoca en tu endpoint
eduv.association
Consentimiento requerido: eduv.association
GET/schoolperiods/school

Association 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.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID

Respuesta

json
[{
  "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"
}]
GET/schoolperiods/school/:id

One school period by id.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
GET/groups/school

Classes (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.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
groupTypestring
Opcional
class | lesson-group (Samio asks for class)
studyOfferingIdstring
Opcional
Limit to one study offering

Respuesta

json
[{
  "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"
}]
GET/groups/school/:groupId

One group by id (groupId is a free string, URL-encoded).

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
GET/enrollments/school

Enrollments of pupils in a study (enrollmentType study) or subject.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
studyOfferingIdstring
Opcional
Limit to one study offering
enrollmentTypestring
Opcional
study | subject
GET/enrollments/school/:id

One enrollment by id.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
POST/enrollments/school/student

Enrollments of one pupil at the school.

Parámetros
ParámetroTipoDescripción
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
studyOfferingIdstring
Opcional
Limit to one study offering
enrollmentTypestring
Opcional
study | subject
Cuerpo de la solicitud
ParámetroTipoDescripción
schoolSchoolReference
Requerido
{ organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] }
studentUserReference
Requerido
{ userMasterIdentifier } or { userIds[] }
GET/assignments/school

Staff assignments (class-teacher, teacher, coach) to groups and pupils.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
assignmentTypestring
Opcional
class-teacher | teacher | coach
GET/assignments/school/:id

One assignment by id.

Parámetros
ParámetroTipoDescripción
orgMasterIdstring
Opcional
RIO onderwijsaanbieder master id of the school (e.g. 777A000). Samio uses this when the school has one
orgIdstring
Opcional
Secondary school id, with orgIdType (OIE_CODE = BRIN4, V_ID = BRIN6 vestiging, AS_ID, BP_ID, DD_ID). Never combined with orgMasterId
orgIdTypestring
Opcional
OIE_CODE | V_ID | AS_ID | BP_ID | DD_ID
POST/assignments/school/employee

Assignments of one staff member at the school.

Parámetros
ParámetroTipoDescripción
schoolPeriodIdstring
Opcional
School year, e.g. 2026-2027 (Samio always sends the current one)
assignmentTypestring
Opcional
class-teacher | teacher | coach
Cuerpo de la solicitud
ParámetroTipoDescripción
schoolSchoolReference
Requerido
{ organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] }
employeeUserReference
Requerido
{ userIds[] }

Resultados

Saliente — Samio lo invoca en tu endpoint
eduv.result
POST/results

Results 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).

Cuerpo de la solicitud
ParámetroTipoDescripción
iduuid
Requerido
Message id, stable across retries
timestampISO datetime
Requerido
When the message was composed
assessmentDateTimeISO datetime
Requerido
End of the batch window
assessmentDefinitionAssessment
Requerido
{ id: "samio-app-exercises", name, version, subjects[{subjectName}], parts[{id, name, index}] }
schoolSchoolReference
Requerido
{ organisationMasterIdentifier, organisationIds[{organisationId, organisationIdType}] }
schoolPeriodstring
Requerido
eejj-eejj, e.g. 2026-2027
toolNamestring
Opcional
Samio Learning
studentScoresAndResultsStudentScoresAndResults[]
Opcional
Per pupil: { id, student: StudentReference, status: Final, scores[{scoreValue, scoreType, scoreMaximum, assessmentId, assessmentPartId}], results[{resultValue, resultType, assessmentId, assessmentPartId}], dateCreated, dateLastModified }

Respuesta

json
HTTP/1.1 202 Accepted
API-Version: 2.1.0

Notificaciones

Entrante — Samio lo expone
eduv.notifications
POST/v1/eduv/notifications/subscribe

Subscribe to change notifications (webhooks).

Cuerpo de la solicitud
ParámetroTipoDescripción
callbackUrlstring
Requerido
URL to receive webhooks
notificationTypesstring[]
Requerido
Event types to subscribe to
schoolIdstring
Opcional
Scope to specific school
DELETE/v1/eduv/notifications/subscribe/:id

Unsubscribe from notifications.

GET/v1/eduv/notifications/subscriptions

List active subscriptions.

POST/v1/eduv/notifications/receive

Receive inbound notification from a partner (webhook).

Cuerpo de la solicitud
ParámetroTipoDescripción
notificationTypestring
Requerido
Event type
objectTypestring
Requerido
student, employee, group, etc.
objectIdstring
Opcional
Object identifier
schoolobject
Opcional
{ brinCode }
urlstring
Opcional
URL to fetch the updated object
isDeleteNotificationboolean
Opcional
Whether the object was deleted

Scopes

Scopes OAuth 2.0 disponibles para la integración Edu-V.

ScopeAPIsDescripción
eduv.consentConsentManage consent lifecycle (supporting)
eduv.dpaDPAVerify Data Processing Agreements (supporting)
eduv.notificationsNotificationsSubscribe to roster-change notifications
eduv.educationEducationOnderwijsinrichting — organisations, studies, subjects (afnemer)
eduv.student.basicStudentsOnderwijsdeelnemer — identity and name (afnemer)
eduv.employee.basicEmployeesOnderwijsmedewerkers — identity and name (afnemer)
eduv.associationAssociationOnderwijsdeelname — groups and enrollments (afnemer)
eduv.resultResultsLeerresultaten — results sent by Samio (verzender)

Modelos de datos

CampoTipoDescripción
schoolIdObjectId → SchoolTarget school
partnerIdObjectId → EduVPartnerPartner
dataServicestringEdu-V scope
statusenumrequested | granted | revoked | denied
initiatedByenumschool | partner
grantedAtDateWhen consent was granted
expiresAtDateConsent expiry
CampoTipoDescripción
namestringPartner name
oinstringYour OIN (20 digits)
jwksUristringYour JWKS
tokenEndpointstringYour token endpoint (Samio authenticates here with private_key_jwt)
outboundClientIdstringclient_id you issued to Samio (iss/sub of Samio's assertion)
baseUrlstringYour v1 base for Students / Employees / Education / Association
resultsBaseUrlstringYour v2 base for Results (derived from baseUrl when empty)
clientIdstringYour client_id at Samio's token endpoint (when you call Samio)
allowedScopesstring[]Scopes you may request at Samio
statusenumpending | active | inactive
CampoTipoDescripción
eduv.oaIdstringRIO onderwijsaanbieder id — sent as orgMasterId
eduv.brinCodestringBRIN — sent as orgId with orgIdType V_ID (6) or OIE_CODE (4) when there is no oaId
eduv.oinNumberstringOIN of the school board
eduv.boardIdstringBestuur ID
eduv.bestuursnummerstringBoard number
eduv.enabledbooleanWhether Edu-V is active
CampoTipoDescripción
eduv.eckIdstringECK iD (userMasterIdentifier) — primary pupil identifier
eduv.externalIdsarray[{ type: ASI | BPI | …, value }] — the SIS userIds
eduv.givenNamestringFirst name as in the SIS
eduv.preferredFirstNamestringRoepnaam
eduv.familyNamestringLast name
eduv.familyNamePrefixstringTussenvoegsel ("van", "de")
eduv.aliasstringAlias
eduv.sisStatusenumactive | tobedeleted — last status from the SIS; tobedeleted also deactivates the pupil in Samio
eduv.lastSyncedAtDateLast roster sync that touched this pupil
CampoTipoDescripción
eduv.userMasterIdentifierstringPrimary identifier from the SIS, when present
eduv.externalIdsarray[{ type: NEPPI | ASI | …, value }]
eduv.preferredFirstNamestringRoepnaam
eduv.familyNamePrefixstringTussenvoegsel
eduv.organisationRolesarray[{ organisationId, role, beginDate, endDate }]
eduv.sisStatusenumactive | tobedeleted — recorded only; never changes the user's Samio access
eduv.lastSyncedAtDateLast roster sync
CampoTipoDescripción
eduv.groupIdstringSIS groupId this class mirrors
eduv.groupNamestringSIS group name (the teacher's own class name stays untouched)
eduv.groupTypeenumclass | lesson-group
eduv.schoolPeriodIdstringSchool year of the group
eduv.sisStatusenumactive | tobedeleted
eduv.memberCountnumberMembers reported by the SIS
eduv.lastSyncedAtDateLast roster sync

Inicio rápido

Flujo de integración completo desde el registro de partner hasta la lectura de resultados.

1

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

2

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)

3

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)

4

Samio reads the school year → GET /schoolperiods/school?orgMasterId=…

5

Samio reads pupils, staff and classes → GET /students/school, /employees/school, /groups/school (schoolPeriodId=…)

6

Pupils are matched by ECK iD (then secondary id, then name); tobedeleted pupils are deactivated; nothing is created from the SIS

7

Pupils use Samio → (normal app usage)

8

Samio sends results → POST {v2 base}/results (scope eduv.result), hourly with retries

9

Optional: you notify Samio of roster changes → POST /v1/eduv/notifications/receive, and Samio re-reads the changed objects

Herramientas para agentes (WebMCP)

Experimental
navigator.modelContext
read-only on public pages
teacher tools on the dashboard

Herramientas que los agentes de IA del navegador pueden invocar en samiolearning.com: cuatro de solo lectura en todas las páginas públicas (precios por país, búsqueda de páginas, datos de producto, enlaces de apps) y, para un docente con sesión iniciada en el panel escolar, las mismas doce herramientas docentes que ofrece el servidor MCP, ejecutadas con la sesión del docente.

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 fallback

Samio 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.

InputTypeRequiredDescription
countrystringyesISO 3166-1 alpha-2 code, e.g. NL, MX, IN

Returns

json
{
  "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 routes

Find 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.

InputTypeRequiredDescription
querystringyesFree-text question or keywords
languagestringnoen (default), es, nl, de, fr, it, pt
audiencestringnoparents | schools | developers

Returns

json
{
  "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.json

Canonical facts about Samio Learning, Samio for Schools or Askimo (features, platforms, compliance, pricing, comparisons) plus entity disambiguation for the names "Samio" and "Askimo".

InputTypeRequiredDescription
productstringnosamio | schools | askimo — omit for all three

Returns

json
{ "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_linksstatic

Official App Store, Google Play and web links for the Askimo or Samio Learning apps, with price and age range.

InputTypeRequiredDescription
appstringnoaskimo (default) | samio

Returns

json
{ "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_progress

Discovery 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.
http
# 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.json

Status: 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.

Servidor MCP remoto (Samio para Escuelas)

v1
MCP 2026-07-28
Streamable HTTP
no login for curriculum tools
OAuth 2.1 for teacher tools
no student data

Un servidor MCP remoto al que puede conectarse cualquier asistente de IA — Claude, ChatGPT, Gemini, Claude Code o tu propio agente. Cuatro herramientas de solo lectura sobre los estándares curriculares de primaria de 50 países (56 currículos) y los precios de Samio para Escuelas funcionan sin iniciar sesión; trece herramientas para docentes (materiales, planes temáticos, desafíos, hechos verificados, cobertura por clase) se activan cuando un docente conecta su cuenta de Samio para Escuelas con OAuth 2.1. Ninguna herramienta expone datos de alumnos.

Endpoint: https://mcp.samiolearning.com/mcp
Health: https://mcp.samiolearning.com/health

The 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/countries

Which 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.

InputTypeRequiredDescription
countrystringnoOptional ISO country code (e.g. NL, ES, US) to return one country only

Returns

json
{
  "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/standards

Official 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.

InputTypeRequiredDescription
countrystringyesISO country code, e.g. NL, ES, DE, FR, IT, PT, GB, US
subjectenumyesmath | reading | grammar | clock | science | physics | history | computing
gradeinteger 1–7noSamio stage (1 ≈ age 4–5 … 7 ≈ age 11–13). Give grade OR age
ageinteger 3–18noChild's age; the matching grade is chosen for you
languagestringnoLanguage of the curriculum text (defaults to the country's)
querystring ≤120noFree-text topic to rank units by, e.g. 'fractions', 'breuken'
limitinteger 1–50noMax units to return (default 20)

Returns

json
{
  "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.

InputTypeRequiredDescription
standardIdstringyesstandardId from search_curriculum_standards
unitIdstringyesunitId from search_curriculum_standards

Returns

json
{ "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.

InputTypeRequiredDescription
countrystringyesTwo-letter ISO country code of the school, e.g. MX, NL, US

Returns

json
{ "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.
bash
# 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":{}}'
http
# 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?" } ]
}

Herramientas para docentes (OAuth 2.1)

authorization code + PKCE S256
refresh rotation
CIMD or DCR
no client_credentials
no student data

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

ScopeWhat the teacher readsTools
openid emailKnow who you are — Name and e-mail of the connected teacher (OIDC id_token + userinfo). Always granted.get_my_account
classes:readSee your classes — Class names and student counts, never student names.list_my_classes
materials:writeCreate 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:writeCreate 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:writeCreate challenges — Drafts unless the teacher asks to publish.generate_challenge
knowledge:readRead Samio's verified knowledge facts — Sourced facts that ground generated content.search_knowledge_facts
analytics:readSee 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 I
any
GET /v1/agent/me

The connected teacher, their school and the scopes this connection holds. Call it first to confirm which account the assistant is acting for.

InputTypeRequiredDescription
No input.

Returns

json
{ "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 classes
classes:read
GET /v1/agent/classes

Your classes with id, name, grade, subjects, language and student count — never student names. The classId feeds every other tool.

InputTypeRequiredDescription
No input.

Returns

json
{ "classes": [ { "classId": "…", "name": "3º A", "gradeLevel": 3, "gradeName": "3.º de Primaria", "subjects": ["math", "reading", …], "language": "es", "studentCount": 24, "academicCalendarId": "…" } ] }
list_material_typesList material types
materials:write
GET /v1/agent/material-types

The four material types Samio generates, with a description of each.

InputTypeRequiredDescription
No input.

Returns

json
{ "types": [ { "type": "mindmap", "title": "Mind map", "description": "…" }, { "type": "presentation", … }, { "type": "didactic_material", … }, { "type": "study_cards", … } ] }
generate_teaching_materialGenerate a teaching material
materials:write
async
POST /v1/agent/teaching-materials → 202

Start 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).

InputTypeRequiredDescription
materialTypeenumyesmindmap | presentation | didactic_material | study_cards
topicstring 2–200yesTopic of the material
classIdstringnoClass to create it for — give classId OR subject + gradeLevel
subjectenumnomath | reading | grammar | clock | science | physics | history | computing
gradeLevelinteger 1–7noSamio stage when no classId is given
languagestringnoContent language (defaults to the school's)
unitIdstringnoCurriculum unit to ground on (from search_curriculum_standards)
standardIdstringnoStandard the unit belongs to
optionsobjectnoType-specific: slideCount + theme, branches, cardCount, materialKind + includeAnswerKey, includeImages

Returns

json
{ "materialId": "…", "status": "generating", "title": "Fractions — mind map", "materialType": "mindmap", "provenance": "Created with Samio's AI assistant via Claude" }
get_teaching_materialGet a teaching material
materials:write
GET /v1/agent/teaching-materials/:materialId

Status and, once ready, the stored content of one material (markdown or structured JSON, as the dashboard receives it).

InputTypeRequiredDescription
materialIdstringyesFrom generate_teaching_material or list_teaching_materials

Returns

json
{ "materialId": "…", "status": "ready", "title": "…", "materialType": "presentation", "subject": "science", "classId": "…", "content": { … }, "provenance": "…", "dashboardUrl": "https://samiolearning.com/schools/dashboard/teaching-materials" }
list_teaching_materialsList teaching materials
materials:write
GET /v1/agent/teaching-materials?classId&limit

Your materials, newest first (max 50), optionally for one class.

InputTypeRequiredDescription
classIdstringnoOnly this class
limitinteger 1–50noDefault 20

Returns

json
{ "materials": [ { "materialId": "…", "status": "ready", "title": "…", "materialType": "study_cards", "createdAt": "2026-09-21T09:12:00Z" } ] }
create_topic_planCreate a topic plan
plans:write
async
POST /v1/agent/topic-plans → 202

Start 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").

InputTypeRequiredDescription
classIdstringyesClass
subjectenumyesmath | reading | grammar | clock | science | physics | history | computing
modeenumnoplan (default) | auto
weeksinteger 1–52noAdvisory — the academic calendar sets the real span
classProfileobjectno{ studentCount, energyLevel: calm | balanced | energetic, traits[], notes }
languagestringnoContent language

Returns

json
{ "planId": "…", "status": "generating", "mode": "plan", "title": "Matemáticas 3º A 2026–2027", "provenance": "…" }
get_topic_planGet a topic plan
plans:write
GET /v1/agent/topic-plans/:planId

Status, progress and the week list of one plan.

InputTypeRequiredDescription
planIdstringyesFrom create_topic_plan or list_topic_plans

Returns

json
{ "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 plan
plans:write
GET /v1/agent/topic-plans/:planId/weeks/:weekNumber

Topics, objectives, the lesson (when generated) and the materials and challenges linked to one week.

InputTypeRequiredDescription
planIdstringyesPlan
weekNumberinteger ≥ 1yes1-based week

Returns

json
{ "planId": "…", "weekNumber": 3, "title": "…", "topics": [ … ], "objectives": [ … ], "lesson": { … } | null,
  "materials": [ { "materialId": "…", "title": "…", "materialType": "mindmap", "status": "ready" } ],
  "challenges": [ { "challengeId": "…", "title": "…", "status": "draft" } ] }
list_topic_plansList topic plans
plans:write
GET /v1/agent/topic-plans?classId

Your plans, optionally for one class.

InputTypeRequiredDescription
classIdstringnoOnly this class

Returns

json
{ "plans": [ { "planId": "…", "status": "active", "title": "…", "subject": "reading", "classId": "…", "totalWeeks": 36, "createdAt": "…" } ] }
generate_challengeGenerate a challenge
challenges:write
async
POST /v1/agent/challenges

Create 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.

InputTypeRequiredDescription
classIdstringyesClass
subjectenumyesmath | reading | grammar | clock | science | physics | history | computing
countinteger 1–10noExercises (default 5)
exerciseTypesstring[]noExercise types of the subject, e.g. fractions, word_problems
difficultystringnoeasy | medium | hard
unitIdstringnoCurriculum unit to target
topicstring ≤200noFree-text steer
publishbooleannoDefault false — drafts unless the teacher asks to publish

Returns

json
{ "challengeId": "…", "status": "generating", "title": "Fractions — quiz", "exerciseCount": 0, "preview": [],
  "publishOnReady": false, "dashboardUrl": "https://samiolearning.com/schools/dashboard/challenges?review=…", "provenance": "…" }
get_challengeGet one challenge
challenges:write
GET /v1/agent/challenges/:challengeId

Status and preview of a challenge created with generate_challenge: generating, draft, published or failed, with the generated questions once ready.

InputTypeRequiredDescription
challengeIdstringyeschallengeId from generate_challenge

Returns

json
{ "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 facts
knowledge:read
GET /v1/agent/knowledge/facts?subject&topic&language&limit

Samio's verified, sourced facts on a topic — the same facts that ground generated lessons and materials (max 20).

InputTypeRequiredDescription
subjectenumyesmath | reading | grammar | clock | science | physics | history | computing
topicstring 2–120yesTopic, e.g. photosynthesis
languagestringnoDefaults to the school's
limitinteger 1–20noDefault 10

Returns

json
{ "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 coverage
analytics:read
GET /v1/agent/classes/:classId/curriculum-progress?subject

Which official curriculum units a class has mastered, is working on or has not started, for one subject. A class rollup — never per-student rows.

InputTypeRequiredDescription
classIdstringyesClass
subjectenumyesmath | reading | grammar | clock | science | physics | history | computing

Returns

json
{ "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

http
# 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_id is an https URL serving JSON with redirect_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://localhost and http://127.0.0.1 match port-agnostically (RFC 8252 §7.3). Both mechanisms produce public clients (token_endpoint_auth_method: "none").
  • PKCE S256 is mandatory; response_type=code only. Pass resource=https://mcp.samiolearning.com/mcp (RFC 8707) — it becomes the token's audience.
  • Tokens: POST /oauth/token accepts 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/revoke always answers 200.
  • No client_credentials grant: 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.

http
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