API Inventory
Complete inventory of all registered API endpoints in BacMR, verified against actual router implementations.
Last Updated: 2026-02-22
Base Configuration
- Base Path:
/api - Health Check:
GET /api/health(returns{"status": "BacMR Online"})
Router Registration Status
| Router | Prefix | Registered | Status |
|---|---|---|---|
| Auth | /auth |
Yes | Active |
| Me | /me |
Yes | Active |
| Chat | None | Yes | Active |
| Wallet | /wallet |
Yes | Active |
| Curriculum | /curriculum |
Yes | Active |
| Scraping | /scraping |
Yes | Active |
| Admin | /admin |
Yes | Active |
| Quiz | /quizzes |
NO | NOT REGISTERED |
Important Note: The quiz router exists (app/api/routers/quiz.py) but is NOT included in the main API router, so quiz endpoints are not available.
1. Authentication Router (/auth)
File: app/api/routers/auth.py
Prefix: /auth
Tags: ["auth"]
Auth Required: No (public endpoints)
POST /auth/signup
Create a new user account.
Request Body: SignUpRequest
Response: AuthResponse
Implementation Notes: - Uses Supabase Auth API - Sets default role to "student" - Full name stored in user metadata
POST /auth/signin
Sign in with email and password.
Request Body: SignInRequest
Response: AuthResponse
{
"success": true,
"data": {
"access_token": "string",
"refresh_token": "string",
"user": {
"id": "uuid",
"email": "string",
"role": "student"
}
},
"error": null
}
POST /auth/logout
Client-side logout endpoint.
Auth: None required
Response: AuthResponse
Implementation Notes: - Mostly client-side (token discarding) - Server doesn't invalidate session - Returns success without actual server-side logout
POST /auth/reset-password
Send password reset email.
Request Body: ResetPasswordRequest
Response: AuthResponse
2. User Profile Router (/me)
File: app/api/routers/me.py
Prefix: /me
Tags: ["user"]
Auth Required: Yes (JWT via get_current_user)
GET /me
Get current user's profile.
Auth: Required (JWT)
Response: UserProfile
{
"id": "uuid",
"email": "string",
"full_name": "string",
"role": "student",
"created_at": "2026-01-15T10:00:00Z"
}
Implementation Notes:
- Fetches from profiles table
- Falls back to JWT data if profile not found
- Includes user_id, email, full_name, role, created_at
PATCH /me
Update current user's profile.
Auth: Required (JWT)
Request Body: UserProfileUpdate
Response: UserProfile
Error Responses: - 400: No fields to update - 404: Profile not found - 500: Update failed
3. Chat Router
File: app/api/routers/chat.py
Prefix: None
Tags: ["chat"]
Auth Required: Yes (JWT)
POST /chat
Send a message and get AI teacher response.
Auth: Required (JWT)
Request Body: ChatRequest
{
"messages": [
{
"role": "user",
"content": "Explain photosynthesis"
}
],
"language": "fr", // "fr" or "ar"
"stream": true, // default: true
"persona_id": "uuid", // optional: Custom instruction set placeholder
"grade": "10", // optional soft hint
"subject": "Biology", // optional soft hint
"namespace": "grade-10-biology" // optional soft hint
}
Note on Retrieval: grade, subject, and namespace are optional soft hints. If omitted, the system performs a broad search across the entire curriculum and uses profile context to boost relevant results without strictly filtering them out.
Response (stream=false): ChatResponse
{
"answer": "string",
"retrieval_query": "string",
"sources": [
{
"pdf_source": "string",
"page_number": 42,
"score": 0.95
}
]
}
Response (stream=true): Server-Sent Events (SSE)
Event types:
- metadata: {"retrieval_query": "...", "language": "fr"}
- token: Individual text tokens
- sources: Array of source objects
- done: "[DONE]"
- error: Error message
Implementation Notes: - Uses teacher_agent graph for processing - Supports streaming and non-streaming modes - Performs RAG retrieval from Pinecone - Handles clarification requests - Deducts tokens from wallet after completion - Creates reservations for token usage
Error Responses: - 422: Empty messages list - 500: Internal server error
4. Wallet Router (/wallet)
File: app/api/routers/wallet.py
Prefix: /wallet
Tags: ["wallet"]
User Endpoints (Auth Required)
GET /wallet/balance
Get current user's wallet balance and tier.
Auth: Required (JWT)
Response: WalletBalanceResponse
{
"user_id": "uuid",
"token_balance": 1000,
"subscription_tier": "free",
"pending_reservations": 2
}
Implementation Notes:
- Fetches from wallet table
- Counts pending reservations (status="reserved")
Error Responses: - 404: Wallet not found - 500: Database error
GET /wallet/reservations
List user's token reservations.
Auth: Required (JWT)
Query Parameters:
- status (optional): Filter by status
Response: list[ReservationResponse]
[
{
"id": "uuid",
"user_id": "uuid",
"estimated": 100,
"actual": 85,
"status": "finalized",
"request_id": "uuid",
"created_at": "2026-01-15T10:00:00Z",
"finalized_at": "2026-01-15T10:01:00Z",
"expires_at": "2026-01-15T10:10:00Z",
"balance_after_reserve": 915,
"refunded": 15
}
]
Implementation Notes: - Returns last 50 reservations - Ordered by created_at (descending)
Admin Endpoints (Admin Auth Required)
POST /wallet/topup
Top up a student's wallet with tokens.
Auth: Admin only
Tags: ["admin"]
Request Body: TopUpRequest
{
"user_id": "uuid",
"token_amount": 500,
"amount_cents": 5000,
"currency": "MRU",
"payment_method": "cash",
"payment_ref": "RECEIPT-123",
"description": "Monthly subscription"
}
Response: TopUpResponse
{
"transaction_id": "uuid",
"user_id": "uuid",
"token_amount": 500,
"new_balance": 1500,
"amount_cents": 5000,
"currency": "MRU",
"status": "completed"
}
Error Responses: - 404: User not found - 500: Top-up failed
POST /wallet/transactions/expense
Log a system expense.
Auth: Admin only
Tags: ["admin"]
Request Body: ExpenseRequest
{
"type": "openai_api",
"amount_cents": 250,
"currency": "USD",
"description": "GPT-4 API usage",
"payment_method": "api_key",
"payment_ref": "INV-123",
"metadata": {"model": "gpt-4o", "tokens": 1000}
}
Response: TransactionResponse
GET /wallet/transactions
List all financial transactions with filters.
Auth: Admin only
Tags: ["admin"]
Query Parameters:
- direction (optional): "credit" or "debit"
- type (optional): Transaction type
- user_id (optional): Filter by user
- limit (default: 50, max: 100)
- offset (default: 0)
Response: TransactionListResponse
Internal Endpoints (Admin Auth Required)
POST /wallet/internal/reserve
Manually create a token reservation.
Auth: Admin only
Tags: ["internal"]
Request Body: ReservationCreate
Response: ReservationResponse
Error Responses: - 400: Invalid request
POST /wallet/internal/finalize
Manually finalize a token reservation.
Auth: Admin only
Tags: ["internal"]
Request Body: ReservationFinalize
Response: ReservationResponse
Error Responses: - 400: Invalid request
5. Curriculum Router (/curriculum)
File: app/api/routers/curriculum.py
Prefix: /curriculum
Tags: ["curriculum"]
Auth Required: No (public endpoints)
GET /curriculum/subjects
Get list of available subjects.
Auth: None
Response: SubjectsResponse
Implementation Notes: - Returns distinct subjects from documents with status="ready" - Sorted alphabetically
GET /curriculum/textbooks
Get available textbooks with optional filters.
Auth: None
Query Parameters:
- grade (optional): Filter by grade
- subject (optional): Filter by subject
- language (optional): Filter by language
Response: TextbookListResponse
{
"levels": ["10", "11", "12"],
"textbooks": [
{
"id": "uuid",
"title": "Biology Textbook",
"pdf_source": "https://...",
"grade": "10",
"subject": "Biology",
"major": "Sciences",
"language": "fr",
"weight": 1,
"category": "textbook",
"page_count": 250,
"chunk_count": 1500,
"namespace": "grade-10-biology"
}
]
}
Implementation Notes: - Returns only documents with status="ready" - Ordered by grade and subject
GET /curriculum/textbooks/{textbook_id}
Get details for a single textbook.
Auth: None
Path Parameters:
- textbook_id: Document ID
Response: TextbookDetailResponse
{
"document": {
"id": "uuid",
"title": "Biology Textbook",
...
},
"page_range": {
"min": 1,
"max": 250
}
}
Implementation Notes: - Fetches page range from chunks table - Returns null for page_range if no chunks found
Error Responses: - 404: Textbook not found
6. Scraping Router (/scraping)
File: app/api/routers/scraping.py
Prefix: /scraping
Tags: ["scraping"]
GET /scraping/sources
Get list of supported scraping sources.
Auth: None
Response: SourcesResponse
Implementation Notes: - Currently only supports "koutoubi"
POST /scraping/{source}/sync
Sync references from a source.
Auth: Admin only
Path Parameters:
- source: Source name (e.g., "koutoubi")
Response: ScrapeSyncResponse
{
"run_id": "uuid",
"status": "success",
"found_count": 50,
"new_count": 10,
"updated_count": 40,
"error_count": 0
}
Implementation Notes: - Creates scrape_run record - Executes scraper (KoutoubiScraper for "koutoubi") - Upserts references with last_seen_at timestamp - Returns summary of scraping results
Error Responses: - 404: Source not supported
GET /scraping/{source}/runs
Get scraping run history.
Auth: None
Path Parameters:
- source: Source name
Query Parameters:
- limit (default: 20)
Response: list[ScrapeRunOut]
[
{
"id": "uuid",
"source": "koutoubi",
"status": "success",
"started_at": "2026-01-15T10:00:00Z",
"finished_at": "2026-01-15T10:05:00Z",
"found_count": 50,
"new_count": 10,
"updated_count": 40,
"error_count": 0,
"error_summary": null
}
]
Error Responses: - 404: Source not supported
GET /scraping/{source}/references
Get scraped references for a source.
Auth: None
Path Parameters:
- source: Source name
Query Parameters:
- status (optional): Filter by status ("discovered", "ready", "failed")
- limit (default: 50)
- offset (default: 0)
Response: PaginationOut
Implementation Notes: - "not_parsed" status is mapped to "discovered" - Ordered by last_seen_at (descending)
Error Responses: - 404: Source not supported
7. Admin Router (/admin)
File: app/api/routers/admin.py
Prefix: /admin
Tags: ["admin"]
Auth Required: Yes (Admin role via require_admin)
Auth Testing
GET /admin/test-role
Test admin authentication.
Auth: Admin only Response:
User Management
GET /admin/users
List all users.
Auth: Admin only
Query Parameters:
- limit (default: 50)
- offset (default: 0)
Response: UserListResponse
POST /admin/users
Create a new user.
Auth: Admin only
Request Body: UserCreateRequest
Response: UserProfile
Implementation Notes: - Uses Supabase Auth admin API - Sets email_confirm to true (no email verification needed) - Profile auto-created by database trigger
Error Responses: - 400: User creation failed
PATCH /admin/users/{user_id}/role
Update a user's role.
Auth: Admin only
Path Parameters:
- user_id: User ID
Request Body: UserUpdateRole
Response: UserProfile
Error Responses: - 400: Invalid role - 404: User not found - 500: Update failed
PATCH /admin/users/{user_id}/hint-level
Update a user's hint level.
Auth: Admin only
Path Parameters:
- user_id: User ID
Query Parameters:
- hint_level (required): Integer 1-5
Response: UserProfile
Error Responses: - 404: User not found - 500: Update failed
DELETE /admin/users/{user_id}
Delete a user.
Auth: Admin only
Path Parameters:
- user_id: User ID
Response:
Implementation Notes: - Deletes from both profiles table and Supabase Auth
Error Responses: - 500: Deletion failed
POST /admin/users/{user_id}/reset-password
Reset a user's password.
Auth: Admin only
Path Parameters:
- user_id: User ID
Request Body: UserResetPasswordRequest
Response:
Error Responses: - 500: Reset failed
Dashboard & Monitoring
GET /admin/references
List all references.
Auth: Admin only
Query Parameters:
- status (optional): Filter by status
- limit (default: 50)
- offset (default: 0)
Response: Array of reference objects
GET /admin/scrape-runs
List all scrape runs.
Auth: Admin only
Query Parameters:
- limit (default: 20)
- offset (default: 0)
Response: Array of scrape run objects
GET /admin/stats
Get dashboard statistics.
Auth: Admin only Response:
{
"documents": {
"total": 50,
"ready": 45,
"processing": 3,
"failed": 2
},
"chunks": {
"total": 25000
},
"references": {
"total": 100,
"discovered": 50,
"ready": 45
},
"users": {
"total": 150,
"admins": 3,
"students": 147
},
"jobs": {
"queued": 5,
"completed": 95,
"failed": 2
}
}
Document Management
PATCH /admin/documents/{document_id}
Update document metadata.
Auth: Admin only
Path Parameters:
- document_id: Document ID
Query Parameters (at least one required):
- title (optional): Update title
- major (optional): Update major
- weight (optional): Update weight
- category (optional): Update category
Response: Updated document object
Error Responses: - 400: No fields to update - 404: Document not found
DELETE /admin/documents/{document_id}
Delete a document and its associated data.
Auth: Admin only
Path Parameters:
- document_id: Document ID
Response:
Implementation Notes: - Deletes Pinecone vectors - Deletes chunks from database - Resets reference status to "discovered" if linked - Deletes document record
Error Responses: - 404: Document not found - 500: Deletion failed
Ingestion Management
POST /admin/ingest/{reference_id}
Create an ingestion job for a reference.
Auth: Admin only
Path Parameters:
- reference_id: Reference ID
Query Parameters:
- force (default: false): Re-ingest if already processed
Response:
Implementation Notes:
- Creates job in "queued" status
- Does not start processing automatically
- Use /admin/jobs/dispatch to process jobs
Error Responses: - 404: Reference not found
POST /admin/jobs/dispatch
Dispatch the oldest queued job for processing.
Auth: Admin only Response:
Implementation Notes: - Picks oldest queued job - Runs processing in background - Job goes through states: queued → parsing → tokenizing → embedding_request_sent → embedding_upserted → ready - Returns immediately, processing continues in background
Error Responses:
- Returns {"status": "no_jobs", "message": "..."} if no queued jobs
GET /admin/jobs
List ingestion jobs.
Auth: Admin only
Query Parameters:
- status (optional): Filter by status
- limit (default: 50)
- offset (default: 0)
Response: IngestionJobListResponse
GET /admin/jobs/{job_id}
Get details for a single job.
Auth: Admin only
Path Parameters:
- job_id: Job ID
Response: IngestionJobOut
Error Responses: - 404: Job not found
POST /admin/jobs/{job_id}/requeue
Requeue a failed job for retry.
Auth: Admin only
Path Parameters:
- job_id: Job ID
Response:
Implementation Notes: - Only works on failed jobs - Increments retry_count - Checks against max_retries (default: 3) - Clears error_message
Error Responses: - 400: Job not failed or max retries exceeded - 404: Job not found
Legacy Upload
POST /admin/upload-curriculum
Manually upload a PDF curriculum file.
Auth: Admin only
Request: Multipart form data
- file: PDF file (required)
- grade: Grade level (required)
- subject: Subject name (required)
- language: Language code (default: "fr")
- namespace: Custom namespace (optional)
- pdf_source: Source URL (optional)
Response:
{
"status": "success",
"namespace": "grade-10-biology",
"pages_extracted": 250,
"chunks_created": 1500,
"vectors_upserted": 1500
}
Implementation Notes: - Synchronous processing (not job-based) - Extracts text, chunks, embeds, and upserts to Pinecone - Creates document record with status="ready" - Does not create reference record
Error Responses: - 400: Not a PDF or text extraction failed - 500: Processing failed
POST /admin/vector-embedding
Legacy endpoint for triggering ingestion.
Auth: Admin only
Request Body: VectorEmbeddingRequest
Response: VectorEmbeddingResponse
Implementation Notes:
- Wrapper around /admin/ingest/{reference_id}
- Creates job but doesn't dispatch it
8. Quiz Router (NOT REGISTERED)
File: app/api/routers/quiz.py
Prefix: /quizzes
Tags: ["quizzes"]
Status: NOT REGISTERED IN MAIN API ROUTER
Important: This router exists but is not included in app/api/router.py, so these endpoints are not available in the API.
POST /quizzes/generate (UNAVAILABLE)
Status: STUB IMPLEMENTATION
Generate a quiz on a specific topic.
Auth: Required (JWT)
Request Body: QuizGenerateRequest
{
"topic": "Photosynthesis",
"grade": "10",
"subject": "Biology",
"num_questions": 5,
"language": "fr"
}
Response: QuizResponse (stub)
{
"quiz_id": "uuid",
"topic": "Photosynthesis",
"grade": "10",
"subject": "Biology",
"language": "fr",
"questions": [
{
"question": "Sample question about Photosynthesis?",
"options": ["Option A", "Option B", "Option C", "Option D"],
"correct": "A",
"explanation": "This is a stub response. Full implementation pending.",
"source_page": 1
}
],
"num_questions": 5,
"tokens_used": 200,
"reservation_id": "uuid",
"request_id": "string",
"sources": [{"chunk_id": "example-chunk", "page_number": 1, "file_id": "example-file"}]
}
Implementation Status: Stub only - returns sample data, no actual quiz generation
GET /quizzes/health (UNAVAILABLE)
Health check for quiz service.
Response:
Non-Existent Endpoints
The following endpoints do NOT exist despite being mentioned in some documentation:
Metrics Endpoints (DO NOT EXIST)
- ❌
GET /metrics/system- Not implemented - ❌
GET /metrics/usage- Not implemented - ❌
GET /metrics/performance- Not implemented
Quiz Endpoints (NOT REGISTERED)
- ❌
POST /quizzes/generate- Router exists but not registered - ❌
GET /quizzes/health- Router exists but not registered
Authentication Patterns
Public Endpoints (No Auth)
- All
/auth/*endpoints - All
/curriculum/*endpoints /scraping/sources/scraping/{source}/runs/scraping/{source}/referencesGET /api/health
User Endpoints (JWT Required)
- All
/me/*endpoints POST /chat- All
/wallet/*user endpoints
Admin Endpoints (Admin Role Required)
- All
/admin/*endpoints /wallet/topup/wallet/transactions/*/wallet/internal/*POST /scraping/{source}/sync
Authentication Methods
- JWT Authentication (
get_current_user): - Validates JWT token from Authorization header
- Returns user dict with id, email, role, token
-
Used for all user endpoints
-
Admin Authentication (
require_admin): - Validates JWT OR matches system admin credentials
- Checks role == "admin"
- Returns admin dict with id, role, method
- Used for all admin endpoints
Common Response Patterns
Error Responses
All endpoints may return:
- 400 Bad Request: Invalid input, missing required fields
- 401 Unauthorized: Missing or invalid authentication
- 403 Forbidden: Insufficient permissions
- 404 Not Found: Resource not found
- 422 Unprocessable Entity: Validation error
- 500 Internal Server Error: Server error
Pagination Pattern
Endpoints supporting pagination use:
- limit: Results per page (typically max 50-100)
- offset: Starting position (0-based)
- Returns total count when available
Streaming Pattern
Chat endpoint supports SSE streaming:
- Content-Type: text/event-stream
- Events: metadata, token, sources, done, error
- Keep-alive headers for connection stability
Schema References
All request/response schemas are defined in:
- app/schemas/auth.py - Auth schemas
- app/schemas/chat.py - Chat schemas
- app/schemas/user.py - User schemas
- app/models/billing.py - Wallet/billing schemas
- app/schemas/curriculum.py - Curriculum schemas
- app/schemas/scraping.py - Scraping schemas
- app/schemas/admin.py - Admin schemas
- app/schemas/ingestion.py - Ingestion schemas