1672 lines
38 KiB
Markdown
1672 lines
38 KiB
Markdown
# Backend API Endpoints Documentation
|
|
|
|
Complete reference for all API endpoints in the Tour Builder Platform backend.
|
|
|
|
## Overview
|
|
|
|
- **Base URL**: `http://localhost:3000/api` for local development
|
|
- **Content-Type**: `application/json`
|
|
- **Authentication**: JWT Bearer Token (except public endpoints)
|
|
- **API Documentation UI**: `http://localhost:3000/api-docs` (Swagger, local development)
|
|
|
|
**Standard VM note:** the VM `dev_stage` backend listens on port `3000`, while
|
|
the frontend listens on `3001` and Apache serves the public domain on port `80`.
|
|
Use `http://127.0.0.1:3000/api/...` for direct VM backend checks. Protected
|
|
routes returning `401 Unauthorized` without JWT are healthy. See
|
|
[`deployment-vm.md`](../../documentation/deployment-vm.md).
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Authentication](#1-authentication)
|
|
2. [Health & System](#2-health--system)
|
|
3. [Users](#3-users)
|
|
4. [Roles](#4-roles)
|
|
5. [Permissions](#5-permissions)
|
|
6. [Projects](#6-projects)
|
|
7. [Project Memberships](#7-project-memberships)
|
|
8. [Tour Pages](#8-tour-pages)
|
|
9. [Assets](#9-assets)
|
|
10. [Asset Variants](#10-asset-variants)
|
|
11. [Project Audio Tracks](#11-project-audio-tracks)
|
|
12. [Project Transition Settings](#12-project-transition-settings)
|
|
13. [Global UI Controls](#13-global-ui-controls)
|
|
14. [Element Defaults](#14-element-defaults)
|
|
15. [Publishing](#15-publishing)
|
|
16. [File Management](#16-file-management)
|
|
17. [Search](#17-search)
|
|
20. [Access Logs](#20-access-logs)
|
|
21. [Publish Events](#21-publish-events)
|
|
22. [PWA Caches](#22-pwa-caches)
|
|
23. [Presigned URL Requests](#23-presigned-url-requests)
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Authentication Header
|
|
|
|
```http
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
### Query Parameters (List Endpoints)
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `page` | number | Page number (0-indexed) |
|
|
| `limit` | number | Items per page (default: 10) |
|
|
| `field` | string | Field to sort by |
|
|
| `sort` | string | Sort direction: `asc` or `desc` |
|
|
| `filetype` | string | Set to `csv` for CSV export |
|
|
| `<field>` | string | Text search filter (ILIKE) |
|
|
| `<field>Range` | string | Range filter: `[min,max]` |
|
|
|
|
### Standard List Response
|
|
|
|
```json
|
|
{
|
|
"rows": [...],
|
|
"count": 100
|
|
}
|
|
```
|
|
|
|
### Rate Limits
|
|
|
|
| Endpoint Type | Limit | Window |
|
|
|---------------|-------|--------|
|
|
| Auth | 10 requests | 15 minutes |
|
|
| Signup | 5 requests | 1 hour |
|
|
| Password Reset | 5 requests | 1 hour |
|
|
| API (general) | 100 requests | 1 minute |
|
|
| Upload | 10 requests | 1 minute |
|
|
| Download | 200 requests | 1 minute |
|
|
| Search | 30 requests | 1 minute |
|
|
| AI | 20 requests | 1 minute |
|
|
|
|
### Rate Limit Headers
|
|
|
|
```http
|
|
X-RateLimit-Limit: 100
|
|
X-RateLimit-Remaining: 99
|
|
X-RateLimit-Reset: 2026-03-30T12:00:00.000Z
|
|
Retry-After: 60 # (only on 429)
|
|
```
|
|
|
|
---
|
|
|
|
## 1. Authentication
|
|
|
|
### POST /api/auth/signin/local
|
|
|
|
Sign in with email and password.
|
|
|
|
**Authentication**: None
|
|
**Rate Limit**: 10/15min
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
```
|
|
|
|
**Errors:**
|
|
- `400`: Invalid credentials
|
|
|
|
---
|
|
|
|
### Self-Registration
|
|
|
|
Self-registration is disabled. `POST /api/auth/signup` is not registered.
|
|
New users are created through the authenticated Users API/UI and receive an
|
|
invitation/setup link.
|
|
|
|
---
|
|
|
|
### GET /api/auth/me
|
|
|
|
Get current authenticated user.
|
|
|
|
**Authentication**: Required
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"phoneNumber": "+1234567890",
|
|
"emailVerified": true,
|
|
"disabled": false,
|
|
"app_role": {
|
|
"id": "uuid",
|
|
"name": "User",
|
|
"globalAccess": false
|
|
},
|
|
"custom_permissions": []
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### PUT /api/auth/password-reset
|
|
|
|
Reset password using token.
|
|
|
|
**Authentication**: None
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"token": "reset-token-from-email",
|
|
"password": "newPassword123"
|
|
}
|
|
```
|
|
|
|
**Response (200):** `true`
|
|
|
|
---
|
|
|
|
### POST /api/auth/send-password-reset-email
|
|
|
|
Send password reset email.
|
|
|
|
**Authentication**: None
|
|
**Rate Limit**: 5/hour
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
**Response (200):** `true`
|
|
|
|
---
|
|
|
|
### PUT /api/auth/password-update
|
|
|
|
Update password for authenticated user.
|
|
|
|
**Authentication**: Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"currentPassword": "oldPassword",
|
|
"newPassword": "newPassword123"
|
|
}
|
|
```
|
|
|
|
**Response (200):** `true`
|
|
|
|
---
|
|
|
|
### PUT /api/auth/profile
|
|
|
|
Update user profile.
|
|
|
|
**Authentication**: Required
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"profile": {
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"phoneNumber": "+1234567890"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200):** `true`
|
|
|
|
---
|
|
|
|
### PUT /api/auth/verify-email
|
|
|
|
Verify email address.
|
|
|
|
**Authentication**: None
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"token": "verification-token-from-email"
|
|
}
|
|
```
|
|
|
|
**Response (200):** JWT token
|
|
|
|
---
|
|
|
|
### GET /api/auth/email-configured
|
|
|
|
Check if email service is configured.
|
|
|
|
**Authentication**: None
|
|
|
|
**Response (200):** `true` or `false`
|
|
|
|
---
|
|
|
|
### GET /api/auth/signin/google
|
|
|
|
Initiate Google OAuth flow.
|
|
|
|
**Authentication**: None
|
|
|
|
**Query Parameters:**
|
|
- `app`: Optional state parameter
|
|
|
|
**Response**: Redirect to Google
|
|
|
|
---
|
|
|
|
### GET /api/auth/signin/google/callback
|
|
|
|
Google OAuth callback.
|
|
|
|
**Response**: Redirect to frontend with token
|
|
|
|
---
|
|
|
|
### GET /api/auth/signin/microsoft
|
|
|
|
Initiate Microsoft OAuth flow.
|
|
|
|
**Authentication**: None
|
|
|
|
**Response**: Redirect to Microsoft
|
|
|
|
---
|
|
|
|
### GET /api/auth/signin/microsoft/callback
|
|
|
|
Microsoft OAuth callback.
|
|
|
|
**Response**: Redirect to frontend with token
|
|
|
|
---
|
|
|
|
## 2. Health & System
|
|
|
|
### GET /api/health
|
|
|
|
Health check endpoint.
|
|
|
|
**Authentication**: None
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"timestamp": "2026-03-30T12:00:00.000Z",
|
|
"uptime": 12345.678,
|
|
"environment": "production",
|
|
"database": "connected"
|
|
}
|
|
```
|
|
|
|
**Response (503):** (when database is down)
|
|
```json
|
|
{
|
|
"status": "degraded",
|
|
"timestamp": "2026-03-30T12:00:00.000Z",
|
|
"uptime": 12345.678,
|
|
"environment": "production",
|
|
"database": "disconnected",
|
|
"databaseError": "Connection refused"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/runtime-context
|
|
|
|
Get current runtime context (environment detection).
|
|
|
|
**Authentication**: None
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"mode": "workspace",
|
|
"projectSlug": "my-project",
|
|
"headerEnvironment": "production"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Users
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/users` | Create user |
|
|
| GET | `/api/users` | List users |
|
|
| GET | `/api/users/count` | Count users |
|
|
| GET | `/api/users/autocomplete` | Autocomplete search |
|
|
| GET | `/api/users/:id` | Get user by ID |
|
|
| PUT | `/api/users/:id` | Update user |
|
|
| DELETE | `/api/users/:id` | Delete user |
|
|
| POST | `/api/users/deleteByIds` | Bulk delete |
|
|
| POST | `/api/users/bulk-import` | Bulk import |
|
|
|
|
**Authentication**: Required
|
|
**Permissions**: `CREATE_USERS`, `READ_USERS`, `UPDATE_USERS`, `DELETE_USERS`
|
|
|
|
### POST /api/users
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"email": "john@example.com",
|
|
"phoneNumber": "+1234567890",
|
|
"app_role": "uuid-of-role",
|
|
"custom_permissions": ["uuid-1", "uuid-2"],
|
|
"password": "password123",
|
|
"disabled": false
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/users
|
|
|
|
**Query Parameters:**
|
|
- `firstName`: Filter by first name
|
|
- `lastName`: Filter by last name
|
|
- `email`: Filter by email
|
|
- `app_role`: Filter by role ID
|
|
- Standard pagination params
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "uuid",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"email": "john@example.com",
|
|
"phoneNumber": "+1234567890",
|
|
"disabled": false,
|
|
"emailVerified": true,
|
|
"app_role": { "id": "uuid", "name": "User" }
|
|
}
|
|
],
|
|
"count": 1
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Roles
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/roles` | Create role |
|
|
| GET | `/api/roles` | List roles |
|
|
| GET | `/api/roles/count` | Count roles |
|
|
| GET | `/api/roles/autocomplete` | Autocomplete search |
|
|
| GET | `/api/roles/:id` | Get role by ID |
|
|
| PUT | `/api/roles/:id` | Update role |
|
|
| DELETE | `/api/roles/:id` | Delete role |
|
|
| POST | `/api/roles/deleteByIds` | Bulk delete |
|
|
|
|
**Permissions**: `CREATE_ROLES`, `READ_ROLES`, `UPDATE_ROLES`, `DELETE_ROLES`
|
|
|
|
### POST /api/roles
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Editor",
|
|
"globalAccess": false,
|
|
"permissions": ["uuid-1", "uuid-2"]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Permissions
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/permissions` | Create permission |
|
|
| GET | `/api/permissions` | List permissions |
|
|
| GET | `/api/permissions/count` | Count permissions |
|
|
| GET | `/api/permissions/autocomplete` | Autocomplete search |
|
|
| GET | `/api/permissions/:id` | Get permission by ID |
|
|
| PUT | `/api/permissions/:id` | Update permission |
|
|
| DELETE | `/api/permissions/:id` | Delete permission |
|
|
| POST | `/api/permissions/deleteByIds` | Bulk delete |
|
|
|
|
**Permissions**: `CREATE_PERMISSIONS`, `READ_PERMISSIONS`, `UPDATE_PERMISSIONS`, `DELETE_PERMISSIONS`
|
|
|
|
### POST /api/permissions
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "READ_CUSTOM_FEATURE"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Projects
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/projects` | Create project |
|
|
| GET | `/api/projects` | List projects |
|
|
| GET | `/api/projects/count` | Count projects |
|
|
| GET | `/api/projects/autocomplete` | Autocomplete search |
|
|
| GET | `/api/projects/:id` | Get project by ID |
|
|
| PUT | `/api/projects/:id` | Update project |
|
|
| DELETE | `/api/projects/:id` | Delete project |
|
|
| POST | `/api/projects/deleteByIds` | Bulk delete |
|
|
|
|
**Permissions**: `CREATE_PROJECTS`, `READ_PROJECTS`, `UPDATE_PROJECTS`, `DELETE_PROJECTS`
|
|
|
|
**Runtime Public Access**: GET endpoints accessible without auth in production mode.
|
|
|
|
### POST /api/projects
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "My Tour",
|
|
"slug": "my-tour",
|
|
"description": "A virtual tour of the museum",
|
|
"logo_url": "assets/logo.png",
|
|
"favicon_url": "assets/favicon.ico",
|
|
"og_image_url": "assets/og-image.jpg"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `name` | string | Project display name |
|
|
| `slug` | string | URL-safe identifier (auto-generated if not provided) |
|
|
| `description` | string | Project description |
|
|
| `logo_url` | string | Path to project logo |
|
|
| `favicon_url` | string | Path to favicon |
|
|
| `og_image_url` | string | Path to Open Graph image |
|
|
|
|
---
|
|
|
|
### POST /api/projects/:id/clone
|
|
|
|
Clone an existing project with all pages and settings.
|
|
|
|
**Request:** None (uses URL parameter)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "new-project-uuid",
|
|
"name": "My Tour (Copy)",
|
|
"slug": "my-tour-copy"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/projects/:id/offline-manifest
|
|
|
|
Get PWA offline manifest for a project.
|
|
|
|
**Query Parameters:**
|
|
- `variant`: `mobile` or `desktop` (default: `desktop`)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"projectId": "uuid",
|
|
"version": "2026-03-30T12:00:00.000Z",
|
|
"assets": [
|
|
{
|
|
"url": "assets/image.jpg",
|
|
"type": "image",
|
|
"size": 1024000
|
|
}
|
|
],
|
|
"pages": [
|
|
{
|
|
"slug": "home",
|
|
"name": "Home Page"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Project Memberships
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/project_memberships` | Create membership |
|
|
| GET | `/api/project_memberships` | List memberships |
|
|
| GET | `/api/project_memberships/count` | Count memberships |
|
|
| GET | `/api/project_memberships/:id` | Get membership by ID |
|
|
| PUT | `/api/project_memberships/:id` | Update membership |
|
|
| DELETE | `/api/project_memberships/:id` | Delete membership |
|
|
|
|
**Permissions**: `CREATE_PROJECT_MEMBERSHIPS`, `READ_PROJECT_MEMBERSHIPS`, etc.
|
|
|
|
### POST /api/project_memberships
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"user": "user-uuid",
|
|
"project": "project-uuid",
|
|
"membershipRole": "editor"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Membership Roles:**
|
|
- `owner` - Full access including delete
|
|
- `editor` - Can edit content
|
|
- `viewer` - Read-only access
|
|
|
|
---
|
|
|
|
## 8. Tour Pages
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/tour_pages` | Create page |
|
|
| GET | `/api/tour_pages` | List pages |
|
|
| GET | `/api/tour_pages/count` | Count pages |
|
|
| GET | `/api/tour_pages/:id` | Get page by ID |
|
|
| PUT | `/api/tour_pages/:id` | Update page |
|
|
| DELETE | `/api/tour_pages/:id` | Delete page |
|
|
| POST | `/api/tour_pages/deleteByIds` | Bulk delete |
|
|
| POST | `/api/tour_pages/reorder` | Reorder pages by updating `sort_order` only |
|
|
| POST | `/api/tour_pages/:id/duplicate` | Duplicate a dev page as a new independent dev page |
|
|
|
|
**Permissions**: `CREATE_TOUR_PAGES`, `READ_TOUR_PAGES`, etc.
|
|
|
|
**Runtime Public Access**: GET endpoints accessible without auth in production mode.
|
|
|
|
### POST /api/tour_pages/reorder
|
|
|
|
Reorders pages for a project in the constructor environment.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"orderedPageIds": ["page-uuid-1", "page-uuid-2", "page-uuid-3"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
[
|
|
{ "id": "page-uuid-1", "sort_order": 1 },
|
|
{ "id": "page-uuid-2", "sort_order": 2 },
|
|
{ "id": "page-uuid-3", "sort_order": 3 }
|
|
]
|
|
```
|
|
|
|
**Rules:**
|
|
- Only `environment: "dev"` is accepted. Stage and production are updated by
|
|
publishing, not by direct reorder writes.
|
|
- `orderedPageIds` must include every page in the project/dev environment
|
|
exactly once.
|
|
- The endpoint updates only `tour_pages.sort_order`; it does not alter
|
|
`ui_schema_json`, slugs, backgrounds, media, navigation, or transitions.
|
|
- The visible stage order changes after Save to Stage. The visible production
|
|
order changes after Publish.
|
|
|
|
**Validation failures:**
|
|
- Missing `projectId`
|
|
- Empty or invalid `orderedPageIds`
|
|
- Duplicate page IDs
|
|
- Missing pages from the ordered list
|
|
- Unknown page IDs or page IDs outside the project/environment
|
|
- Any environment other than `dev`
|
|
|
|
### POST /api/tour_pages/:id/duplicate
|
|
|
|
Duplicates a constructor/dev page into a new independent dev page.
|
|
|
|
**Auth:** Required
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"name": "Lobby Copy",
|
|
"slug": "lobby-copy"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Rules:**
|
|
- Only source pages in `environment: "dev"` can be duplicated.
|
|
- The requested target environment must also be `dev`.
|
|
- The source page must belong to the requested project.
|
|
- The new page is appended to constructor order with
|
|
`sort_order = max(project dev sort_order) + 1`.
|
|
- The duplicate receives a new page ID, unique slug, and empty `source_key`.
|
|
- Page settings, background media fields, design dimensions, `requires_auth`,
|
|
and `ui_schema_json` are copied.
|
|
- Inline `ui_schema_json.elements[]` IDs and nested gallery/carousel/info-panel
|
|
item IDs are regenerated for independence.
|
|
- Asset URLs, transition URLs, and navigation `targetPageSlug` references are
|
|
preserved.
|
|
- Reverse-video processing uses the existing `TourPagesService` path.
|
|
|
|
**Validation failures:**
|
|
- Invalid source page ID
|
|
- Source page not found
|
|
- Source page outside requested project
|
|
- Source or target environment other than `dev`
|
|
|
|
### POST /api/tour_pages
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"name": "Home Page",
|
|
"slug": "home",
|
|
"sort_order": 1,
|
|
"background_image_url": "assets/bg.jpg",
|
|
"background_video_url": null,
|
|
"background_audio_url": "assets/ambient.mp3",
|
|
"background_loop": true,
|
|
"requires_auth": false,
|
|
"ui_schema_json": {
|
|
"elements": [],
|
|
"canvasSettings": {}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**ui_schema_json Structure:**
|
|
```json
|
|
{
|
|
"elements": [
|
|
{
|
|
"id": "element-uuid",
|
|
"type": "button",
|
|
"props": {
|
|
"x": 100,
|
|
"y": 200,
|
|
"width": 150,
|
|
"height": 50,
|
|
"label": "Click Me",
|
|
"targetPageSlug": "next-page",
|
|
"transitionVideo": "assets/transition.mp4"
|
|
}
|
|
}
|
|
],
|
|
"canvasSettings": {
|
|
"width": 1920,
|
|
"height": 1080
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Assets
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/assets` | Create asset record |
|
|
| GET | `/api/assets` | List assets |
|
|
| GET | `/api/assets/count` | Count assets |
|
|
| GET | `/api/assets/autocomplete` | Autocomplete search |
|
|
| GET | `/api/assets/:id` | Get asset by ID |
|
|
| PUT | `/api/assets/:id` | Update asset |
|
|
| DELETE | `/api/assets/:id` | Delete asset |
|
|
| POST | `/api/assets/deleteByIds` | Bulk delete |
|
|
|
|
**Permissions**: `CREATE_ASSETS`, `READ_ASSETS`, etc.
|
|
|
|
### POST /api/assets
|
|
|
|
Create an asset record with MIME type validation.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Hero Image",
|
|
"asset_type": "image",
|
|
"type": "background",
|
|
"cdn_url": "https://cdn.example.com/assets/hero.jpg",
|
|
"storage_key": "assets/hero.jpg",
|
|
"mime_type": "image/jpeg",
|
|
"size_mb": 2.5,
|
|
"width_px": 1920,
|
|
"height_px": 1080,
|
|
"duration_sec": null,
|
|
"checksum": "abc123...",
|
|
"is_public": true,
|
|
"project": "project-uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Asset Types:** `image`, `video`, `audio`, `document`, `other`
|
|
|
|
**Type (Usage):** `background`, `transition`, `element`, `general`
|
|
|
|
**MIME Type Validation:**
|
|
|
|
The `mime_type` must match the `asset_type`:
|
|
|
|
| asset_type | Valid mime_type prefixes |
|
|
|------------|-------------------------|
|
|
| `image` | `image/` (jpeg, png, gif, webp, svg, etc.) |
|
|
| `video` | `video/` (mp4, webm, mov, etc.) |
|
|
| `audio` | `audio/` (mp3, wav, ogg, etc.) |
|
|
|
|
Other asset types (`document`, `other`, `file`) skip MIME validation.
|
|
|
|
**Error Response (400):**
|
|
```json
|
|
{
|
|
"message": "Invalid file type for image. Expected image (jpeg, png, gif, webp, svg, etc.), got \"video/mp4\""
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Asset Variants
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/asset_variants` | Create variant |
|
|
| GET | `/api/asset_variants` | List variants |
|
|
| GET | `/api/asset_variants/:id` | Get variant by ID |
|
|
| PUT | `/api/asset_variants/:id` | Update variant |
|
|
| DELETE | `/api/asset_variants/:id` | Delete variant |
|
|
|
|
**Permissions**: `CREATE_ASSET_VARIANTS`, `READ_ASSET_VARIANTS`, etc.
|
|
|
|
### POST /api/asset_variants
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"asset": "asset-uuid",
|
|
"variant_type": "mobile",
|
|
"cdn_url": "https://cdn.example.com/assets/hero-mobile.jpg",
|
|
"width_px": 768,
|
|
"height_px": 432,
|
|
"size_mb": 0.8
|
|
}
|
|
}
|
|
```
|
|
|
|
**Variant Types:** `desktop`, `mobile`, `thumbnail`
|
|
|
|
---
|
|
|
|
## 11. Project Audio Tracks
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/project_audio_tracks` | Create audio track |
|
|
| GET | `/api/project_audio_tracks` | List audio tracks |
|
|
| GET | `/api/project_audio_tracks/:id` | Get audio track |
|
|
| PUT | `/api/project_audio_tracks/:id` | Update audio track |
|
|
| DELETE | `/api/project_audio_tracks/:id` | Delete audio track |
|
|
|
|
**Runtime Public Access**: GET endpoints accessible without auth in production mode.
|
|
|
|
### POST /api/project_audio_tracks
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"name": "Background Music",
|
|
"slug": "background-music",
|
|
"url": "assets/music.mp3",
|
|
"loop": true,
|
|
"volume": 0.5,
|
|
"sort_order": 1,
|
|
"is_enabled": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Project Transition Settings
|
|
|
|
Environment-aware CSS transition settings for page navigation.
|
|
|
|
### Authentication Model
|
|
|
|
Uses **URL-path-based public access** - no headers required:
|
|
|
|
| Endpoint | Method | Environment | Auth Required |
|
|
|----------|--------|-------------|---------------|
|
|
| `/project/:id/env/production` | GET | production | **No** (public) |
|
|
| `/project/:id/env/dev` | GET | dev | JWT + READ_PAGE_ELEMENTS |
|
|
| `/project/:id/env/stage` | GET | stage | JWT + READ_PAGE_ELEMENTS |
|
|
| `/project/:id/env/*` | PUT/DELETE | any | JWT + UPDATE_PAGE_ELEMENTS |
|
|
| Standard CRUD | all | n/a | JWT + PAGE_ELEMENTS CRUD permission |
|
|
|
|
This allows public presentations (`/p/[slug]`) to fetch production transition settings without authentication in incognito mode.
|
|
|
|
### Standard CRUD Endpoints (All Require Auth)
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/project-transition-settings` | Create settings |
|
|
| GET | `/api/project-transition-settings` | List all settings |
|
|
| GET | `/api/project-transition-settings/:id` | Get by ID |
|
|
| PUT | `/api/project-transition-settings/:id` | Update by ID |
|
|
| DELETE | `/api/project-transition-settings/:id` | Delete by ID |
|
|
|
|
### Environment-Specific Endpoints
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| GET | `/project/:projectId/env/production` | None | Get production settings (public) |
|
|
| GET | `/project/:projectId/env/dev` | JWT | Get dev settings |
|
|
| GET | `/project/:projectId/env/stage` | JWT | Get stage settings |
|
|
| PUT | `/project/:projectId/env/:environment` | JWT + UPDATE_PAGE_ELEMENTS | Create or update (upsert) |
|
|
| DELETE | `/project/:projectId/env/:environment` | JWT + UPDATE_PAGE_ELEMENTS | Reset settings to global defaults |
|
|
|
|
### GET /api/project-transition-settings/project/:projectId/env/:environment
|
|
|
|
**Parameters:**
|
|
- `projectId` (path): Project UUID
|
|
- `environment` (path): `dev`, `stage`, or `production`
|
|
|
|
**Authentication:**
|
|
- `production`: None required (public access)
|
|
- `dev`/`stage`: `Authorization: Bearer {token}`
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"projectId": "project-uuid",
|
|
"environment": "production",
|
|
"transition_type": "fade",
|
|
"duration_ms": 700,
|
|
"easing": "ease-in-out",
|
|
"overlay_color": "#000000",
|
|
"createdAt": "2026-05-01T12:00:00Z",
|
|
"updatedAt": "2026-05-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
**Response (200):** `null` when no settings exist (use global/fallback defaults)
|
|
|
|
**Response (401):** Authentication required (for dev/stage without JWT)
|
|
|
|
### PUT /api/project-transition-settings/project/:projectId/env/:environment
|
|
|
|
Creates or updates settings for a project/environment combination.
|
|
|
|
**Authentication**: Required (JWT + UPDATE_PAGE_ELEMENTS)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"transition_type": "fade",
|
|
"duration_ms": 1000,
|
|
"easing": "ease-out",
|
|
"overlay_color": "#1a1a1a"
|
|
}
|
|
```
|
|
|
|
**Response (200):** Created or updated settings object
|
|
|
|
---
|
|
|
|
## 13. Global UI Controls
|
|
|
|
Configurable fullscreen, global sound, and offline buttons. Settings cascade
|
|
from global defaults to project/environment overrides and then page overrides.
|
|
|
|
### Global Defaults
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| GET | `/api/global-ui-control-defaults` | None | Get singleton global UI-control defaults |
|
|
| GET | `/api/global-ui-control-defaults/:id` | None | Get defaults by ID |
|
|
| PUT | `/api/global-ui-control-defaults/:id` | JWT + UPDATE_PAGE_ELEMENTS | Update singleton defaults |
|
|
|
|
### Project/Environment Overrides
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| GET | `/api/project-ui-control-settings/project/:projectId/env/production` | None or JWT for private production | Get production project overrides |
|
|
| GET | `/api/project-ui-control-settings/project/:projectId/env/dev` | JWT | Get dev project overrides |
|
|
| GET | `/api/project-ui-control-settings/project/:projectId/env/stage` | JWT | Get stage project overrides |
|
|
| PUT | `/api/project-ui-control-settings/project/:projectId/env/:environment` | JWT + UPDATE_PAGE_ELEMENTS | Upsert project overrides |
|
|
| DELETE | `/api/project-ui-control-settings/project/:projectId/env/:environment` | JWT + UPDATE_PAGE_ELEMENTS | Reset project overrides to global defaults |
|
|
|
|
`production` reads follow private-production presentation rules: public
|
|
presentations are public-readable, private presentations require JWT and an
|
|
allowlist/staff access check.
|
|
|
|
### Settings JSON
|
|
|
|
```json
|
|
{
|
|
"offline": {
|
|
"enabled": true,
|
|
"hidden": false,
|
|
"xPercent": 89.5,
|
|
"yPercent": 6,
|
|
"anchor": "center",
|
|
"buttonSizePercent": 2.6,
|
|
"iconSizePercent": 1.35,
|
|
"defaultIconUrl": "",
|
|
"activeIconUrl": "",
|
|
"defaultBackgroundColor": "#2563EB",
|
|
"activeBackgroundColor": "#059669",
|
|
"hoverBackgroundColor": "#1D4ED8",
|
|
"color": "#FFFFFF",
|
|
"defaultBorderColor": "#2563EB",
|
|
"activeBorderColor": "#059669",
|
|
"borderRadiusPercent": 0.42,
|
|
"opacity": 1,
|
|
"boxShadow": "",
|
|
"zIndex": 900,
|
|
"order": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Element Defaults
|
|
|
|
### Global Transition Defaults
|
|
|
|
Platform-wide default transition settings (singleton).
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| GET | `/api/global-transition-defaults` | **None** | Get defaults (public) |
|
|
| GET | `/api/global-transition-defaults/:id` | **None** | Get by ID (public) |
|
|
| PUT | `/api/global-transition-defaults/:id` | JWT + UPDATE_PAGE_ELEMENTS | Update defaults |
|
|
|
|
**Authentication Model**: GET is always public (for runtime presentations), PUT requires JWT.
|
|
|
|
**GET /api/global-transition-defaults**
|
|
|
|
```http
|
|
GET /api/global-transition-defaults
|
|
# No authentication required
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"transition_type": "fade",
|
|
"duration_ms": 700,
|
|
"easing": "ease-in-out",
|
|
"overlay_color": "#000000"
|
|
}
|
|
```
|
|
|
|
**PUT /api/global-transition-defaults/:id**
|
|
|
|
```http
|
|
PUT /api/global-transition-defaults/:id
|
|
Authorization: Bearer {token}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"data": {
|
|
"transition_type": "fade",
|
|
"duration_ms": 1000,
|
|
"easing": "ease-out"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Element Type Defaults (Global)
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/element-type-defaults` | List global defaults |
|
|
| GET | `/api/element-type-defaults/:id` | Get default by ID |
|
|
| PUT | `/api/element-type-defaults/:id` | Update default |
|
|
|
|
**Alternative Path:** `/api/ui-elements` (backwards compatibility)
|
|
|
|
**Element Types (11 predefined):**
|
|
- `button`, `hotspot`, `tooltip`, `gallery`
|
|
- `media_player`, `text_block`, `popup`
|
|
- `logo`, `spot`, `hamburger_menu`, `image`
|
|
|
|
---
|
|
|
|
### Project Element Defaults
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/project-element-defaults` | List project defaults |
|
|
| GET | `/api/project-element-defaults/:id` | Get default by ID |
|
|
| PUT | `/api/project-element-defaults/:id` | Update default |
|
|
| POST | `/api/project-element-defaults/:id/reset` | Reset to global |
|
|
| GET | `/api/project-element-defaults/:id/diff` | Compare with global |
|
|
|
|
### POST /api/project-element-defaults/:id/reset
|
|
|
|
Reset project element default to current global settings.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"element_type": "button",
|
|
"projectId": "project-uuid",
|
|
"default_props_json": {...}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/project-element-defaults/:id/diff
|
|
|
|
Compare project default with global default.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"hasDifferences": true,
|
|
"projectDefault": {...},
|
|
"globalDefault": {...},
|
|
"differences": {
|
|
"backgroundColor": {
|
|
"project": "#FF0000",
|
|
"global": "#0000FF"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 15. Publishing
|
|
|
|
### POST /api/publish
|
|
|
|
Publish from stage to production.
|
|
|
|
**Authentication**: Required
|
|
**Permissions**: `CREATE_PUBLISH_EVENTS`
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": "project-uuid",
|
|
"title": "Version 1.0 Release",
|
|
"description": "Initial public release with all pages"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"publishEventId": "event-uuid",
|
|
"summary": {
|
|
"pages_copied": 10,
|
|
"audios_copied": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /api/publish/save-to-stage
|
|
|
|
Copy dev content to stage environment.
|
|
|
|
**Authentication**: Required
|
|
**Permissions**: `CREATE_PUBLISH_EVENTS`
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": "project-uuid"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"publishEventId": "event-uuid",
|
|
"summary": {
|
|
"pages_copied": 10,
|
|
"audios_copied": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
- `400`: Publish already in progress
|
|
- `404`: Project not found
|
|
|
|
---
|
|
|
|
## 16. File Management
|
|
|
|
### GET /api/file/download
|
|
|
|
Download a file from storage. Supports automatic client disconnect handling via AbortController.
|
|
|
|
**Authentication**: None (uses private URL)
|
|
**Rate Limit**: 200/min
|
|
|
|
**Query Parameters:**
|
|
- `privateUrl`: Storage key (e.g., `assets/image.jpg`)
|
|
|
|
**Response:** File stream with appropriate Content-Type
|
|
|
|
**Error Responses:**
|
|
|
|
| HTTP Status | Condition | S3 Error Types |
|
|
|-------------|-----------|----------------|
|
|
| 400 | Missing privateUrl parameter | - |
|
|
| 401 | Expired credentials | ExpiredToken |
|
|
| 403 | Access denied | AccessDenied, InvalidAccessKeyId |
|
|
| 404 | File not found | NoSuchKey, NotFound, NoSuchBucket |
|
|
| 429 | Rate limited | ThrottlingException |
|
|
| 500 | Internal server error | InternalError |
|
|
| 503 | Service unavailable | NetworkingError, ServiceUnavailable |
|
|
| 504 | Gateway timeout | TimeoutError, RequestTimeout |
|
|
|
|
**Error Response Format:**
|
|
```json
|
|
{
|
|
"message": "Could not download the file. NoSuchKey: The specified key does not exist."
|
|
}
|
|
```
|
|
|
|
**Client Disconnect:** When a client disconnects during download, the backend automatically aborts the S3 request to prevent wasted bandwidth.
|
|
|
|
---
|
|
|
|
### POST /api/file/presign
|
|
|
|
Generate presigned URLs for direct S3 access. Includes path validation to prevent directory traversal attacks.
|
|
|
|
**Authentication**: None
|
|
**Rate Limit**: 200/min
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"urls": [
|
|
"assets/image1.jpg",
|
|
"assets/image2.jpg",
|
|
"assets/video.mp4"
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"presignedUrls": {
|
|
"assets/image1.jpg": "https://bucket.s3.amazonaws.com/assets/image1.jpg?...",
|
|
"assets/image2.jpg": "https://bucket.s3.amazonaws.com/assets/image2.jpg?...",
|
|
"assets/video.mp4": "https://bucket.s3.amazonaws.com/assets/video.mp4?..."
|
|
}
|
|
}
|
|
```
|
|
|
|
**Limits:**
|
|
- Maximum 50 URLs per request
|
|
- Presigned URLs expire in 1 hour (configurable via `AWS_S3_PRESIGN_EXPIRY`)
|
|
|
|
**Path Validation:**
|
|
URLs are validated to prevent path traversal and ensure security:
|
|
- Must be non-empty strings
|
|
- Must not contain `..` (parent directory traversal)
|
|
- Must not start with `/` (absolute paths)
|
|
- Must not contain null bytes
|
|
|
|
**Error Responses:**
|
|
|
|
| HTTP Status | Condition |
|
|
|-------------|-----------|
|
|
| 400 | Missing `urls` array |
|
|
| 400 | `urls` array is empty |
|
|
| 400 | `urls` exceeds maximum of 50 |
|
|
| 400 | Invalid URL format (contains `..`, starts with `/`, etc.) |
|
|
| 500 | S3 presigning failed |
|
|
| 503 | S3 service unavailable |
|
|
|
|
**Error Response Format:**
|
|
```json
|
|
{
|
|
"message": "Invalid URL format",
|
|
"code": "INVALID_PATH",
|
|
"details": { "invalidUrls": ["../etc/passwd", "/root/secret"] }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /api/file/upload/:table/:field
|
|
|
|
Upload a file (single request).
|
|
|
|
**Authentication**: Required
|
|
**Rate Limit**: 10/min
|
|
**Content-Type**: `multipart/form-data`
|
|
|
|
**Form Fields:**
|
|
- `file`: Binary file data
|
|
- `filename`: Target filename
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Uploaded the file successfully: assets/hero.jpg",
|
|
"url": "https://bucket.s3.amazonaws.com/assets/hero.jpg"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Chunked Upload (Large Files)
|
|
|
|
For files larger than a few MB, use chunked upload:
|
|
|
|
#### 1. Initialize Session
|
|
|
|
**POST /api/file/upload-sessions/init**
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"folder": "assets",
|
|
"filename": "large-video.mp4",
|
|
"totalChunks": 10,
|
|
"size": 104857600,
|
|
"contentType": "video/mp4"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"sessionId": "session-uuid",
|
|
"uploadedChunks": [],
|
|
"totalChunks": 10
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. Upload Chunks
|
|
|
|
**PUT /api/file/upload-sessions/:sessionId/chunks/:chunkIndex**
|
|
|
|
**Content-Type**: `application/octet-stream`
|
|
**Body**: Raw binary chunk data
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"sessionId": "session-uuid",
|
|
"chunkIndex": 0,
|
|
"uploadedChunks": 1,
|
|
"totalChunks": 10
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. Check Session Status
|
|
|
|
**GET /api/file/upload-sessions/:sessionId**
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"sessionId": "session-uuid",
|
|
"totalChunks": 10,
|
|
"uploadedChunks": [0, 1, 2, 3, 4],
|
|
"status": "uploading"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 4. Finalize Upload
|
|
|
|
**POST /api/file/upload-sessions/:sessionId/finalize**
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Uploaded the file successfully: assets/large-video.mp4",
|
|
"privateUrl": "assets/large-video.mp4",
|
|
"url": "https://bucket.s3.amazonaws.com/assets/large-video.mp4"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 17. Search
|
|
|
|
### POST /api/search
|
|
|
|
Global full-text search across entities.
|
|
|
|
**Authentication**: Required
|
|
**Rate Limit**: 30/min
|
|
**Permissions**: `READ_SEARCH` (implicit via entity permissions)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"searchQuery": "museum"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid",
|
|
"name": "Museum Tour",
|
|
"tableName": "projects",
|
|
"matchAttribute": ["name", "description"]
|
|
},
|
|
{
|
|
"id": "uuid",
|
|
"slug": "museum-entrance",
|
|
"tableName": "tour_pages",
|
|
"matchAttribute": ["slug"]
|
|
}
|
|
]
|
|
```
|
|
|
|
**Searchable Tables:**
|
|
|
|
| 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 | - |
|
|
|
|
---
|
|
|
|
## 20. Access Logs
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/access_logs` | List access logs |
|
|
| GET | `/api/access_logs/count` | Count access logs |
|
|
| GET | `/api/access_logs/:id` | Get access log by ID |
|
|
|
|
**Permissions**: `READ_ACCESS_LOGS`
|
|
|
|
### GET /api/access_logs
|
|
|
|
**Query Parameters:**
|
|
- `path`: Filter by path
|
|
- `ip_address`: Filter by IP
|
|
- `user_agent`: Filter by user agent
|
|
- Standard pagination params
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "uuid",
|
|
"path": "/p/my-tour/",
|
|
"ip_address": "192.168.1.1",
|
|
"user_agent": "Mozilla/5.0...",
|
|
"project": { "id": "uuid", "name": "My Tour" },
|
|
"createdAt": "2026-03-30T12:00:00.000Z"
|
|
}
|
|
],
|
|
"count": 100
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 21. Publish Events
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/publish_events` | List publish events |
|
|
| GET | `/api/publish_events/count` | Count publish events |
|
|
| GET | `/api/publish_events/:id` | Get publish event by ID |
|
|
|
|
**Permissions**: `READ_PUBLISH_EVENTS`
|
|
|
|
### GET /api/publish_events
|
|
|
|
**Query Parameters:**
|
|
- `project`: Filter by project ID
|
|
- `status`: Filter by status (queued, running, success, failed)
|
|
- `from_environment`: Filter by source (dev, stage)
|
|
- `to_environment`: Filter by target (stage, production)
|
|
- Standard pagination params
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [
|
|
{
|
|
"id": "uuid",
|
|
"title": "Version 1.0",
|
|
"description": "Initial release",
|
|
"from_environment": "stage",
|
|
"to_environment": "production",
|
|
"status": "success",
|
|
"pages_copied": 10,
|
|
"audios_copied": 3,
|
|
"started_at": "2026-03-30T12:00:00.000Z",
|
|
"finished_at": "2026-03-30T12:00:05.000Z",
|
|
"project": { "id": "uuid", "name": "My Tour" },
|
|
"user": { "id": "uuid", "firstName": "John" }
|
|
}
|
|
],
|
|
"count": 5
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 22. PWA Caches
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/pwa_caches` | Create PWA cache record |
|
|
| GET | `/api/pwa_caches` | List PWA caches |
|
|
| GET | `/api/pwa_caches/:id` | Get PWA cache by ID |
|
|
| PUT | `/api/pwa_caches/:id` | Update PWA cache |
|
|
| DELETE | `/api/pwa_caches/:id` | Delete PWA cache |
|
|
|
|
**Permissions**: `CREATE_PWA_CACHES`, `READ_PWA_CACHES`, etc.
|
|
|
|
### POST /api/pwa_caches
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"project": "project-uuid",
|
|
"cache_version": "v1.0.0",
|
|
"manifest_json": {
|
|
"name": "My Tour",
|
|
"short_name": "Tour"
|
|
},
|
|
"asset_list_json": [
|
|
"assets/image1.jpg",
|
|
"assets/image2.jpg"
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 23. Presigned URL Requests
|
|
|
|
### Standard CRUD Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/presigned_url_requests` | Create presign request |
|
|
| GET | `/api/presigned_url_requests` | List presign requests |
|
|
| GET | `/api/presigned_url_requests/:id` | Get presign request |
|
|
| PUT | `/api/presigned_url_requests/:id` | Update presign request |
|
|
| DELETE | `/api/presigned_url_requests/:id` | Delete presign request |
|
|
|
|
**Permissions**: `CREATE_PRESIGNED_URL_REQUESTS`, `READ_PRESIGNED_URL_REQUESTS`, etc.
|
|
|
|
### POST /api/presigned_url_requests
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"requested_key": "assets/new-upload.jpg",
|
|
"mime_type": "image/jpeg",
|
|
"requested_size_mb": 2.5,
|
|
"status": "pending",
|
|
"project": "project-uuid",
|
|
"user": "user-uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Codes Reference
|
|
|
|
| Code | Message | Description |
|
|
|------|---------|-------------|
|
|
| 400 | Bad Request | Invalid input data or validation error |
|
|
| 401 | Unauthorized | Missing or invalid JWT token |
|
|
| 403 | Forbidden | Insufficient permissions |
|
|
| 404 | Not Found | Resource does not exist |
|
|
| 409 | Conflict | Resource conflict (e.g., duplicate) |
|
|
| 422 | Unprocessable Entity | Validation failed |
|
|
| 429 | Too Many Requests | Rate limit exceeded |
|
|
| 500 | Internal Server Error | Unexpected server error |
|
|
|
|
---
|
|
|
|
## Headers Reference
|
|
|
|
### Request Headers
|
|
|
|
| Header | Description | Required |
|
|
|--------|-------------|----------|
|
|
| `Authorization` | `Bearer <token>` | For protected endpoints |
|
|
| `Content-Type` | `application/json` | For JSON bodies |
|
|
| `X-Runtime-Environment` | `dev`, `stage`, or `production` | For runtime context |
|
|
| `X-Request-Id` | UUID for request tracing | Optional |
|
|
|
|
### Response Headers
|
|
|
|
| Header | Description |
|
|
|--------|-------------|
|
|
| `X-Request-Id` | Request ID for tracing |
|
|
| `X-RateLimit-Limit` | Rate limit maximum |
|
|
| `X-RateLimit-Remaining` | Remaining requests |
|
|
| `X-RateLimit-Reset` | Reset timestamp |
|
|
| `Retry-After` | Seconds until rate limit resets (on 429) |
|
|
| `Cross-Origin-Resource-Policy` | `cross-origin` (on file downloads) |
|