1701 lines
33 KiB
Markdown
1701 lines
33 KiB
Markdown
# API Reference - E2E Documentation
|
|
|
|
## Overview
|
|
|
|
The Tour Builder Platform exposes a RESTful API built with Express.js. All endpoints follow consistent patterns for CRUD operations, authentication, and response formats.
|
|
|
|
**Base URL:** `http://localhost:3000/api` for local development.
|
|
|
|
**Standard VM:** Apache serves the public domain on port `80`, the frontend
|
|
PM2 app listens on `3001`, and the backend PM2 app listens on `3000` with
|
|
`NODE_ENV=dev_stage`. Use `http://127.0.0.1:3000/api/...` for direct VM backend
|
|
checks, or `http://tbp.flatlogic.app/api/...` through Apache/Cloudflare. See
|
|
[deployment-vm.md](deployment-vm.md).
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### JWT Bearer Token
|
|
|
|
Most endpoints require JWT authentication via the Authorization header:
|
|
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Token Details:**
|
|
- Expiration: 6 hours
|
|
- Payload: `{ id, email, name }`
|
|
- Obtained via `/api/auth/signin/local` or OAuth callbacks
|
|
|
|
### Public Endpoints (No Auth Required)
|
|
|
|
- `GET /api/health`
|
|
- `POST /api/auth/signin/local`
|
|
- `POST /api/auth/send-password-reset-email`
|
|
- `PUT /api/auth/password-reset`
|
|
- `PUT /api/auth/verify-email`
|
|
- `GET /api/auth/email-configured`
|
|
- `GET /api/auth/signin/google`
|
|
- `GET /api/auth/signin/microsoft`
|
|
- `GET /api/runtime-context`
|
|
|
|
### Runtime Public Access
|
|
|
|
In **production** runtime mode only, certain GET endpoints allow unauthenticated read access (stage requires authentication):
|
|
- `GET /api/projects`
|
|
- `GET /api/tour_pages`
|
|
- `GET /api/project_audio_tracks`
|
|
- `GET /api/global-ui-control-defaults`
|
|
- `GET /api/project-ui-control-settings/project/:projectId/env/production`
|
|
|
|
**Note:** The `X-Runtime-Environment` header must be set to `production` for public access. Stage environment (`stage`) requires JWT authentication as it serves as a workspace for review.
|
|
|
|
---
|
|
|
|
## Standard Response Formats
|
|
|
|
### Success Responses
|
|
|
|
**Single Entity:**
|
|
```json
|
|
{
|
|
"id": "uuid-here",
|
|
"name": "Entity Name",
|
|
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
"updatedAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**List Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{ "id": "uuid-1", "name": "Entity 1" },
|
|
{ "id": "uuid-2", "name": "Entity 2" }
|
|
],
|
|
"count": 100
|
|
}
|
|
```
|
|
|
|
**Count Response:**
|
|
```json
|
|
{
|
|
"count": 100
|
|
}
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
```json
|
|
{
|
|
"error": "Error message description"
|
|
}
|
|
```
|
|
|
|
**HTTP Status Codes:**
|
|
| Code | Description |
|
|
|------|-------------|
|
|
| 200 | Success |
|
|
| 201 | Created |
|
|
| 400 | Bad Request (validation errors) |
|
|
| 401 | Unauthorized (missing/invalid token) |
|
|
| 403 | Forbidden (insufficient permissions) |
|
|
| 404 | Not Found |
|
|
| 429 | Too Many Requests (rate limited) |
|
|
| 500 | Internal Server Error |
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
The API uses rate limiting to prevent abuse. Rate limits are applied per IP address, and rate limit headers are returned with each response:
|
|
|
|
| Header | Description |
|
|
|--------|-------------|
|
|
| `X-RateLimit-Limit` | Maximum requests allowed per window |
|
|
| `X-RateLimit-Remaining` | Remaining requests in current window |
|
|
| `X-RateLimit-Reset` | ISO timestamp when the window resets |
|
|
| `Retry-After` | Seconds until you can retry (when limited) |
|
|
|
|
### Rate Limit Configuration
|
|
|
|
| Limiter | Window | Max Requests | Endpoints |
|
|
|---------|--------|--------------|-----------|
|
|
| **Auth** | 15 minutes | 10 | `/api/auth/signin/*` |
|
|
| **Password Reset** | 1 hour | 5 | `/api/auth/send-password-reset-email` |
|
|
| **API** | 1 minute | 100 | General API endpoints |
|
|
| **Upload** | 1 minute | 10 | `/api/file/upload*` |
|
|
| **Download** | 1 minute | 200 | `/api/file/download`, `/api/file/presign` |
|
|
| **Search** | 1 minute | 30 | `/api/search` |
|
|
|
|
**Note:** Rate limiting is skipped for localhost (`127.0.0.1`, `::1`) in development mode.
|
|
|
|
---
|
|
|
|
## Standard Entity Endpoints
|
|
|
|
All entity routes (projects, assets, users, etc.) support these standard endpoints:
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| `GET` | `/api/{entity}` | List entities (paginated) |
|
|
| `GET` | `/api/{entity}/count` | Get count only |
|
|
| `GET` | `/api/{entity}/autocomplete` | Autocomplete search |
|
|
| `GET` | `/api/{entity}/:id` | Get single entity by ID |
|
|
| `POST` | `/api/{entity}` | Create entity |
|
|
| `PUT` | `/api/{entity}/:id` | Update entity |
|
|
| `DELETE` | `/api/{entity}/:id` | Delete entity |
|
|
| `POST` | `/api/{entity}/deleteByIds` | Bulk delete |
|
|
| `POST` | `/api/{entity}/bulk-import` | CSV import |
|
|
|
|
### GET /api/{entity}/count
|
|
|
|
Get count of entities matching the query.
|
|
|
|
**Query Parameters:** Same as list endpoint
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"count": 100
|
|
}
|
|
```
|
|
|
|
### GET /api/{entity}/autocomplete
|
|
|
|
Get autocomplete suggestions for a field.
|
|
|
|
**Query Parameters:**
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `query` | string | Search query |
|
|
| `limit` | number | Max results (default: 10) |
|
|
| `offset` | number | Pagination offset |
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{ "id": "uuid-1", "label": "Option 1" },
|
|
{ "id": "uuid-2", "label": "Option 2" }
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Standard Query Parameters
|
|
|
|
All list endpoints support these query parameters:
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `limit` | number | 10 | Items per page |
|
|
| `offset` | number | 0 | Pagination offset |
|
|
| `page` | number | 0 | Page number (alternative to offset) |
|
|
| `search` | string | - | Text search across searchable fields |
|
|
| `sort` | string | - | Sort field (e.g., `name` or `name:DESC`) |
|
|
| `filetype` | string | - | Export format (`csv`) |
|
|
|
|
### Entity-Specific Filters
|
|
|
|
Filters vary by entity based on their `SEARCHABLE_FIELDS`, `RANGE_FIELDS`, and `ENUM_FIELDS`:
|
|
|
|
```
|
|
# Text search
|
|
?search=keyword
|
|
|
|
# Range filters (numeric/date)
|
|
?createdAtRange=2024-01-01,2024-12-31
|
|
?sizeRange=0,1000000
|
|
|
|
# Enum/exact match filters
|
|
?type=image
|
|
?status=active
|
|
?environment=production
|
|
|
|
# Relationship filters
|
|
?projectId=uuid
|
|
?tour_pageId=uuid
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Endpoints
|
|
|
|
### POST /api/auth/signin/local
|
|
|
|
Login with email and password.
|
|
|
|
**Rate Limit:** 10 requests per 15 minutes
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "user-uuid",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"token": "eyJhbGciOiJIUzI1NiIs..."
|
|
}
|
|
```
|
|
|
|
### Self-Registration
|
|
|
|
Self-registration is disabled. The API does not expose
|
|
`POST /api/auth/signup`; new users are created through the authenticated Users
|
|
flow and receive an invitation/setup link.
|
|
|
|
### GET /api/auth/me
|
|
|
|
Get current authenticated user.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "user-uuid",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"app_role": {
|
|
"id": "role-uuid",
|
|
"name": "Admin"
|
|
}
|
|
}
|
|
```
|
|
|
|
### PUT /api/auth/password-update
|
|
|
|
Update password for authenticated user.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"currentPassword": "oldpassword",
|
|
"newPassword": "newpassword123"
|
|
}
|
|
```
|
|
|
|
### POST /api/auth/send-password-reset-email
|
|
|
|
Send password reset email.
|
|
|
|
**Rate Limit:** 5 requests per hour
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
### POST /api/auth/send-email-address-verification-email
|
|
|
|
Resend email verification email for current user.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:** `true` on success
|
|
|
|
### PUT /api/auth/password-reset
|
|
|
|
Reset password with token.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"token": "reset-token-from-email",
|
|
"password": "newpassword123"
|
|
}
|
|
```
|
|
|
|
### PUT /api/auth/profile
|
|
|
|
Update user profile.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"profile": {
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"phoneNumber": "+1234567890"
|
|
}
|
|
}
|
|
```
|
|
|
|
### OAuth Endpoints
|
|
|
|
| Endpoint | Description |
|
|
|----------|-------------|
|
|
| `GET /api/auth/signin/google` | Initiate Google OAuth |
|
|
| `GET /api/auth/signin/google/callback` | Google OAuth callback |
|
|
| `GET /api/auth/signin/microsoft` | Initiate Microsoft OAuth |
|
|
| `GET /api/auth/signin/microsoft/callback` | Microsoft OAuth callback |
|
|
|
|
---
|
|
|
|
## System Endpoints
|
|
|
|
### GET /api/health
|
|
|
|
Health check endpoint.
|
|
|
|
**Auth:** Not required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
"uptime": 3600,
|
|
"environment": "production",
|
|
"database": "connected"
|
|
}
|
|
```
|
|
|
|
### GET /api/runtime-context
|
|
|
|
Get runtime context information.
|
|
|
|
**Auth:** Not required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"mode": "admin",
|
|
"projectSlug": null
|
|
}
|
|
```
|
|
|
|
**Modes:**
|
|
- `admin` - Full access (localhost or admin subdomain)
|
|
- `stage` - Stage environment (stage.{project}.domain)
|
|
- `production` - Production environment ({project}.domain)
|
|
|
|
---
|
|
|
|
## File Endpoints
|
|
|
|
### POST /api/file/upload-sessions/init
|
|
|
|
Initialize chunked upload session.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"filename": "video.mp4",
|
|
"size": 104857600,
|
|
"mimeType": "video/mp4",
|
|
"projectId": "project-uuid"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"sessionId": "session-uuid",
|
|
"chunkSize": 5242880,
|
|
"totalChunks": 20,
|
|
"expiresAt": "2024-01-02T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
### GET /api/file/upload-sessions/:sessionId
|
|
|
|
Get upload session status.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"sessionId": "session-uuid",
|
|
"filename": "video.mp4",
|
|
"uploadedChunks": [0, 1, 2],
|
|
"totalChunks": 20,
|
|
"status": "uploading"
|
|
}
|
|
```
|
|
|
|
### PUT /api/file/upload-sessions/:sessionId/chunks/:chunkIndex
|
|
|
|
Upload file chunk.
|
|
|
|
**Auth:** Required
|
|
|
|
**Content-Type:** `application/octet-stream`
|
|
|
|
**Body:** Raw binary data
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"chunkIndex": 0,
|
|
"received": true
|
|
}
|
|
```
|
|
|
|
### POST /api/file/upload-sessions/:sessionId/finalize
|
|
|
|
Finalize chunked upload.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"asset": {
|
|
"id": "asset-uuid",
|
|
"url": "https://cdn.example.com/video.mp4",
|
|
"storageKey": "uploads/video.mp4"
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/file/download
|
|
|
|
Download file.
|
|
|
|
**Query Parameters:**
|
|
- `privateUrl` - Encoded file path
|
|
- `id` - Asset ID (alternative)
|
|
|
|
**Response:** File stream with appropriate Content-Type
|
|
|
|
### POST /api/file/presign
|
|
|
|
Generate presigned URLs for batch asset downloads. For S3 storage, returns direct S3 signed URLs for client-side downloads (bypassing the backend). For other storage providers, returns backend proxy URLs.
|
|
|
|
**Auth:** Not required (public endpoint for runtime asset preloading)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"urls": [
|
|
"uploads/projects/abc/image1.jpg",
|
|
"uploads/projects/abc/video.mp4",
|
|
"uploads/projects/abc/audio.mp3"
|
|
]
|
|
}
|
|
```
|
|
|
|
**Limits:**
|
|
- Maximum 50 URLs per request
|
|
- All URLs must be non-empty strings
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"presignedUrls": {
|
|
"uploads/projects/abc/image1.jpg": "https://bucket.s3.region.amazonaws.com/uploads/projects/abc/image1.jpg?X-Amz-...",
|
|
"uploads/projects/abc/video.mp4": "https://bucket.s3.region.amazonaws.com/uploads/projects/abc/video.mp4?X-Amz-...",
|
|
"uploads/projects/abc/audio.mp3": "https://bucket.s3.region.amazonaws.com/uploads/projects/abc/audio.mp3?X-Amz-..."
|
|
}
|
|
}
|
|
```
|
|
|
|
**Presigned URL Expiry:** 1 hour (3600 seconds)
|
|
|
|
**Use Case:** Frontend preloading uses this endpoint to get direct S3 download URLs, enabling:
|
|
- Parallel asset downloads without backend proxy overhead
|
|
- Direct storage in browser Cache API
|
|
- Blob URL creation for instant display
|
|
|
|
### POST /api/file/upload/:table/:field
|
|
|
|
Legacy single-file upload.
|
|
|
|
**Auth:** Required
|
|
|
|
**Content-Type:** `multipart/form-data`
|
|
|
|
---
|
|
|
|
## Projects Endpoints
|
|
|
|
### POST /api/projects
|
|
|
|
Create a new project.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "My Tour",
|
|
"slug": "my-tour",
|
|
"description": "A virtual tour",
|
|
"logo_url": "https://...",
|
|
"favicon_url": "https://...",
|
|
"og_image_url": "https://...",
|
|
"theme_config_json": {},
|
|
"custom_css_json": {},
|
|
"cdn_base_url": "https://cdn.example.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/projects
|
|
|
|
List all projects.
|
|
|
|
**Auth:** Optional (runtime public mode)
|
|
|
|
**Query Parameters:**
|
|
- Standard pagination/filtering
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "project-uuid",
|
|
"name": "My Tour",
|
|
"slug": "my-tour",
|
|
"tour_pages": [...],
|
|
"assets": [...]
|
|
}
|
|
],
|
|
"count": 10
|
|
}
|
|
```
|
|
|
|
### GET /api/projects/:id
|
|
|
|
Get project by ID.
|
|
|
|
**Auth:** Optional (runtime public mode)
|
|
|
|
**Includes:** tour_pages, assets, project_audio_tracks, project_element_defaults
|
|
|
|
### PUT /api/projects/:id
|
|
|
|
Update project.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"id": "project-uuid",
|
|
"data": {
|
|
"name": "Updated Tour Name"
|
|
}
|
|
}
|
|
```
|
|
|
|
### DELETE /api/projects/:id
|
|
|
|
Delete project.
|
|
|
|
**Auth:** Required
|
|
|
|
### POST /api/projects/:id/clone
|
|
|
|
Clone project with all related entities.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "new-project-uuid",
|
|
"name": "My Tour (Copy)",
|
|
"slug": "my-tour-copy"
|
|
}
|
|
```
|
|
|
|
### POST /api/publish/save-to-stage
|
|
|
|
Copy all `dev` environment content to `stage` for preview.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": "project-uuid"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"publishEventId": "event-uuid",
|
|
"summary": {
|
|
"pages_copied": 10,
|
|
"audios_copied": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note:** This is part of the dev → stage → production workflow. Content is edited in `dev` (Constructor), previewed in `stage`, then published to `production`. Page elements, navigation, and transitions are stored in `tour_pages.ui_schema_json` and copied with pages.
|
|
|
|
## Tour Pages Endpoints
|
|
|
|
### POST /api/tour_pages
|
|
|
|
Create a tour page.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"title": "Home Page",
|
|
"slug": "home",
|
|
"sort_order": 0,
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"background_image_url": "https://...",
|
|
"background_video_url": "https://...",
|
|
"ui_schema_json": {}
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/tour_pages
|
|
|
|
List tour pages.
|
|
|
|
**Auth:** Optional (runtime public mode)
|
|
|
|
**Query Parameters:**
|
|
- `projectId` - Filter by project
|
|
- `environment` - Filter by environment
|
|
|
|
### GET /api/tour_pages/:id
|
|
|
|
Get tour page by ID.
|
|
|
|
**Includes:** project
|
|
|
|
**Note:** Page elements are stored directly in the `ui_schema_json` field.
|
|
|
|
### PUT /api/tour_pages/:id
|
|
|
|
Update tour page.
|
|
|
|
**Auth:** Required
|
|
|
|
### POST /api/tour_pages/reorder
|
|
|
|
Update page order within a project/environment without touching page content.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"orderedPageIds": ["page-uuid-1", "page-uuid-2", "page-uuid-3"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Behavior:**
|
|
- Reordering is allowed only in `dev`.
|
|
- `orderedPageIds` must contain every dev page in that project exactly once.
|
|
- Duplicate IDs, missing IDs, IDs from another project, and unknown IDs are
|
|
rejected.
|
|
- The backend updates only `sort_order`; page content, slugs, navigation links,
|
|
and media fields are not changed.
|
|
- Stage receives the new order after `POST /api/publish/save-to-stage`.
|
|
- Production receives the new order after `POST /api/publish`.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{ "id": "page-uuid-1", "sort_order": 1 },
|
|
{ "id": "page-uuid-2", "sort_order": 2 },
|
|
{ "id": "page-uuid-3", "sort_order": 3 }
|
|
]
|
|
```
|
|
|
|
**Errors:**
|
|
| Status | Reason |
|
|
|--------|--------|
|
|
| 400 | Missing `projectId`, invalid `orderedPageIds`, duplicate/missing pages, or `environment` other than `dev` |
|
|
| 403 | Authenticated user is not allowed to update pages in the project |
|
|
|
|
### POST /api/tour_pages/:id/duplicate
|
|
|
|
Duplicate a dev tour page into a new independent dev page.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"name": "Lobby Copy",
|
|
"slug": "lobby-copy"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Behavior:**
|
|
- Only dev pages can be duplicated. Stage and production are updated through
|
|
Save to Stage and Publish.
|
|
- The duplicate receives a new page ID, unique slug, blank `source_key`, and
|
|
`sort_order = max(project dev sort_order) + 1`.
|
|
- Page media/settings fields and `ui_schema_json` are copied from the source
|
|
page.
|
|
- Inline element IDs and nested item IDs in `ui_schema_json` are regenerated so
|
|
the new page can be edited independently.
|
|
- Asset URLs, transition URLs, and navigation `targetPageSlug` values are
|
|
preserved.
|
|
- Existing reverse-video processing still runs through `TourPagesService`, so
|
|
transition reverse URLs are handled consistently with normal page save/create.
|
|
|
|
**Response:** Created tour page record.
|
|
|
|
**Errors:**
|
|
| Status | Reason |
|
|
|--------|--------|
|
|
| 400 | Missing source page, source page outside project, or non-dev source/target environment |
|
|
| 403 | Authenticated user is not allowed to create/update pages in the project |
|
|
|
|
### DELETE /api/tour_pages/:id
|
|
|
|
Delete tour page.
|
|
|
|
**Auth:** Required
|
|
|
|
**Constructor behavior:** the constructor uses this same endpoint for the active
|
|
dev page after showing a confirmation modal. After deletion it reloads page data
|
|
and selects the next page in presentation order when available.
|
|
|
|
---
|
|
|
|
## Assets Endpoints
|
|
|
|
### POST /api/assets
|
|
|
|
Create an asset record.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Hero Image",
|
|
"cdn_url": "https://cdn.example.com/image.jpg",
|
|
"storage_key": "uploads/image.jpg",
|
|
"mime_type": "image/jpeg",
|
|
"checksum": "abc123...",
|
|
"width_px": 1920,
|
|
"height_px": 1080,
|
|
"size_mb": 0.5,
|
|
"duration_sec": null,
|
|
"projectId": "project-uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/assets
|
|
|
|
List assets.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `projectId` - Filter by project
|
|
- `mime_type` - Filter by MIME type
|
|
|
|
**Includes:** asset_variants, project
|
|
|
|
### GET /api/assets/:id
|
|
|
|
Get asset by ID.
|
|
|
|
**Includes:** asset_variants
|
|
|
|
### PUT /api/assets/:id
|
|
|
|
Update asset.
|
|
|
|
**Auth:** Required
|
|
|
|
### DELETE /api/assets/:id
|
|
|
|
Delete asset (and file from storage).
|
|
|
|
**Auth:** Required
|
|
|
|
---
|
|
|
|
## Asset Variants Endpoints
|
|
|
|
### POST /api/asset_variants
|
|
|
|
Create an asset variant.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"assetId": "asset-uuid",
|
|
"variant_type": "webp",
|
|
"cdn_url": "https://cdn.example.com/image.webp",
|
|
"storage_key": "uploads/image.webp",
|
|
"mime_type": "image/webp",
|
|
"width_px": 1920,
|
|
"height_px": 1080,
|
|
"size_bytes": 51200
|
|
}
|
|
}
|
|
```
|
|
|
|
**Variant Types:**
|
|
- `thumbnail` - Small preview
|
|
- `preview` - Medium preview
|
|
- `webp` - WebP format
|
|
- `mp4_low` - Low quality video
|
|
- `mp4_high` - High quality video
|
|
- `original` - Original file
|
|
|
|
### GET /api/asset_variants
|
|
|
|
List asset variants.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `assetId` - Filter by parent asset
|
|
- `variant_type` - Filter by variant type
|
|
|
|
---
|
|
|
|
## Project Audio Tracks Endpoints
|
|
|
|
### POST /api/project_audio_tracks
|
|
|
|
Create a project audio track.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Background Music",
|
|
"projectId": "project-uuid",
|
|
"audio_url": "https://cdn.example.com/music.mp3",
|
|
"duration_sec": 180,
|
|
"volume": 0.8,
|
|
"loop": true,
|
|
"autoplay": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/project_audio_tracks
|
|
|
|
List audio tracks.
|
|
|
|
**Auth:** Optional (runtime public mode)
|
|
|
|
**Query Parameters:**
|
|
- `projectId` - Filter by project
|
|
|
|
---
|
|
|
|
## Project Memberships Endpoints
|
|
|
|
Manage team collaboration and per-project access control.
|
|
|
|
### POST /api/project_memberships
|
|
|
|
Create a project membership (invite user to project).
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"userId": "user-uuid",
|
|
"access_level": "editor",
|
|
"is_active": true,
|
|
"invited_at": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Access Levels:** `owner`, `editor`, `reviewer`, `viewer`
|
|
|
|
### GET /api/project_memberships
|
|
|
|
List project memberships.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `projectId` or `project` - Filter by project
|
|
- `userId` or `user` - Filter by user
|
|
- `access_level` - Filter by access level
|
|
- `is_active` - Filter by active status
|
|
|
|
### GET /api/project_memberships/:id
|
|
|
|
Get project membership by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
**Includes:** project, user
|
|
|
|
### PUT /api/project_memberships/:id
|
|
|
|
Update project membership.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"id": "membership-uuid",
|
|
"data": {
|
|
"access_level": "viewer",
|
|
"is_active": false
|
|
}
|
|
}
|
|
```
|
|
|
|
### DELETE /api/project_memberships/:id
|
|
|
|
Remove user from project.
|
|
|
|
**Auth:** Required
|
|
|
|
---
|
|
|
|
## PWA Caches Endpoints
|
|
|
|
Track PWA cache versions and offline asset manifests per project.
|
|
|
|
### POST /api/pwa_caches
|
|
|
|
Create a PWA cache record.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "production",
|
|
"cache_version": "v1704067200000",
|
|
"manifest_json": {},
|
|
"asset_list_json": [],
|
|
"generated_at": "2024-01-01T00:00:00.000Z",
|
|
"is_active": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Environment Values:** `dev`, `stage`, `production`
|
|
|
|
### GET /api/pwa_caches
|
|
|
|
List PWA cache records.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `projectId` or `project` - Filter by project
|
|
- `environment` - Filter by environment
|
|
- `is_active` - Filter by active status
|
|
|
|
### GET /api/pwa_caches/:id
|
|
|
|
Get PWA cache by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
**Includes:** project
|
|
|
|
### PUT /api/pwa_caches/:id
|
|
|
|
Update PWA cache record.
|
|
|
|
**Auth:** Required
|
|
|
|
### DELETE /api/pwa_caches/:id
|
|
|
|
Delete PWA cache record.
|
|
|
|
**Auth:** Required
|
|
|
|
---
|
|
|
|
## Users Endpoints
|
|
|
|
### POST /api/users
|
|
|
|
Create a user.
|
|
|
|
**Auth:** Required (Admin)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"email": "newuser@example.com",
|
|
"password": "password123",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"phoneNumber": "+1234567890",
|
|
"app_roleId": "role-uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/users
|
|
|
|
List users.
|
|
|
|
**Auth:** Required
|
|
|
|
**Searchable Fields:** firstName, lastName, email, phoneNumber
|
|
|
|
### GET /api/users/:id
|
|
|
|
Get user by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
**Includes:** app_role
|
|
|
|
### PUT /api/users/:id
|
|
|
|
Update user.
|
|
|
|
**Auth:** Required
|
|
|
|
### DELETE /api/users/:id
|
|
|
|
Delete user.
|
|
|
|
**Auth:** Required (Admin)
|
|
|
|
---
|
|
|
|
## Roles Endpoints
|
|
|
|
### POST /api/roles
|
|
|
|
Create a role.
|
|
|
|
**Auth:** Required (Admin)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Editor",
|
|
"globalAccess": false
|
|
}
|
|
}
|
|
```
|
|
|
|
### GET /api/roles
|
|
|
|
List roles.
|
|
|
|
**Auth:** Required
|
|
|
|
### GET /api/roles/:id
|
|
|
|
Get role by ID.
|
|
|
|
**Includes:** permissions
|
|
|
|
---
|
|
|
|
## Permissions Endpoints
|
|
|
|
### POST /api/permissions
|
|
|
|
Create a permission.
|
|
|
|
**Auth:** Required (Admin)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"roleId": "role-uuid",
|
|
"entity": "projects",
|
|
"action": "read"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Actions:** `create`, `read`, `update`, `delete`
|
|
|
|
---
|
|
|
|
## Publishing Endpoints
|
|
|
|
The platform uses a three-tier publishing workflow:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Constructor (dev) Stage (preview) Production (live) │
|
|
│ dev ───► stage ───► production │
|
|
│ "Save to Stage" "Publish" │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### POST /api/publish/save-to-stage
|
|
|
|
Copy `dev` content to `stage` for preview. See [Projects Endpoints](#post-apipublishsave-to-stage).
|
|
|
|
### POST /api/publish (or POST /api/publish/publish)
|
|
|
|
Publish project from `stage` to `production` environment. Both endpoints are aliases and perform the same action.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": "project-uuid",
|
|
"title": "v1.0 Release",
|
|
"description": "Initial production release"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"publishEventId": "event-uuid",
|
|
"summary": {
|
|
"pages_copied": 10,
|
|
"audios_copied": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note:** Publishes `stage` environment content to `production`. Use `save-to-stage` first to copy changes from `dev`. Page elements, navigation links, and transitions are stored within `tour_pages.ui_schema_json` and copied automatically with pages.
|
|
|
|
---
|
|
|
|
## Publish Events Endpoints
|
|
|
|
### GET /api/publish_events
|
|
|
|
List publish events.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `projectId` - Filter by project
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "event-uuid",
|
|
"projectId": "project-uuid",
|
|
"title": "v1.0 Release",
|
|
"description": "Initial release",
|
|
"publishedAt": "2024-01-01T00:00:00.000Z",
|
|
"createdBy": "user-uuid"
|
|
}
|
|
],
|
|
"count": 5
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Access Logs Endpoints
|
|
|
|
### POST /api/access_logs
|
|
|
|
Create an access log entry.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"project": "project-uuid",
|
|
"user": "user-uuid",
|
|
"environment": "production",
|
|
"path": "/api/tour_pages",
|
|
"ip_address": "192.168.1.1",
|
|
"user_agent": "Mozilla/5.0...",
|
|
"accessed_at": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Environment Values:** `admin`, `stage`, `production`
|
|
|
|
### GET /api/access_logs
|
|
|
|
List access logs.
|
|
|
|
**Auth:** Required
|
|
|
|
**Searchable Fields:** `path`, `ip_address`, `user_agent`
|
|
|
|
**Query Parameters:**
|
|
- `project` - Filter by project ID or name
|
|
- `user` - Filter by user ID or name
|
|
- `environment` - Filter by environment (admin/stage/production)
|
|
- `accessed_atRange` - Filter by date range
|
|
|
|
---
|
|
|
|
## Presigned URL Requests Endpoints
|
|
|
|
Track and audit presigned URL generation requests.
|
|
|
|
### POST /api/presigned_url_requests
|
|
|
|
Create a presigned URL request record.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"requested_key": "uploads/projects/abc/image.jpg",
|
|
"mime_type": "image/jpeg",
|
|
"status": "pending",
|
|
"requested_size_mb": 2.5,
|
|
"expires_at": "2024-01-01T01:00:00.000Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Status Values:** `pending`, `completed`, `expired`, `failed`
|
|
|
|
### GET /api/presigned_url_requests
|
|
|
|
List presigned URL requests.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `status` - Filter by status
|
|
- `requested_key` - Filter by storage key
|
|
|
|
### GET /api/presigned_url_requests/:id
|
|
|
|
Get presigned URL request by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
### PUT /api/presigned_url_requests/:id
|
|
|
|
Update presigned URL request.
|
|
|
|
**Auth:** Required
|
|
|
|
### DELETE /api/presigned_url_requests/:id
|
|
|
|
Delete presigned URL request.
|
|
|
|
**Auth:** Required
|
|
|
|
---
|
|
|
|
## Global UI Control Defaults Endpoints
|
|
|
|
Global UI controls are the system-owned fullscreen, sound, and offline buttons.
|
|
Runtime settings resolve through:
|
|
|
|
```
|
|
global_ui_control_defaults → project_ui_control_settings → tour_pages.global_ui_controls_settings_json
|
|
```
|
|
|
|
### GET /api/global-ui-control-defaults
|
|
|
|
Get singleton platform defaults.
|
|
|
|
**Auth:** Not required for runtime reads
|
|
|
|
### PUT /api/global-ui-control-defaults/:id
|
|
|
|
Update singleton platform defaults.
|
|
|
|
**Auth:** Required
|
|
|
|
### GET /api/project-ui-control-settings/project/:projectId/env/:environment
|
|
|
|
Get project/environment overrides. `environment` is `dev`, `stage`, or
|
|
`production`.
|
|
|
|
**Auth:** Required for `dev` and `stage`. Production is public for public
|
|
presentations and requires JWT/access grants for private production
|
|
presentations.
|
|
|
|
### PUT /api/project-ui-control-settings/project/:projectId/env/:environment
|
|
|
|
Upsert project/environment overrides.
|
|
|
|
**Auth:** Required
|
|
|
|
### DELETE /api/project-ui-control-settings/project/:projectId/env/:environment
|
|
|
|
Delete project/environment overrides so runtime falls back to global defaults.
|
|
|
|
**Auth:** Required
|
|
|
|
---
|
|
|
|
## Element Type Defaults Endpoints
|
|
|
|
Global platform-wide default settings for each element type. These are the source of truth that get snapshotted to `project_element_defaults` when a project is created.
|
|
|
|
**Alias:** `/api/ui-elements` routes to the same endpoints as `/api/element-type-defaults`.
|
|
|
|
**Note:** The Constructor page uses `project_element_defaults` (not this endpoint) to fetch element defaults for the current project. This endpoint is for platform-wide administration of default settings.
|
|
|
|
### GET /api/element-type-defaults
|
|
|
|
List all element type defaults.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "uuid",
|
|
"element_type": "navigation_next",
|
|
"name": "Navigation: Forward",
|
|
"sort_order": 1,
|
|
"is_active": true,
|
|
"default_settings_json": {
|
|
"width": "60px",
|
|
"height": "60px",
|
|
"opacity": 1
|
|
}
|
|
}
|
|
],
|
|
"count": 11
|
|
}
|
|
```
|
|
|
|
### GET /api/element-type-defaults/:id
|
|
|
|
Get element type default by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
### PUT /api/element-type-defaults/:id
|
|
|
|
Update element type default settings.
|
|
|
|
**Auth:** Required (Admin)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"data": {
|
|
"default_settings_json": {
|
|
"width": "80px",
|
|
"height": "80px"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Project Element Defaults Endpoints
|
|
|
|
Project-specific default settings for UI elements. These are automatically snapshotted from global defaults (`element_type_defaults`) when a project is created. The Constructor page uses this endpoint to fetch element defaults for the current project.
|
|
|
|
**Three-Tier Element Defaults Hierarchy:**
|
|
```
|
|
element_type_defaults (global) → project_element_defaults (project) → tour_pages.ui_schema_json (instance)
|
|
```
|
|
|
|
### GET /api/project-element-defaults
|
|
|
|
List project element defaults.
|
|
|
|
**Auth:** Required
|
|
|
|
**Query Parameters:**
|
|
- `projectId` or `project` - Filter by project ID or name (recommended)
|
|
- Supports `|` pipe separator for multiple values
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "uuid",
|
|
"projectId": "project-uuid",
|
|
"element_type": "navigation_next",
|
|
"name": "Navigation: Forward",
|
|
"sort_order": 1,
|
|
"settings_json": {
|
|
"width": "60px",
|
|
"height": "60px"
|
|
},
|
|
"source_element_id": "global-default-uuid",
|
|
"snapshot_version": 1
|
|
}
|
|
],
|
|
"count": 11
|
|
}
|
|
```
|
|
|
|
### GET /api/project-element-defaults/:id
|
|
|
|
Get project element default by ID.
|
|
|
|
**Auth:** Required
|
|
|
|
### PUT /api/project-element-defaults/:id
|
|
|
|
Update project-specific default settings.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"data": {
|
|
"settings_json": {
|
|
"width": "100px",
|
|
"backgroundColor": "#ff0000"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST /api/project-element-defaults/:id/reset
|
|
|
|
Reset project default to current global default. Copies settings from `element_type_defaults` and increments `snapshot_version`.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:** Returns the updated project element default record:
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"projectId": "project-uuid",
|
|
"element_type": "navigation_next",
|
|
"name": "Navigation: Forward",
|
|
"sort_order": 1,
|
|
"settings_json": { "width": "60px", "height": "60px" },
|
|
"source_element_id": "global-default-uuid",
|
|
"snapshot_version": 2,
|
|
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
"updatedAt": "2024-01-15T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**Error:** Returns 500 if global default not found for the element type.
|
|
|
|
### GET /api/project-element-defaults/:id/diff
|
|
|
|
Compare project default with current global default.
|
|
|
|
**Auth:** Required
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"projectDefault": {
|
|
"id": "uuid",
|
|
"element_type": "navigation_next",
|
|
"settings_json": { "width": "100px" }
|
|
},
|
|
"globalDefault": {
|
|
"id": "global-uuid",
|
|
"element_type": "navigation_next",
|
|
"default_settings_json": { "width": "60px" }
|
|
},
|
|
"hasGlobalDefault": true,
|
|
"isDifferent": true,
|
|
"projectSettings": { "width": "100px" },
|
|
"globalSettings": { "width": "60px" }
|
|
}
|
|
```
|
|
|
|
**Response when global default not found:**
|
|
```json
|
|
{
|
|
"projectDefault": { ... },
|
|
"globalDefault": null,
|
|
"hasGlobalDefault": false,
|
|
"isDifferent": true
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Search Endpoint
|
|
|
|
### POST /api/search
|
|
|
|
Search across all entities. Searches both text fields and numeric fields (cast to text for matching).
|
|
|
|
**Auth:** Required
|
|
|
|
**Rate Limit:** 30 requests per minute per IP
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"searchQuery": "keyword"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
Returns a flat array of matching records from all searchable tables. Each record includes `tableName` and `matchAttribute` fields to identify the source and matching fields.
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid-1",
|
|
"name": "My Project",
|
|
"slug": "my-project",
|
|
"tableName": "projects",
|
|
"matchAttribute": ["name", "slug"]
|
|
},
|
|
{
|
|
"id": "uuid-2",
|
|
"title": "Home Page",
|
|
"tableName": "tour_pages",
|
|
"matchAttribute": ["title"]
|
|
}
|
|
]
|
|
```
|
|
|
|
**Searchable Tables & Fields:**
|
|
|
|
| Table | Text Fields | Numeric Fields |
|
|
|-------|-------------|----------------|
|
|
| `users` | firstName, lastName, phoneNumber, email | - |
|
|
| `projects` | name, slug, description, logo_url, favicon_url, og_image_url | - |
|
|
| `assets` | name, cdn_url, storage_key, mime_type, checksum | size_mb, width_px, height_px, duration_sec |
|
|
| `asset_variants` | cdn_url | width_px, height_px, size_mb |
|
|
| `presigned_url_requests` | requested_key, mime_type, status | requested_size_mb |
|
|
| `tour_pages` | source_key, name, slug, background_image_url, background_video_url, background_audio_url, ui_schema_json | sort_order |
|
|
| `project_audio_tracks` | source_key, name, slug, url | volume, sort_order |
|
|
| `publish_events` | error_message | pages_copied, transitions_copied, audios_copied |
|
|
| `pwa_caches` | cache_version, manifest_json, asset_list_json | - |
|
|
| `access_logs` | path, ip_address, user_agent | - |
|
|
|
|
**Permission Check:** Search results are filtered based on user permissions. Only tables where user has `READ_{TABLE_NAME}` permission are searched.
|
|
|
|
**Limit:** Maximum 50 results per table.
|
|
|
|
---
|
|
|
|
## Bulk Operations
|
|
|
|
### POST /api/{entity}/deleteByIds
|
|
|
|
Bulk delete entities.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": ["uuid-1", "uuid-2", "uuid-3"]
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"deleted": 3
|
|
}
|
|
```
|
|
|
|
### POST /api/{entity}/bulk-import
|
|
|
|
Bulk import from CSV.
|
|
|
|
**Auth:** Required
|
|
|
|
**Content-Type:** `multipart/form-data`
|
|
|
|
**Form Fields:**
|
|
- `file` - CSV file
|
|
- `importHash` - Unique import identifier
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"imported": 50,
|
|
"errors": []
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## CSV Export
|
|
|
|
Add `?filetype=csv` to any list endpoint to export as CSV.
|
|
|
|
**Example:**
|
|
```
|
|
GET /api/projects?filetype=csv&limit=1000
|
|
```
|
|
|
|
**Response:** CSV file download with appropriate headers
|
|
|
|
---
|
|
|
|
## Swagger Documentation
|
|
|
|
Interactive API documentation available at:
|
|
|
|
```
|
|
GET /api-docs
|
|
```
|
|
|
|
Provides Swagger UI with all endpoints, request/response schemas, and testing capabilities.
|