Skip to content

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

{
  "email": "user@example.com",
  "password": "string",
  "full_name": "string" // optional
}

Response: AuthResponse

{
  "success": true,
  "data": {
    "user_id": "uuid",
    "email": "string"
  },
  "error": null
}

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

{
  "email": "user@example.com",
  "password": "string"
}

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

{
  "success": true,
  "data": {
    "message": "Logged out successfully"
  }
}

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

{
  "email": "user@example.com"
}

Response: AuthResponse

{
  "success": true,
  "data": {
    "message": "Password reset email sent"
  }
}


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

{
  "full_name": "string" // optional
}

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

{
  "items": [...],
  "total": 150,
  "limit": 50,
  "offset": 0
}

Internal Endpoints (Admin Auth Required)

POST /wallet/internal/reserve

Manually create a token reservation.

Auth: Admin only Tags: ["internal"] Request Body: ReservationCreate

{
  "user_id": "uuid",
  "estimated": 100,
  "request_id": "uuid"
}

Response: ReservationResponse

Error Responses: - 400: Invalid request

POST /wallet/internal/finalize

Manually finalize a token reservation.

Auth: Admin only Tags: ["internal"] Request Body: ReservationFinalize

{
  "reservation_id": "uuid",
  "actual": 85
}

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

{
  "subjects": ["Math", "Physics", "Biology"],
  "total": 3
}

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

{
  "sources": ["koutoubi"]
}

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

{
  "limit": 50,
  "offset": 0,
  "items": [...]
}

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:

{
  "status": "authorized",
  "is_admin": true,
  "method": "jwt",
  "user_id": "uuid",
  "role": "admin"
}

User Management

GET /admin/users

List all users.

Auth: Admin only Query Parameters: - limit (default: 50) - offset (default: 0)

Response: UserListResponse

{
  "users": [...],
  "total_count": 150
}

POST /admin/users

Create a new user.

Auth: Admin only Request Body: UserCreateRequest

{
  "email": "user@example.com",
  "password": "string",
  "full_name": "John Doe",
  "role": "student"
}

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

{
  "role": "admin"  // "admin", "student", or "teacher"
}

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:

{
  "status": "deleted",
  "user_id": "uuid"
}

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

{
  "new_password": "string"
}

Response:

{
  "status": "password_reset",
  "user_id": "uuid"
}

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:

{
  "status": "deleted",
  "document_id": "uuid",
  "chunks_removed": 1500
}

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:

{
  "status": "queued",
  "job_id": "uuid",
  "reference_id": "uuid"
}

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:

{
  "status": "dispatched",
  "job_id": "uuid",
  "reference_id": "uuid"
}

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

{
  "items": [...],
  "total": 100
}

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:

{
  "status": "requeued",
  "job_id": "uuid",
  "retry_count": 2
}

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

{
  "reference_id": "uuid",
  "force": false
}

Response: VectorEmbeddingResponse

{
  "status": "queued",
  "reference_id": "uuid",
  "message": null
}

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:

{
  "status": "ok",
  "service": "quiz",
  "version": "1.0.0"
}


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}/references
  • GET /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

  1. JWT Authentication (get_current_user):
  2. Validates JWT token from Authorization header
  3. Returns user dict with id, email, role, token
  4. Used for all user endpoints

  5. Admin Authentication (require_admin):

  6. Validates JWT OR matches system admin credentials
  7. Checks role == "admin"
  8. Returns admin dict with id, role, method
  9. 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