39948-vm/documentation/api-reference.md
2026-07-03 16:11:24 +02:00

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.