> ## Documentation Index
> Fetch the complete documentation index at: https://theteam-13fc7f49.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Trainings Export / Import

Export trainings as JSON, bulk-download all trainings as a ZIP, import trainings from JSON files, and back up a training together with its chapters, lessons, and chapter quizzes.

Use these endpoints to move trainings between environments, snapshot them for compliance, or seed a new company catalog.

> **Heads-up:** chapters, lessons, and quizzes are **not** included in a standard training export or import. Only the **Back up a training** endpoint bundles chapters, lessons, and chapter quizzes next to the training JSON.

## Table of contents

* **Base URL & Headers**
* **The training export schema**
* **Export a single training**
* **Export all trainings (ZIP)**
* **Import trainings from JSON**
* **Back up a training**
* **Import behavior at a glance**
* **Postman setup**

***

## Base URL & Headers

```shellscript theme={null}
Base URL: {{base_url}}/api
```

```shellscript theme={null}
Accept: application/json
Authorization: Bearer {{token}}
```

All endpoints run behind `auth:sanctum`, the "user is not blocked" check, and the per-company policy middleware.

***

## The training export schema

Every per-training JSON produced by the export and bulk export endpoints follows this shape:

```json theme={null}
{
  "id": 12,
  "company_id": 1,
  "instructor_id": 5,
  "label": "Intro to PMP",
  "slug": "intro-to-pmp",
  "description": "<p>...</p>",
  "level": "Beginner",
  "duration": 120,
  "price": 199.99,
  "discounted_price": 149.99,
  "revenue_percentage": 40,
  "image_id": 22,
  "video_id": null,
  "is_public": true,
  "views_count": 42,
  "created_at": "2026-04-01 09:22:10",
  "updated_at": "2026-04-10 14:03:55",
  "archived_at": null,
  "deleted_at": null,
  "categories": ["Project Management"],
  "tags": ["PMP", "Exam Prep"],
  "instructor": {
    "id": 5,
    "first_name": "Ali",
    "last_name": "Ben",
    "email": "ali@example.com"
  },
  "image": { "id": 22, "url": "/storage/files/xxx.png" },
  "video": null,
  "average_rating": 4.5,
  "total_ratings": 12
}
```

`categories` holds the labels of the training's categories: one label since 01/11/2026 (a training has one category, see [Trainings → Assign categories](https://theteam-13fc7f49.mintlify.site/api-reference/trainings.md#assign-categories)), still as an array. Files exported earlier can list several.

***

## Export a single training

```shellscript theme={null}
GET /trainings/export-import/export/{id}
```

Downloads one training as a `.json` file. Chapters, lessons, and quizzes are **not** included.

### Query parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `company_id` | integer | Yes | The training must belong to this company; otherwise `404` is returned. |

### Example request

```shellscript theme={null}
GET {{base_url}}/api/trainings/export-import/export/12?company_id=1
Accept: application/json
Authorization: Bearer {{token}}
```

> In Postman, click **Send and Download** to save the file. The filename follows the pattern `{slug}-{YYYY-MM-DD-HH-mm}.json`.

### Responses

`200 OK` — file body follows the **training export schema**.

`404 Not Found`

```json theme={null}
{ "message": "Training not found or does not belong to the specified company." }
```

`403 Forbidden`

```json theme={null}
{ "message": "Unauthorized access to training." }
```

`422 Unprocessable Entity`

```json theme={null}
{
  "message": "Invalid request parameters.",
  "errors": { "company_id": ["The company id field is required."] }
}
```

`500 Internal Server Error`

```json theme={null}
{ "message": "Failed to export training." }
```

***

## Export all trainings (ZIP)

```shellscript theme={null}
GET /trainings/export-import/export-all
```

Downloads a ZIP archive containing one JSON file per training for the given company.

### Query parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `company_id` | integer | Yes | - |

### Example request

```shellscript theme={null}
GET {{base_url}}/api/trainings/export-import/export-all?company_id=1
Accept: application/json
Authorization: Bearer {{token}}
```

### ZIP layout

```shellscript theme={null}
ALL_trainings_export_2026-04-17-12-30.zip
├── intro-to-pmp-2026-04-17-12-30.json
├── pmp-advanced-2026-04-17-12-30.json
└── agile-basics-2026-04-17-12-30.json
```

Each JSON file follows the **training export schema**.

### Responses

`404 Not Found`

```json theme={null}
{ "message": "No trainings found for the given company." }
```

`500 Internal Server Error` — ZIP creation failed

```json theme={null}
{ "message": "Failed to create ZIP archive." }
```

`500 Internal Server Error` — generic failure

```json theme={null}
{ "message": "Failed to export trainings." }
```

***

## Import trainings from JSON

```shellscript theme={null}
POST /trainings/export-import/import
```

Creates one or more trainings from uploaded `.json` files. Accepts either a single file or an array.

> ⚠️ **Import behavior is intentionally conservative:**
>
> * **Instructor** is always forced to the fallback system user (id `2`). The original `instructor_id` in the file is ignored.
> * **Media** — `image_id` and `video_id` are always set to `null`.
> * **Visibility** — `is_public` is always `false` after import.
> * **Slug** is normalized, truncated to 13 characters, and suffixed with a random 4-character string on collision.
> * **Category** — only the **first** label of `categories` is kept (one category per training since 01/11/2026).
> * **Chapters / lessons / quizzes** are not imported. Use the chapter/lesson import flow separately.

### Form-data fields

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `company_id` | integer | Yes | Target company for every training in this import. |
| `training_file` | file | \* | A single `.json` file. Required if `training_files[]` is not sent. |
| `training_files[]` | file\[] | \* | One or more `.json` files. Required if `training_file` is not sent. |

### Per-file JSON validation

Each uploaded file is validated before insertion:

* `revenue_percentage` — **required**, numeric, between `0` and `100`.
* `label` — optional (defaults to `"Untitled Training"`).
* `slug` — optional (generated from `label` when missing).
* `level` — optional (defaults to `Beginner`).
* `duration`, `price` — optional (default `0`).
* `discounted_price`, `views_count` — optional.
* `categories` — optional array of labels. Only the **first** label is used (one category per training since 01/11/2026): it is matched by label in the target company and created when missing. The other labels are ignored and never created, so a file exported before 01/11/2026 with several categories keeps its first one.
* `tags` — optional array of labels. Missing ones are auto-created for the target company.

### Example file payload

`intro-to-pmp.json`

```json theme={null}
{
  "label": "Intro to PMP",
  "slug": "intro-to-pmp",
  "description": "<p>Full primer.</p>",
  "level": "Beginner",
  "duration": 120,
  "price": 199.99,
  "discounted_price": 149.99,
  "revenue_percentage": 40,
  "views_count": 0,
  "categories": ["Project Management"],
  "tags": ["PMP", "Exam Prep"]
}
```

### Postman setup (Body → form-data)

Multi-file upload:

```shellscript theme={null}
Key                | Type | Value
-------------------+------+----------------------
company_id         | Text | 1
training_files[]   | File | intro-to-pmp.json
training_files[]   | File | pmp-advanced.json
```

Single-file upload:

```shellscript theme={null}
Key             | Type | Value
----------------+------+--------------------
company_id      | Text | 1
training_file   | File | intro-to-pmp.json
```

> Remove any explicit `Content-Type` header in Postman so the client adds the correct `multipart/form-data; boundary=...` itself.

### Responses

`200 OK` — all files succeeded

```json theme={null}
{
  "imported_trainings": [
    {
      "file_name": "intro-to-pmp.json",
      "training_id": 58,
      "message": "Training imported successfully."
    },
    {
      "file_name": "pmp-advanced.json",
      "training_id": 59,
      "message": "Training imported successfully."
    }
  ],
  "failed_trainings": []
}
```

`200 OK` — partial success

> Partial failures still return HTTP `200`. Always inspect the `failed_trainings` array.

```json theme={null}
{
  "imported_trainings": [
    {
      "file_name": "intro-to-pmp.json",
      "training_id": 58,
      "message": "Training imported successfully."
    }
  ],
  "failed_trainings": [
    { "file_name": "broken.json",      "message": "Invalid JSON structure." },
    { "file_name": "no-revenue.json",  "message": "Revenue percentage is missing or not numeric." },
    { "file_name": "bad-revenue.json", "message": "Revenue percentage must be between 0 and 100." }
  ]
}
```

`400 Bad Request` — nothing uploaded

```json theme={null}
{ "message": "No training files provided." }
```

`422 Unprocessable Entity` — bad file type or missing company

```json theme={null}
{
  "message": "The given data was invalid.",
  "errors": {
    "training_files.0": ["The training_files.0 must be a file of type: json."]
  }
}
```

***

## Back up a training

```shellscript theme={null}
GET /trainings/export-import/backup/{id}
```

Downloads a ZIP archive with the training JSON **plus** all its chapters (each with its lessons and **all** its chapter quizzes). The training's final quiz is not part of the backup — export it with the [quiz export](https://theteam-13fc7f49.mintlify.site/api-reference/quizzes-and-exams-export-and-import-endpoints.md).

### Query parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `company_id` | integer | Yes | |

### Example request

```shellscript theme={null}
GET {{base_url}}/api/trainings/export-import/backup/12?company_id=1
Accept: application/json
Authorization: Bearer {{token}}
```

### ZIP layout

```shellscript theme={null}
intro-to-pmp-backup-2026-04-17-12-30.zip
├── intro-to-pmp-2026-04-17-12-30.json
└── chapters/
    ├── chapter-1-2026-04-17-12-30.json
    ├── chapter-2-2026-04-17-12-30.json
    └── ...
```

The top-level training JSON uses the **training export schema**. Each `chapters/*.json` is the full chapter record, including its lessons and, since 19/10/2026, a `quizzes` array with **every** quiz of the chapter (published and draft, creation order) with questions, options, and option items, plus the deprecated `quiz` (= the first one). Same shape as [the chapter export schema](https://theteam-13fc7f49.mintlify.site/api-reference/chapters-and-lessons-export-import.md#the-chapter-export-schema).

### Example chapter file

`chapter-1-2026-04-17-12-30.json`

```json theme={null}
{
  "id": 40,
  "training_id": 12,
  "label": "Chapter 1",
  "position": 1,
  "created_at": "2026-04-01 09:22:10",
  "updated_at": "2026-04-10 14:03:55",
  "archived_at": null,
  "deleted_at": null,
  "training": { "id": 12, "label": "Intro to PMP" },
  "lessons": [
    {
      "id": 101,
      "chapter_id": 40,
      "label": "Lesson 1",
      "type": "document",
      "position": 1,
      "content": "<p>...</p>",
      "created_at": "2026-04-01 09:25:00",
      "updated_at": "2026-04-01 09:25:00"
    }
  ],
  "quizzes": [
    {
      "id": 77,
      "training_id": null,
      "chapter_id": 40,
      "label": "Chapter 1 Quiz",
      "slug": "chapter-1-quiz",
      "is_published": 1,
      "quiz_questions": [
        {
          "id": 300,
          "quiz_id": 77,
          "quiz_question_type_id": 2,
          "label": "What does the project scope define?",
          "quiz_question_options": [
            {
              "id": 900,
              "quiz_question_id": 300,
              "label": "The work to be done",
              "is_correct": true,
              "quiz_question_option_items": []
            }
          ]
        }
      ]
    },
    {
      "id": 80,
      "training_id": null,
      "chapter_id": 40,
      "label": "Chapter 1 Recap Quiz",
      "slug": "chapter-1-recap-quiz",
      "is_published": 0,
      "quiz_questions": []
    }
  ],
  "quiz": { "id": 77, "chapter_id": 40, "label": "Chapter 1 Quiz" }
}
```

Each quiz object is shortened here; the file carries every column of the quiz row (plus its `instructor` and computed rating / sales fields). `quiz` is the deprecated copy of `quizzes[0]` (or `null`), kept for older readers; backups made before 19/10/2026 only have `quiz`.

### Responses

`404 Not Found`

```json theme={null}
{ "message": "Training not found or does not belong to the specified company." }
```

`403 Forbidden`

```json theme={null}
{ "message": "Unauthorized access to training." }
```

`500 Internal Server Error`

```json theme={null}
{ "message": "Failed to backup training." }
```

***

## Import behavior at a glance

| Aspect | Behavior |
| :- | :- |
| Chapters / lessons / quizzes | Not imported. Use the dedicated chapter/lesson import flow — it imports chapters and lessons only; the `quizzes` / `quiz` keys of a chapter file are ignored. |
| Instructor | Forced to fallback user id `2`. Original `instructor_id` is ignored. |
| Media (image / video) | Not imported. `image_id` and `video_id` are set to `null`. |
| Visibility | Always `is_public = false` after import. |
| Slug | Normalized, truncated to 13 chars, suffixed randomly on collision. |
| Category | The first label of `categories` only (one category per training since 01/11/2026), matched by `label` + `company_id` and created when missing. The other labels are ignored. |
| Tags | Auto-created for the target company (matched by `label` + `company_id`). |
| Transactions | One DB transaction per file — a failing file rolls back only itself. |

***

## Postman setup

1. **Environment variables.** Reuse the same environment as your main API collection:
   * `base_url` — e.g. `http://localhost:8000`
   * `token` — Sanctum token from `POST /auth/login`
2. **Collection-level headers.**
   * `Accept: application/json`
   * `Authorization: Bearer {{token}}` Do **not** set a `Content-Type` header on the import request — Postman must build the multipart boundary itself.
3. **Downloading files.** For `export`, `export-all`, and `backup`, click **Send and Download** so Postman writes the file to disk.
4. **Smoke-test flow.**
   1. `GET /trainings/export-import/export-all?company_id=1` — download ZIP
   2. Unzip and pick one file
   3. `POST /trainings/export-import/import` with that file
   4. `GET /trainings?company_id=1` — confirm the new training appears
   5. `GET /trainings/export-import/backup/{id}?company_id=1` — snapshot chapters too


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.