# 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 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.