Skip to main content
Quiz / Exam JSON & ZIP export, bulk question import
This file documents the bulk import / export API surface for Quizzes and Exams — extracted from the main Quizzes & Exams endpoints page so it can be consumed on its own. It covers the export of single quizzes/exams to JSON, bulk export of every quiz or exam to a ZIP archive, and the destructive question/option import (with images). All endpoints are served by the Export_Import_Quizzes_Exams_Controller. For the data model, the quiz/exam CRUD endpoints, and authoring questions through the API, see Quizzes & Exams endpoints. The student attempt lifecycle (start / finish / save score / show answers) is documented in Quizzes & Exams progress tracking.

Table of Contents

  1. Overview
  2. Base URL and Headers
  3. Export / Import Endpoints
    • 3.1 Export Quiz JSON
    • 3.2 Import Questions & Options (with images)
    • 3.3 Export Exam JSON
    • 3.4 Export All Exams ZIP
    • 3.5 Export All Quizzes ZIP
  4. Business Rules & Edge Cases
  5. History Tracking Actions
  6. Related Files

1. Overview

A Quiz is a container of questions. There is one Eloquent model and one quizzes table behind everything here — what a row is depends entirely on whether its training_id and chapter_id foreign keys are set. There is no type column and no separate “exam” table. This distinction decides which export endpoint you use:
  • Quiz (chapter quiz or training/final quiz) — exported via the /api/quizzes/... routes (§3.1, §3.5). Quiz JSON files additionally carry a belongs_to block naming the parent training/chapter.
  • Exam (standalone, both FKs null) — exported via the /api/exams/... routes (§3.3, §3.4).
Passing an exam id to a quiz export route (or vice-versa) returns 404 — each endpoint filters by its own scope (isQuiz() / isExam()). The question/option import (§3.2) is shared by both: it targets any quizzes row by id and destructively replaces its questions and options.

2. Base URL and Headers

Base API URL:
Suggested Postman variables:
Headers:
company_id must be passed explicitly on most endpoints and is validated against the companies table. App\Http\Middleware\CompanyPolicyMiddleware enforces that the value matches the authenticated user’s own company_id (super admins bypass this check).

3. Export / Import Endpoints

Controller: app/Http/Controllers/Export_Import_Quizzes_Exams_Controller.php.

3.1 Export Quiz JSON

GET /api/quizzes/{quizId}/export-json Authorize: view on the quiz. Only isQuiz() rows (linked to training/chapter) for the authenticated user’s company_id are exported. Passing an exam id returns 404. Example Request:
No request body needed. The response is a file download (not a JSON object in the response body). Response: streamed application/json file download.
  • Filename format: {slug(label)}-quiz-{Y-m-d-H-i}.json
  • Example filename: chapter-1-final-quiz-2026-05-16-14-30.json
The downloaded JSON file structure:
belongs_to (quiz exports only). Every exported quiz JSON includes a belongs_to block naming the parent training and/or chapter the quiz lives under — so the file is self-describing even outside the database. Resolution:
  • Quiz linked directly to a training (training_id set) → belongs_to.training is filled, belongs_to.chapter is null.
  • Quiz linked to a chapter (chapter_id set) → belongs_to.chapter is filled, and belongs_to.training is resolved through the chapter (chapter.training_id), even when the quiz row itself has no training_id.
  • Each entry exposes id, label, and (for training) slug; chapter also exposes its training_id. This block is not present in exam exports (§3.3 / §3.4) — exams are standalone and belong to no training/chapter.
Error Responses:

3.2 Import Questions & Options (with images)

POST /api/quizzes/{quizId}/import-questions-images Content-Type: multipart/form-data — in Postman, set Body → form-data. Form-Data Fields: Postman Setup:
  1. Set method to POST, URL to {{base_url}}/api/quizzes/5/import-questions-images
  2. Under Headers: add Authorization: Bearer {{token}} and Accept: application/json (do not manually set Content-Type for form-data — Postman sets it automatically with the boundary)
  3. Under Body, select form-data
  4. Add row: Key = company_id, Type = Text, Value = 1
  5. Add row: Key = questions_file, Type = File, Value = select your .json file
JSON File Content — save as questions_import.json and upload:
image_url — when provided and not null, the server downloads the image via HTTP and registers it as a File record. The URL must be publicly reachable from the server. Set to null to skip image import for a question.
Behavior:
  1. Authorize update on the quiz.
  2. Verify company_id matches the quiz’s company_id.
  3. Destructive replace — deletes all existing questions and options on the quiz.
  4. Creates questions, options, and option items (drag & drop only) from the file.
  5. Downloads any image_url values, stores files, and links them to the question.
  6. History: imported_questions_with_images.
Success Response (200):
Error Responses:

3.3 Export Exam JSON

GET /api/exams/export-json Both query params are required. The quiz must pass the isExam() scope (training_id = null and chapter_id = null). Passing a quiz (non-exam) id returns 404. Query Parameters: Example Request:
Response: application/json file download.
  • Filename format: {slug(label)}-exam-{d-m-Y-H-i}.json
  • Example filename: advanced-python-exam-16-05-2026-14-30.json
  • File content has the same structure as §3.1, minus the belongs_to block (exams are standalone and belong to no training/chapter).
Error Responses:

3.4 Export All Exams ZIP

GET /api/exams/export-all-json Exports every isExam() row for the company as a single ZIP archive, with one JSON file per exam. Query Parameters: Example Request:
Response: ZIP file download.
  • ZIP filename format: exams_export_{d-m-Y-H-i}.zip
  • Each exam inside: {slug(label)}-exam-{d-m-Y-H-i}.json
Error Responses:
Large companies should expect this to be heavy. Make sure your server’s php.ini allows large uploads: upload_max_filesize = 300M, post_max_size = 300M, memory_limit = 512M, max_execution_time = 600.

3.5 Export All Quizzes ZIP

GET /api/quizzes/export-all-json Exports every isQuiz() row for the company (i.e. quizzes linked to a training and/or a chapter — not standalone exams) as a single ZIP archive, with one JSON file per quiz. This is the quiz-side counterpart of §3.4 (Export All Exams ZIP). Query Parameters: Example Request:
Response: ZIP file download.
  • ZIP filename format: quizzes_export_{d-m-Y-H-i}.zip
  • Each quiz inside: {slug(label)}-quiz-{d-m-Y-H-i}.json
  • Each JSON file has the same structure as §3.1 — including the belongs_to block that records the parent training and/or chapter the quiz belongs to.
Error Responses:
Like §3.4, this can be heavy for large companies — give PHP enough headroom (memory_limit = 512M, max_execution_time = 600).

4. Business Rules & Edge Cases

  • Scope filtering decides 404s. Quiz export routes (/api/quizzes/...) only match isQuiz() rows; exam export routes (/api/exams/...) only match isExam() rows. Cross-using them returns 404.
  • belongs_to is quiz-only. Only quiz JSON exports (§3.1, §3.5) carry the belongs_to block. For a chapter quiz (only chapter_id set), the training is resolved through the chapter. Exam exports never include it.
  • Question import is destructive. §3.2 deletes all existing questions and options on the target quiz before re-creating them from the file — there is no merge mode. Back up via §3.1 first if needed.
  • company_id must match. The import verifies the supplied company_id equals the quiz’s own company_id (422 on mismatch), on top of the tenant CompanyPolicyMiddleware check.
  • Image import requires public URLs. image_url values are fetched server-side over HTTP; URLs unreachable from the server cause the import to fail (500).
  • Bulk ZIP exports are heavy. Both §3.4 and §3.5 build the entire archive in memory/temp before streaming; ensure PHP memory_limit / max_execution_time are generous for large catalogs.

5. History Tracking Actions

Calls to insert_in_history_table($action, $resource_id, $table) made by this controller:
The export endpoints (§3.1, §3.3, §3.4, §3.5) are read-only and do not write history rows.

  • app/Http/Controllers/Export_Import_Quizzes_Exams_Controller.php — the controller behind every endpoint in this file.
  • routes/api.php — Export_Import_Quizzes_Exams_Controller route group.
  • app/Models/Quiz.php — parent model (scopes isQuiz, isExam; training() / chapter() relations used to build belongs_to).
  • app/Models/QuizQuestion.php, QuizQuestionType.php, QuizQuestionOption.php, QuizQuestionOptionItem.php — the structures serialized into / parsed from the JSON.
  • app/Models/Training.php, app/Models/Chapter.php — sources for the belongs_to training/chapter context.
  • Quizzes & Exams endpoints — data model, quiz/exam CRUD, and question authoring.
  • Quizzes & Exams progress tracking — student attempt lifecycle.