API reference
Learner API
Product user JWT API for humans in Campus, the website widget, WordPress, or your own UI.
Base path: https://www.tribepeer.com/api/product/v1. Tokens are issued only after email verification — no Sanctum sessions.
Always send X-Institution-Uuid on school-scoped routes so learners only see that institution’s classes.
Partner tp_sec_ never belongs in Campus / webview env vars.
AuthorizationBearer <product_jwt> on authenticated routes
X-Institution-UuidSchool UUID — required for tribes, chat, ask, manage
Content-Typeapplication/json
Acceptapplication/json
Authentication
POST
/product/v1/auth/register
Create a TribePeer user. No token until verify.
Request body
first_namestring ≤80, no spaces, required
last_namestring ≤80, no spaces, required
emailunique email, required
passwordmin 8 chars, required
roleoptional user | tutor
Response 201
application/json
{
"requires_verification": true,
"email": "learner@school.edu",
"message": "Account created. Check your email for a verification code. No token until you verify."
}
POST
/product/v1/auth/verify
Confirm the 6-digit OTP and receive a product JWT.
emailrequired
otpexactly 6 digits
Response 200
application/json Copy
{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": "2026-09-06T12:00:00+00:00",
"user": { "uuid": "…", "email": "…", "first_name": "…", "…" : "…" }
}
POST
/product/v1/auth/login
Sign in with email or username. Unverified accounts get 403 + fresh OTP.
loginemail or username, required
passwordrequired
POST /api/product/v1/auth/login
cURL
JavaScript
PHP
Python
Copy
curl -s https://www.tribepeer.com/api/product/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"login": "learner@school.edu",
"password": "••••••••"
}'
const res = await fetch('https://www.tribepeer.com/api/product/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
login: 'learner@school.edu',
password: '••••••••',
}),
})
const data = await res.json()
// data.access_token, data.user
$data = Http::post('https://www.tribepeer.com/api/product/v1/auth/login', [
'login' => 'learner@school.edu',
'password' => 'secret-pass',
])->json();
r = requests.post('https://www.tribepeer.com/api/product/v1/auth/login', json={
'login': 'learner@school.edu',
'password': 'secret-pass',
})
print(r.json())
Unverified 403
application/json
{
"error": "unverified",
"requires_verification": true,
"email": "learner@school.edu",
"message": "Verify your email before signing in. We sent a new code."
}
POST /auth/resend-otpbody: { "email" }
POST /auth/refreshauthenticated · new JWT
GET /meauthenticated · current user
Public branding
No auth. Resolve school brand by publishable key or institution UUID.
keyquery · pk_test_ / pk_live_
institutionquery · institution UUID (or header X-Institution-Uuid)
Response includes institution, branding colours/logo, and Campus flags such as Ask enabled / watermark.
Join a class
Redeem an institution join code for the signed-in user.
join_codestring ≤40, required
Response 200
application/json
{
"status": "joined",
"message": "…",
"tribe_uuid": "…",
"institution_uuid": "…"
}
Classes & materials
GET
/product/v1/tribes/mine
Classes the user enrolled in or owns at this school.
GET /api/product/v1/tribes/mine
cURL
JavaScript
PHP
Python
Copy
curl -s https://www.tribepeer.com/api/product/v1/tribes/mine \
-H "Authorization: Bearer $USER_TOKEN" \
-H "X-Institution-Uuid: $INSTITUTION_UUID"
const res = await fetch('https://www.tribepeer.com/api/product/v1/tribes/mine', {
headers: {
Authorization: `Bearer ${userToken}`,
'X-Institution-Uuid': institutionUuid,
},
})
const { enrolled, owned, is_owner } = await res.json()
$data = Http::withToken($userToken)
->withHeaders(['X-Institution-Uuid' => $institutionUuid])
->get('https://www.tribepeer.com/api/product/v1/tribes/mine')
->json();
r = requests.get('https://www.tribepeer.com/api/product/v1/tribes/mine', headers={
'Authorization': f'Bearer {user_token}',
'X-Institution-Uuid': institution_uuid,
})
print(r.json())
Response 200
application/json Copy
{
"institution": { "uuid": "…", "name": "Lagos High" },
"enrolled": [ { "uuid": "…", "title": "SS1 Biology", "…" : "…" } ],
"owned": [ ],
"is_owner": false
}
GET /tribes/{uuid}Full class payload (modules + published materials + progress)
POST /tribes/{uuid}/materials/{material}/completeMark a text lesson complete → { success, progress, material_id }
POST /tribes/{uuid}/materials/{material}/quizSubmit quiz answers
POST /tribes/{uuid}/materials/{material}/assignmentSubmit assignment (text and/or file)
POST
/product/v1/tribes/{uuid}/materials/{material}/quiz
Save quiz results for the signed-in learner.
scoreinteger ≥0, required
max_scoreinteger ≥0, required
needs_reviewboolean, optional
answersobject/array, optional · keyed answers
POST …/quiz
cURL
JavaScript
PHP
Python
Copy
curl -s https://www.tribepeer.com/api/product/v1/tribes/$TRIBE/materials/$MATERIAL/quiz \
-H "Authorization: Bearer $USER_TOKEN" \
-H "X-Institution-Uuid: $INSTITUTION_UUID" \
-H "Content-Type: application/json" \
-d '{
"score": 8,
"max_score": 10,
"needs_review": false,
"answers": { "0": "Leaf", "1": "Chloroplast" }
}'
await fetch(`https://www.tribepeer.com/api/product/v1/tribes/${tribeUuid}/materials/${materialUuid}/quiz`, {
method: 'POST',
headers: {
Authorization: `Bearer ${userToken}`,
'X-Institution-Uuid': institutionUuid,
'Content-Type': 'application/json',
},
body: JSON.stringify({
score: 8,
max_score: 10,
needs_review: false,
answers: { '0': 'Leaf', '1': 'Chloroplast' },
}),
}).then(r => r.json())
$data = Http::withToken($userToken)
->withHeaders(['X-Institution-Uuid' => $institutionUuid])
->post("https://www.tribepeer.com/api/product/v1/tribes/{$tribeUuid}/materials/{$materialUuid}/quiz", [
'score' => 8,
'max_score' => 10,
'needs_review' => false,
'answers' => ['0' => 'Leaf', '1' => 'Chloroplast'],
])->json();
r = requests.post(
f'https://www.tribepeer.com/api/product/v1/tribes/{tribe_uuid}/materials/{material_uuid}/quiz',
headers={
'Authorization': f'Bearer {user_token}',
'X-Institution-Uuid': institution_uuid,
},
json={
'score': 8,
'max_score': 10,
'answers': {'0': 'Leaf', '1': 'Chloroplast'},
},
)
print(r.json())
Response 200
application/json
{
"message": "Quiz results saved.",
"submission": {
"score": 8,
"max_score": 10,
"score_pct": 80,
"needs_review": false,
"answers_revealed": false
}
}
Cohort chat
GET /chat/threadsThreads for the school community
GET /chat/threads/{thread}/messagesLatest messages
POST /chat/threads/{thread}/messagesbody: { "body": "…" }
Threads response shape
application/json
{
"community": { "uuid": "…", "name": "…" },
"threads": [
{ "uuid": "…", "name": "General", "is_default": true, "message_count": 12 }
]
}
Ask TribeMate
POST /ai/ask with { "message": "…" } (max 4000).
Returns { reply, usage }. Full quota / error docs:
AI · Learner Ask .
School manage (staff in Campus)
Same product JWT. Intended for institution owners / staff driving a Campus-style admin UI — still no Sanctum.
GET /manage/tribesClasses this staff can manage
POST /manage/tribesCreate class
PATCH /manage/tribes/{uuid}Update class
GET /manage/join-codes · POST List / create join codes
GET /manage/tribes/{uuid}/curriculumModules + materials editor payload
POST …/modules · materials CRUD · publishCurriculum writes
GET …/studentsRoster
GET …/submissionsResults inbox
GET …/submissions/quiz/{id}One quiz attempt
POST …/assignments/{submissionUuid}/gradeGrade assignment
POST …/quiz/{id}/resetAllow retake after answers revealed
Errors
401Missing / invalid product JWT
403Unverified, or Campus grant missing
402Student cap / AI quota / plan
404Tribe / thread / school not found
422Validation (join code, quiz already revealed, etc.)
429Auth / campus / ask throttles
← Organisation API
AI docs