pdf-quiz-generator/docs/api-reference.md
Daniel 2cbbfe00c3 Tag filtering, multi-category, bug fixes, image validation, docs
- Fix tag filtering (sa_text import shadowing caused UnboundLocalError)
- Add TagBrowser component with per-section search
- Multi-category selection (OR within categories, AND with tags)
- AI image validation: has_figure field in extraction prompt
- Skip known branding images by MD5 hash + dimension filters
- Fix quiz timer auto-submit (wrong useEffect dependency)
- Fix QuizResponse schema: section_id nullable
- Fix Question.quiz_id → source_quiz_id attribute name
- Fix SQL injection in quizzes.py vector search
- Add PDF processing progress steps via Redis
- Add delete user from admin panel
- Admin page: no spinner flash on data refresh
- Upload progress: axios 1.x e.progress, remove manual Content-Type
- Duplicate model error: 409 with clear message
- Backend startup: retry DDL migration on lock timeout
- Replace all silent except:pass with warning logs
- Comprehensive multi-page documentation (docs/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:48:26 +02:00

27 KiB

PedsHub API Reference

Base URL: /api

All endpoints return JSON unless noted otherwise. Authentication is via Bearer token in the Authorization header. Role hierarchy: admin > moderator > user.


Auth (/api/auth)

POST /api/auth/register

Register a new user account.

  • Auth: None
  • Rate limit: Turnstile verification required if configured
  • Request body:
    {
      "email": "string",
      "password": "string (min 8 chars)",
      "name": "string",
      "turnstile_token": "string | null"
    }
    
  • Response:
    • First user ever: {"access_token": "...", "token_type": "bearer"} (auto-admin, auto-verified)
    • Subsequent users: {"requires_verification": true, "message": "..."}
  • Notes: Registration can be disabled by admin via settings. First registered user is always admin and auto-verified.

POST /api/auth/login

Authenticate and receive a JWT token.

  • Auth: None
  • Rate limit: 10 attempts per IP per 15 minutes (Redis). Turnstile verification if configured.
  • Request body:
    {
      "email": "string",
      "password": "string",
      "turnstile_token": "string | null"
    }
    
  • Response: {"access_token": "string", "token_type": "bearer"}
  • Errors: 401 invalid credentials, 403 email not verified, 429 rate limited

GET /api/auth/verify-email

Verify a user's email address via token link.

  • Auth: None
  • Query params: token (string, required)
  • Response: {"message": "Email verified successfully! You can now log in."}
  • Errors: 400 invalid/expired token

POST /api/auth/resend-verification

Resend the email verification link.

  • Auth: None
  • Request body: {"email": "string"}
  • Response: {"message": "If that email exists, a verification link has been sent."}
  • Notes: Deliberately vague response to prevent email enumeration.

POST /api/auth/forgot-password

Request a password reset link.

  • Auth: None
  • Rate limit: 3 reset requests per email per hour
  • Request body: {"email": "string"}
  • Response: {"message": "If that email is registered, a reset link has been sent."}
  • Notes: Deliberately vague response to prevent email enumeration. Sends email in background.

POST /api/auth/reset-password

Reset password using a valid token.

  • Auth: None
  • Request body:
    {
      "token": "string",
      "new_password": "string (min 8 chars)"
    }
    
  • Response: {"message": "Password reset successfully. You can now log in."}
  • Errors: 400 invalid/expired/used token

GET /api/auth/me

Get the current authenticated user's profile.

  • Auth: User
  • Response: UserResponse{id, email, name, role, created_at}

PUT /api/auth/me

Update current user's profile (name, password).

  • Auth: User
  • Request body:
    {
      "name": "string | null",
      "current_password": "string | null (required to change password)",
      "new_password": "string | null (min 8 chars)"
    }
    
  • Response: UserResponse

GET /api/auth/me/settings

Get user settings stored in Redis (e.g. Nextcloud config).

  • Auth: User
  • Response: {} or a JSON object of user settings

PUT /api/auth/me/settings

Save user settings to Redis.

  • Auth: User
  • Request body: Any JSON object
  • Response: {"saved": true}

Documents (/api/documents)

POST /api/documents/upload

Upload a PDF document for processing.

  • Auth: Moderator
  • Content-Type: multipart/form-data
  • Request: file (PDF, max size from MAX_UPLOAD_SIZE setting)
  • Response: DocumentResponse{id, user_id, filename, original_filename, status, total_pages, error_message, uploaded_at}
  • Notes: Processing dispatched to Celery (falls back to sync). Status starts as "processing".

GET /api/documents/

List all documents. Moderators see all; regular users see only their own.

  • Auth: User
  • Response: DocumentResponse[]

GET /api/documents/{document_id}

Get a single document by ID.

  • Auth: User (own documents only; moderators can access any)
  • Response: DocumentResponse
  • Errors: 404

GET /api/documents/{document_id}/status

Poll document processing status.

  • Auth: User (own documents only; moderators can access any)
  • Response: {id, status, total_pages, error_message}

GET /api/documents/{document_id}/processing-steps

Get PDF processing progress steps from Redis.

  • Auth: User (own documents only; moderators can access any)
  • Response: {"steps": [{step object}, ...]}
  • Notes: Steps are stored in Redis list pdf:steps:{document_id}.

POST /api/documents/{document_id}/sections

Create a named page range section within a document.

  • Auth: Moderator
  • Request body:
    {
      "name": "string",
      "start_page": "int (>= 1)",
      "end_page": "int (> start_page, <= total_pages)"
    }
    
  • Response: SectionResponse{id, document_id, name, start_page, end_page}
  • Errors: 400 if document not ready, invalid page range

DELETE /api/documents/{document_id}/sections/{section_id}

Delete a section.

  • Auth: Moderator
  • Response: 204 No Content

DELETE /api/documents/{document_id}

Delete a document, its file, and its ChromaDB vector collection.

  • Auth: Moderator
  • Response: 204 No Content

Quizzes (/api/quizzes)

POST /api/quizzes/

Start async quiz extraction from a document section.

  • Auth: Moderator
  • Request body:
    {
      "section_id": "int",
      "title": "string",
      "mode": "timed | learning",
      "time_limit_minutes": "int | null",
      "model_id": "int | null (AIModelConfig.id)",
      "question_category_id": "int | null",
      "extraction_mode": "string | null (standard, questions_only, two_step, regex, ai_decide, generate)"
    }
    
  • Response: {"job_id": "uuid", "status": "pending"}
  • Notes: Poll /api/quizzes/job/{job_id} for progress. Falls back to sync if Celery unavailable.

GET /api/quizzes/jobs

List all recent extraction jobs for the current user (any browser session).

  • Auth: User
  • Response:
    [
      {
        "job_id": "uuid",
        "title": "string",
        "status": "pending | running | completed | failed | cancelled",
        "steps_count": "int",
        "last_step": "string (truncated to 80 chars)",
        "quiz_id": "int | null"
      }
    ]
    

GET /api/quizzes/job/{job_id}

Poll extraction job progress.

  • Auth: Moderator
  • Response: {"job_id", "status", "steps": [...], "quiz_id" (if completed), "error" (if failed)}

POST /api/quizzes/job/{job_id}/cancel

Cancel a running extraction job.

  • Auth: User (must own the job)
  • Response: {"status": "cancelled"}
  • Errors: 403 if not owner, 400 if not running

Hybrid semantic + keyword search across quiz titles and questions.

  • Auth: User
  • Query params:
    • q (string, required, 2-200 chars)
    • mode (string: "title" | "questions" | "all", default "all")
  • Response: Array of search results with quiz_id, quiz_title, match_type, matching_questions[]
  • Notes: Uses pgvector cosine similarity (threshold 0.30) for semantic search, plus ILIKE for keyword matching.

GET /api/quizzes/

List all quizzes. Moderators see all (non-deleted); regular users see only published.

  • Auth: User
  • Response: QuizResponse[]{id, section_id, user_id, title, questions_count, mode, time_limit_minutes, is_published, created_at}

GET /api/quizzes/{quiz_id}

Get a quiz for taking. Learning-mode quizzes include answers/explanations.

  • Auth: User
  • Query params: study (bool, default false) — forces answers/explanations to be included
  • Response: QuizDetail (timed) or QuizLearningDetail (learning/study)
  • Notes: Excludes soft-deleted quizzes.

PATCH /api/quizzes/{quiz_id}

Update quiz metadata (title).

  • Auth: Moderator
  • Request body: {"title": "string"}
  • Response: QuizResponse

POST /api/quizzes/{quiz_id}/shuffle

Return quiz with shuffled question and option order (does not modify DB).

  • Auth: User
  • Query params: shuffle_options (bool, default true)
  • Response: QuizDetail with shuffled questions

GET /api/quizzes/{quiz_id}/review

Get quiz with full answers. Requires a completed attempt (or moderator role).

  • Auth: User (must have completed attempt) or Moderator
  • Response: QuizReview

GET /api/quizzes/{quiz_id}/questions

Get all questions with answers for editing.

  • Auth: Moderator
  • Response: Array of {id, question_text, question_type, options, correct_answer, explanation}

PATCH /api/quizzes/{quiz_id}/questions/{question_id}

Update a question. Changes are shared across all quizzes referencing this question.

  • Auth: Moderator
  • Request body: Any of {question_text, options, correct_answer, explanation, question_type}
  • Response: Updated question object
  • Errors: 400 if correct_answer does not match one of the options

DELETE /api/quizzes/{quiz_id}/questions/{question_id}

Remove a question from this quiz. If shared with other quizzes, only the link is removed. If exclusive, the question is deleted from the bank.

  • Auth: Moderator
  • Response: 204 No Content

PATCH /api/quizzes/{quiz_id}/publish

Show or hide a quiz for regular users.

  • Auth: Moderator
  • Query params: published (bool, default true)
  • Response: {"quiz_id": int, "is_published": int}

GET /api/quizzes/trash

List soft-deleted quizzes.

  • Auth: Moderator
  • Response: QuizResponse[]

DELETE /api/quizzes/{quiz_id}

Soft-delete a quiz (move to trash).

  • Auth: Moderator
  • Response: 204 No Content

PATCH /api/quizzes/{quiz_id}/restore

Restore a soft-deleted quiz from trash.

  • Auth: Moderator
  • Response: QuizResponse

DELETE /api/quizzes/{quiz_id}/permanent

Permanently delete a quiz that is already in trash. Exclusive questions are orphaned to the bank.

  • Auth: Moderator
  • Response: 204 No Content
  • Errors: 400 if quiz is not in trash

Questions (/api/questions)

GET /api/questions/bank

List all questions across all quizzes with filtering, search, and pagination.

  • Auth: User
  • Query params:
    • q (string, optional) — keyword/semantic search
    • quiz_id (int, optional) — filter by source quiz
    • category_id (int, optional) — filter by question category
    • category_ids (string, optional) — comma-separated category IDs (OR filter)
    • uncategorized (bool, default false)
    • favorites_only (bool, default false)
    • tag_ids (string, optional) — comma-separated tag IDs (AND filter)
    • search_mode (string: "keyword" | "semantic" | "hybrid", default "hybrid")
    • limit (int, default 50, max 200)
    • offset (int, default 0)
  • Response:
    {
      "total": "int",
      "questions": [
        {
          "id": "int",
          "quiz_id": "int",
          "quiz_title": "string",
          "question_category_id": "int | null",
          "question_category_name": "string | null",
          "question_text": "string",
          "question_type": "string",
          "options": ["string"],
          "correct_answer": "string",
          "explanation": "string",
          "image_path": "string | null"
        }
      ]
    }
    
  • Notes: Hybrid search returns semantic matches first (pgvector cosine similarity >= 0.55), then keyword matches.

GET /api/questions/bank/ids

Return just question IDs for all matching questions (for server-side select-all).

  • Auth: User
  • Query params: Same filters as /bank (except limit, offset, search_mode)
  • Response: [int]

PATCH /api/questions/{question_id}/category

Assign or remove a question category.

  • Auth: Moderator
  • Query params: category_id (int | null)
  • Response: {"question_id": int, "question_category_id": int | null}

POST /api/questions/from-bank

Create a new quiz by referencing existing bank questions (no copying -- edits propagate).

  • Auth: Moderator
  • Request body:
    {
      "title": "string",
      "question_ids": [1, 2, 3],
      "mode": "timed | learning",
      "time_limit_minutes": "int | null"
    }
    
  • Response: {"id": int, "title": "string", "questions_count": int}

POST /api/questions/bulk-category

Assign or remove a category from multiple questions at once.

  • Auth: Moderator
  • Request body:
    {
      "question_ids": [1, 2, 3],
      "category_id": "int | null"
    }
    
  • Response: {"updated": int, "question_category_id": int | null}

Attempts (/api/attempts)

POST /api/attempts/start

Start a new quiz attempt (or resume the most recent incomplete one).

  • Auth: User
  • Query params:
    • quiz_id (int, required)
    • fresh (bool, default false) — force a new attempt even if one is in progress
  • Response: AttemptResponse{id, quiz_id, score, total_questions, percentage, started_at, completed_at}

POST /api/attempts/{attempt_id}/submit

Submit answers for a quiz attempt.

  • Auth: User (must own the attempt)
  • Request body:
    {
      "answers": [
        {"question_id": "int", "user_answer": "string"}
      ]
    }
    
  • Response: AttemptDetail — includes answers[] with full review (question text, options, user answer, correct answer, is_correct, explanation)
  • Notes: Updates spaced repetition reminder schedule. Clears saved progress from Redis. Unanswered questions are marked incorrect.

GET /api/attempts/

List all attempts for the current user.

  • Auth: User
  • Query params: quiz_id (int, optional)
  • Response: AttemptResponse[]

GET /api/attempts/{attempt_id}

Get a specific attempt with full answer details.

  • Auth: User (must own the attempt)
  • Response: AttemptDetail

GET /api/attempts/stats/dashboard

Get dashboard statistics for the current user.

  • Auth: User
  • Response:
    {
      "total_documents": "int",
      "total_quizzes": "int (distinct quizzes attempted)",
      "total_attempts": "int",
      "average_score": "float",
      "quiz_stats": [
        {
          "quiz_id": "int",
          "quiz_title": "string",
          "attempts_count": "int",
          "best_score": "float",
          "latest_score": "float",
          "average_score": "float"
        }
      ]
    }
    

GET /api/attempts/history

Get per-quiz attempt history for the performance line graph.

  • Auth: User
  • Response:
    [
      {
        "quiz_id": "int",
        "title": "string",
        "attempts": [
          {"attempt_id": "int", "date": "ISO string", "percentage": "float", "score": "int", "total": "int"}
        ]
      }
    ]
    

GET /api/attempts/in-progress

Return all incomplete (not yet submitted) attempts for the current user.

  • Auth: User
  • Response:
    [
      {
        "attempt_id": "int",
        "quiz_id": "int",
        "quiz_title": "string",
        "total_questions": "int",
        "started_at": "ISO string"
      }
    ]
    

GET /api/attempts/{attempt_id}/in-progress

Return the latest incomplete attempt for a specific quiz.

  • Auth: User
  • Query params: quiz_id (int, required)
  • Response: AttemptResponse | null

POST /api/attempts/progress

Save in-progress quiz answers to Redis (survives logout/browser change).

  • Auth: User
  • Headers: x-quiz-session (optional, for device session lock)
  • Request body:
    {
      "quiz_id": "int",
      "attempt_id": "int",
      "answers": {"question_id": "answer"},
      "current_idx": "int",
      "mode": "string",
      "voice": "string | null",
      "time_left": "int | null",
      "started_at": "string | null (ISO timestamp)",
      "total_time": "int | null (seconds)"
    }
    
  • Response: {"saved": true}
  • Notes: Progress expires after 7 days. Session lock (30s TTL) prevents concurrent resume on another device.

GET /api/attempts/progress

Retrieve saved progress for a quiz. Auto-submits timed quizzes if timer expired.

  • Auth: User
  • Headers: x-quiz-session (optional, for device session lock)
  • Query params: quiz_id (int, required)
  • Response: Saved progress object or null
  • Errors: 409 if another device is actively using this attempt

DELETE /api/attempts/progress/{attempt_id}

Clear saved progress.

  • Auth: User
  • Response: 204 No Content

DELETE /api/attempts/{attempt_id}

Delete an attempt, its answers, and associated reminder schedule.

  • Auth: User (must own the attempt)
  • Response: 204 No Content

Tags (/api/tags)

GET /api/tags

Get all tags grouped by type (subjects, diseases, keywords), with question counts.

  • Auth: User
  • Response:
    {
      "subjects": [{"id": "int", "name": "string", "count": "int"}],
      "diseases": [{"id": "int", "name": "string", "count": "int"}],
      "keywords": [{"id": "int", "name": "string", "count": "int"}]
    }
    

POST /api/tags/classify

Trigger background AI classification of untagged questions.

  • Auth: Moderator
  • Response: {"job_id": "uuid", "status": "started"}

GET /api/tags/status/{job_id}

Check classification job progress.

  • Auth: User
  • Response: {"job_id": "string", "status": "string", "steps": [...], "error": "string | null"}

Teach (/api/teach)

GET /api/teach/models

Return available teach AI models.

  • Auth: User
  • Response: [{"id": int, "name": "string", "model_id": "string", "is_default": bool}]

POST /api/teach/chat

Send a message to the AI tutor with full question context.

  • Auth: User
  • Rate limit: 30 messages per user per 10 minutes (admins and unthrottled users exempt)
  • Request body:
    {
      "question_id": "int",
      "messages": [
        {"role": "user | assistant", "content": "string"}
      ],
      "model_id": "int | null (AIModelConfig.id)"
    }
    
  • Response: {"reply": "string (markdown)", "suggestions": ["string", "string", "string"]}
  • Notes: System prompt includes the question, correct answer, explanation, and up to 4 semantically similar questions for broader context. Follow-up suggestions are parsed from lines starting with > .
  • Errors: 503 if no teach model configured, 502 if AI temporarily unavailable

TTS (/api/tts)

GET /api/tts/voices

Return available TTS voices. Excludes Polly voices if Polly is disabled.

  • Auth: User
  • Response: [{"id": "string (model_id)", "name": "string", "is_default": bool}]

POST /api/tts/speak

Convert text to speech audio.

  • Auth: User
  • Rate limit: 60 requests per user per hour (admins and unthrottled users exempt)
  • Request body:
    {
      "text": "string (max 2000 chars)",
      "voice": "string | null (model_id override)"
    }
    
  • Response: Binary audio/mpeg data
  • Notes: Supports OpenAI TTS (tts-1:alloy), ElevenLabs (elevenlabs/<voice_id>), Google Cloud TTS (google/<voice_name>), and AWS Polly (polly/<VoiceId>).

Favorites (/api/favorites)

POST /api/favorites

Add a question to the current user's favorites.

  • Auth: User
  • Request body: {"question_id": "int"}
  • Response (201): FavoriteResponse{id, user_id, question_id, created_at}
  • Notes: Idempotent -- returns existing favorite if already favorited.

DELETE /api/favorites/{question_id}

Remove a question from favorites.

  • Auth: User
  • Response: 204 No Content

GET /api/favorites

Get list of favorited question IDs for the current user.

  • Auth: User
  • Response: [int]

Categories (/api/categories)

Quiz-level categories for organizing quizzes.

GET /api/categories/

List all quiz categories with quiz counts.

  • Auth: User
  • Response: [{"id": int, "name": "string", "quiz_count": int}]

POST /api/categories/

Create a new quiz category.

  • Auth: Moderator
  • Request body: {"name": "string"}
  • Response: CategoryResponse

DELETE /api/categories/{category_id}

Delete a quiz category. Unassigns all quizzes in the category.

  • Auth: Moderator
  • Response: 204 No Content

PATCH /api/categories/quizzes/{quiz_id}

Assign or remove a category from a quiz.

  • Auth: Moderator
  • Query params: category_id (int | null)
  • Response: {"quiz_id": int, "category_id": int | null}

Question Categories (/api/question-categories)

Question-level categories for organizing the question bank.

GET /api/question-categories/

List all question categories with question counts.

  • Auth: User
  • Response: [{"id": int, "name": "string", "description": "string | null", "question_count": int}]

POST /api/question-categories/

Create a new question category.

  • Auth: Moderator
  • Request body: {"name": "string", "description": "string | null"}
  • Response: QCatResponse

PATCH /api/question-categories/{cat_id}

Update a question category.

  • Auth: Moderator
  • Request body: {"name": "string", "description": "string | null"}
  • Response: QCatResponse

DELETE /api/question-categories/{cat_id}

Delete a question category. Optionally move its questions to another category.

  • Auth: Moderator
  • Query params: move_to (int | null) -- target category ID, or null to uncategorize
  • Response: 204 No Content

POST /api/question-categories/{cat_id}/create-quiz

Create a new quiz from all questions in a question category.

  • Auth: Moderator
  • Query params:
    • title (string, required)
    • mode (string: "timed" | "learning", default "timed")
    • time_limit_minutes (int | null)
  • Response: {"id": int, "title": "string", "questions_count": int}

Contact (/api/contact)

POST /api/contact

Submit a contact form (public, no auth).

  • Auth: None
  • Request body:
    {
      "name": "string (max 120)",
      "email": "valid email",
      "type": "question | moderator",
      "message": "string (max 2000)",
      "turnstile_token": "string | null"
    }
    
  • Response: {"success": true}
  • Notes: Stores submission in DB and emails admin. Turnstile verification if configured.

GET /api/contact/submissions

List all contact submissions.

  • Auth: Admin (enforced in endpoint body, not decorator)
  • Response: [{id, name, email, type, message, read, created_at}]

PUT /api/contact/submissions/{submission_id}/read

Mark a contact submission as read.

  • Auth: Admin (enforced in endpoint body, not decorator)
  • Response: {"success": true}

Admin (/api/admin)

User Management

GET /api/admin/users

List all users.

  • Auth: Admin
  • Response: UserResponse[]

POST /api/admin/users

Create a user directly (email auto-verified).

  • Auth: Admin
  • Request body: {"email": "string", "password": "string", "name": "string"}
  • Response: UserResponse

PUT /api/admin/users/{user_id}/role

Change a user's role.

  • Auth: Admin
  • Request body: {"role": "admin | moderator | user"}
  • Response: UserResponse
  • Errors: 400 cannot change own role

PUT /api/admin/users/{user_id}/unthrottle

Set or clear the unthrottle flag (exempt from AI/TTS rate limits).

  • Auth: Admin
  • Request body: {"unthrottled": true | false}
  • Response: UserResponse

DELETE /api/admin/users/{user_id}

Delete a user and all their data.

  • Auth: Admin
  • Response: 204 No Content
  • Errors: 400 cannot delete yourself

AI Model Configuration

GET /api/admin/models/available

List active models for a task (for users when creating/taking quizzes).

  • Auth: User
  • Query params: task (string, default "extraction")
  • Response: [{"id": int | null, "name": "string", "model_id": "string", "is_default": bool}]
  • Notes: Falls back to env LITELLM_MODEL if nothing configured.

GET /api/admin/models

List all AI model configurations.

  • Auth: Admin
  • Response: AIModelConfigResponse[] -- {id, name, model_id, task, is_active, is_default, api_key (masked)}

POST /api/admin/models

Create an AI model configuration.

  • Auth: Admin
  • Request body:
    {
      "name": "string",
      "model_id": "string",
      "task": "extraction | tts | teach | keyword",
      "is_active": "bool",
      "is_default": "bool",
      "api_key": "string | null"
    }
    
  • Response: AIModelConfigResponse
  • Notes: Auto-sets as default if first model for that task. Unique constraint on (model_id, task).

PUT /api/admin/models/{model_id}

Update an AI model configuration.

  • Auth: Admin
  • Request body: Partial AIModelConfigUpdate
  • Response: AIModelConfigResponse

DELETE /api/admin/models/{model_id}

Delete an AI model configuration.

  • Auth: Admin
  • Response: 204 No Content

POST /api/admin/models/{model_id}/test

Send a test completion to verify an LLM model is reachable.

  • Auth: Admin
  • Response: {"message": "model_id -> 'OK'"}
  • Errors: 400 for TTS models (use Preview button instead), 502 if unreachable

POST /api/admin/litellm/models

Query available models from LiteLLM proxy or OpenAI-compatible API.

  • Auth: Admin
  • Request body: {"api_key": "string | null", "api_base": "string | null"}
  • Response: {"models": ["string"], "source": "string"}

POST /api/admin/tts/voices

Discover available TTS voices from a provider.

  • Auth: Admin
  • Request body:
    {
      "provider": "elevenlabs | polly | openai",
      "api_key": "string | null",
      "region": "string | null"
    }
    
  • Response: {"voices": [{"model_id": "string", "name": "string", "labels": {}}]}
  • Notes: OpenAI returns hardcoded list of tts-1 and tts-1-hd voices.

System Settings

GET /api/admin/settings

Get system settings (registration enabled, embedding model, Polly enabled).

  • Auth: Admin
  • Response: {"registration_enabled": bool, "embedding_model": "string", "polly_enabled": bool}

PUT /api/admin/settings

Update system settings.

  • Auth: Admin
  • Request body: {"registration_enabled": bool, "embedding_model": "string", "polly_enabled": bool}
  • Response: {"success": true, "message": "Settings updated"}

Embedding Management

POST /api/admin/embedding/test

Test the currently configured embedding model.

  • Auth: Admin
  • Response: {"model": "string", "dimensions": int, "status": "ok"}

POST /api/admin/embedding/regenerate

Queue a Celery task to regenerate all question embeddings.

  • Auth: Admin
  • Response: {"job_id": "uuid", "message": "Regeneration started..."}

Nextcloud (/api/nextcloud)

WebDAV proxy for importing PDFs from Nextcloud (avoids browser CORS issues).

POST /api/nextcloud/test

Test Nextcloud credentials.

  • Auth: Moderator
  • Request body: {"server": "string", "username": "string", "password": "string", "path": "/"}
  • Response: {"ok": true, "message": "Connected successfully"}

POST /api/nextcloud/files

List PDFs and folders at a given Nextcloud path.

  • Auth: Moderator
  • Request body: {"server": "string", "username": "string", "password": "string", "path": "/"}
  • Response: {"path": "string", "items": [{"name", "path", "type": "dir | pdf", "size"}]}

POST /api/nextcloud/download

Download a PDF from Nextcloud and stream it back.

  • Auth: Moderator
  • Request body: {"server": "string", "username": "string", "password": "string", "path": "/some/file.pdf"}
  • Response: Streamed application/pdf binary
  • Errors: 400 if not a PDF file