# Builderly API reference

Base URL: http://bulgariandebateassociation.max-media.io
Generated from the running server on 2026-11-09T16:55:30.339Z. Every mounted route is listed with the access it needs, the parameters its handler reads and the data models it uses.

## Authentication

Send `Authorization: Bearer <token>` where a route needs a token. Add `?ionic=true` to get JSON from routes that also render pages.

- **Admin token** (`admin`): Bearer token of a site admin (Authorization: Bearer <token>).
- **User token** (`user`): Bearer token of a signed-in user of the site.
- **Optional user** (`optional-user`): Works without a token; a user token personalises the answer.
- **User with access** (`user-priv`): A signed-in user whose plan grants access to the item.
- **Page access** (`page-priv`): Pages limited to some users check the token when one is sent.
- **Webhook signature** (`webhook`): Called by a provider; verified by its signature.
- **reCAPTCHA** (`recaptcha`): Needs a reCAPTCHA token from the site form.
- **System** (`system`): Internal - called by the platform itself.

### For AI assistants: getting a token

Public endpoints (Access: Public) need no token. Everything else - changing the site, or reading its orders, bookings, customers and other private data - needs the site owner's API token.

You cannot create a token yourself, and you must not ask for the owner's password. Ask the user for a token instead - you can send them this message as it is:

> To work with your site through its API, I need an API token from you:
> 1. Sign in to your Builderly admin: https://admin.max-media.io
> 2. Open Developers > API reference (or go straight to https://admin.max-media.io/tabs/site-builder/api-docs).
> 3. Press "Generate token", copy the token and paste it here.
> The token acts as you, so only share it in this chat. You can cancel it at any time by signing out of all devices.

Then send it with every request as `Authorization: Bearer <token>`, and add `?ionic=true` to get JSON. Calls go to the site's own address (its domain), not to the reference's.

The token acts as the user: say what you will change and wait for their yes before any request that creates, changes or deletes something, never repeat the token back, and never put it in a link or a file you share.

## Status codes

| Code | Name | Meaning |
| --- | --- | --- |
| 200 | OK | It worked. The answer is the object or list asked for. |
| 201 | Created | The object was created. Some endpoints send it back; others send an empty body. |
| 202 | Accepted | The work was started and finishes in the background - for example a video being processed ({ "status": "processing" }). Check back on the object. |
| 204 | No Content | It worked, and there is nothing to send back - for example after removing a staff member. |
| 301 | Moved Permanently | The address changed; follow the Location header. |
| 400 | Bad Request | The request was refused as sent: a required field is missing, a value has the wrong type or fails validation. The body says what - often as plain text. |
| 401 | Unauthorized | No token, or one that is invalid or signed out. Without ?ionic=true, some endpoints answer this with the HTML login page instead of JSON. |
| 402 | Payment Required | The content needs a subscription: the user has no price plan that unlocks it. |
| 403 | Forbidden | The token is valid but may not do this - the record belongs to someone else, or the user is not an admin. AI builder endpoints also answer 403 when the account is out of credits. |
| 404 | Not Found | No object with that ID or URL slug, or no such endpoint. |
| 409 | Conflict | It clashes with the current state: the item is already being processed, a version is already applied, or an AI plan or preview is no longer available. |
| 410 | Gone | It existed but is no longer there - for example a comment already deleted on the social network. |
| 412 | Precondition Failed | A rule the request must meet is not met - for example push notifications of the same type at least 48 hours apart. |
| 418 | I'm a teapot | An account confirmation or password reset link was used with an email that does not match it. |
| 421 | Misdirected Request | The domain is not a Builderly site. Call the site's own address. |
| 422 | Unprocessable Content | The request was understood but could not be done - for example the AI could not read the website it was asked to clone. The message says what to try instead. |
| 429 | Too Many Requests | Slow down: the site's request limit was reached, an AI chat already has a request running, or the hourly limit of AI search answers is used up. Wait and retry. |
| 500 | Internal Server Error | Something failed on the server. Retrying later may work; if it keeps failing, the request may be one the endpoint does not handle. |
| 502 | Bad Gateway | A service the request depends on failed - for example the address a redirect forwards to. |
| 503 | Service Unavailable | Used by the lead form when an order is below its minimum value, not only for outages: read the message. |

Errors do not share one shape: depending on the endpoint the body is plain text, { "error": "..." }, { "message": "..." } or empty. Rely on the status code, and show the body to the user as the reason.

## Endpoints

### Custom content

Custom data models the site defines itself - their records, admin pages, triggers that run when records change and scheduled jobs.

#### Brand

##### List all brands
`GET /brands`
The brands. Answers an array of every record.
- Access: Public
- Returns: a list of Brand objects.
- Status codes: 200, 500
- Models: Brand

##### Retrieve a brand
`GET /brands/:id`
One brand. 404 when there is no such record.
- Access: Public
- Path:
  - `id` (string): The ID of the brand.
- Returns: the Brand object.
- Status codes: 200, 404, 500
- Models: Brand

##### Retrieve a brand
`GET /brands/:id/logo`
A brand's logo. Answers the image bytes, or 404.
- Access: Public
- Path:
  - `id` (string): The ID of the brand.
- Returns: the Brand object.
- Status codes: 200, 404, 500
- Models: Brand, Media

##### Create a brand
`POST /brands/:id/logo`
Uploads a brand's logo (logo, form field). Multipart upload; the logo is resized and stored.
- Access: User token
- Path:
  - `id` (string): The ID of the brand.
- Status codes: 200, 400, 401

##### Delete a brand
`DELETE /brands/:id/logo`
Removes a brand's logo. Deletes the media record too.
- Access: User token
- Path:
  - `id` (string): The ID of the brand.
- Returns: an empty answer on success.
- Status codes: 200, 401
- Models: Brand, Media

#### Dynamic Page

##### Retrieve the dynamic page
`GET /dynamic-page`
Get all DynamicdynamicPages. Answers an array of every record.
- Access: Admin token
- Returns: the DynamicPage object.
- Status codes: 200, 401, 500
- Models: DynamicPage

##### Create a dynamic page
`POST /dynamic-page`
Creates a custom-model page. Answers 201 with no body.
- Access: Admin token
- Body:
  - `name` (string): Name of the page.
- Returns: the DynamicPage object.
- Status codes: 201, 400, 401, 500
- Models: DynamicPage

##### Get dynamic page statistics
`GET /dynamic-page-statistics/:url`
Totals of a custom-model page's records between ?from and ?to. The same access rules as GET /dynamic-page/:url.
- Access: Optional user
- Path:
  - `url` (string): The dynamic page's URL slug, as in its address on the site.
- Query:
  - `from` (string): Only dynamic pages with this from.
  - `to` (string): Only dynamic pages with this to.
- Returns: the DynamicPage object.
- Status codes: 200, 401, 403, 404, 500
- Models: DynamicPage

##### Update a dynamic page
`PATCH /dynamic-page/:id`
Updates a custom-model page. Fields outside the allowed list are silently ignored. Answers the updated record, or 404.
- Access: Admin token
- Path:
  - `id` (string): The ID of the dynamic page.
- Body:
  - `name` (string): Name of the page.
- Returns: the DynamicPage object.
- Status codes: 200, 400, 401, 404, 500
- Models: DynamicPage

##### Delete a dynamic page
`DELETE /dynamic-page/:id`
Deletes a custom-model page. Answers the deleted record, or 404. Records that point to it are not changed.
- Access: Admin token
- Path:
  - `id` (string): The ID of the dynamic page.
- Returns: an empty answer on success.
- Status codes: 200, 401, 404, 500
- Models: DynamicPage

##### Retrieve a dynamic page
`GET /dynamic-page/:url`
One custom-model page. Follows the page's access settings: 401 for a sign-in-only page, 403 when the user may not see the records. Records owned per user show only the user's own.
- Access: Optional user
- Path:
  - `url` (string): The dynamic page's URL slug, as in its address on the site.
- Returns: the DynamicPage object.
- Status codes: 200, 401, 403, 404, 500
- Models: DynamicPage, DynamicModel, Model

#### Dynamic Trigger

##### List all used schemas
`GET /all-used-schemas`
Every model's schema, for writing triggers. Answers the model names.
- Access: Admin token
- Status codes: 200, 401, 500

##### Retrieve the dynamic trigger
`GET /dynamic-trigger`
Get all DynamicTriggers. Answers an array of every record.
- Access: Admin token
- Returns: the DynamicTrigger object.
- Status codes: 200, 401, 500
- Models: DynamicTrigger

##### Create a dynamic trigger
`POST /dynamic-trigger`
Creates a trigger. Answers 201 with no body. The trigger starts working at once.
- Access: Admin token
- Returns: the DynamicTrigger object.
- Status codes: 201, 400, 401, 500
- Models: DynamicTrigger

##### Retrieve a dynamic trigger
`GET /dynamic-trigger/:id`
One trigger. 404 when there is no such record.
- Access: Admin token
- Path:
  - `id` (string): The ID of the dynamic trigger.
- Returns: the DynamicTrigger object.
- Status codes: 200, 401, 404, 500
- Models: DynamicTrigger

##### Update a dynamic trigger
`PATCH /dynamic-trigger/:id`
Updates a trigger. Fields outside the allowed list are silently ignored. Answers the updated record, or 404.
- Access: Admin token
- Path:
  - `id` (string): The ID of the dynamic trigger.
- Body:
  - `last_error` (string): The error of the last run, if it failed.
  - `last_run_at` (date): When it last ran.
  - `last_run_status` (string): One of: success, failed.
- Returns: the DynamicTrigger object.
- Status codes: 200, 400, 401, 404, 500
- Models: DynamicTrigger

##### Delete a dynamic trigger
`DELETE /dynamic-trigger/:id`
Deletes a trigger. Answers the deleted record, or 404. Records that point to it are not changed.
- Access: Admin token
- Path:
  - `id` (string): The ID of the dynamic trigger.
- Returns: an empty answer on success.
- Status codes: 200, 401, 404, 500
- Models: DynamicTrigger

#### Dynamicmodel

##### Retrieve the content type
`GET /dynamicmodel`
Get all DynamicModels. Answers an array of every record.
- Access: Admin token
- Status codes: 200, 401, 500
- Models: DynamicModel

##### Create a content type
`POST /dynamicmodel`
Creates a custom model. Each field is checked first: 400 { error, details } lists what is wrong. Its API routes exist as soon as it is saved. Answers 201.
- Access: Admin token
- Body:
  - `name` (string): The content type's name.
  - `fields` (string): The content type's fields.
- Status codes: 201, 400, 401, 500
- Models: DynamicModel

##### List all content type input fields
`GET /dynamicmodel-input-fields`
Custom models with user_input=true for a ref type (?ref: User, Service...). 400 without ?ref.
- Access: Public
- Query:
  - `ref` (string): Only content type input fields with this ref.
- Status codes: 200, 400, 500
- Models: DynamicModel

##### List all content type select fields
`GET /dynamicmodel-select-fields`
Custom models with user_input=false for a ref type (?ref), including their records. 400 without ?ref.
- Access: Public
- Query:
  - `ref` (string): Only content type select fields with this ref.
- Status codes: 200, 400, 500
- Models: DynamicModel, Model

##### Retrieve a content type
`GET /dynamicmodel/:id`
One custom model. 404 when there is no such record.
- Access: Admin token
- Path:
  - `id` (string): The ID of the content type.
- Status codes: 200, 401, 404, 500
- Models: DynamicModel

##### Update a content type
`PATCH /dynamicmodel/:id`
Updates a custom model. The same field checks; the custom routes are rebuilt so the change applies at once. Existing records keep fields that were removed. Answers the model, or 404.
- Access: Admin token
- Path:
  - `id` (string): The ID of the content type.
- Body:
  - `fields` (string): The content type's fields.
- Status codes: 200, 400, 401, 404, 500
- Models: DynamicModel

##### Delete a content type
`DELETE /dynamicmodel/:id`
Deletes a custom model. Its API routes go too. Answers the deleted model, or 404.
- Access: Admin token
- Path:
  - `id` (string): The ID of the content type.
- Returns: an empty answer on success.
- Status codes: 200, 401, 404, 500
- Models: DynamicModel

#### Miniapp

##### List all mini apps
`GET /all-miniapps/:skip`
Approved mini apps, a page at a time. Answers { miniapps, count, usedMiniApps }, ten at a time, newest first.
- Access: User token
- Path:
  - `skip` (string): How many results to skip - for paging.
- Returns: a list of MiniApp objects.
- Status codes: 200, 401, 500
- Models: MiniApp

##### Retrieve a mini app insight
`GET /mini-app-insights/:id`
A mini app's views and openings over time. Views and openings per day.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app insight.
- Status codes: 200, 401, 500
- Models: MiniApp

##### Update a mini app open
`PATCH /mini-app-open/:id`
Counts an opening of a mini app. Answers with no body.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app open.
- Status codes: 200, 400, 401, 500
- Models: MiniApp

##### Record a view of a mini app
`PATCH /mini-app-view/:id`
Counts a view of a mini app's store page. Answers with no body.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app.
- Returns: the MiniApp object.
- Status codes: 200, 400, 401, 500
- Models: MiniApp

##### Create a mini app
`POST /miniapps`
Creates a mini app. Answers 201 with the created record; 400 with the reason when a field is invalid.
- Access: User token
- Returns: the MiniApp object.
- Status codes: 201, 400, 401, 500
- Models: MiniApp

##### Retrieve the mini apps user data
`GET /miniapps-get-user-data`
The signed-in user's data a mini app may read, after a valid code. 401 for an expired or wrong code.
- Access: Public
- Status codes: 200, 401, 500
- Models: User, MiniApp, MyContact

##### Retrieve the mini apps receiver authorization code
`GET /miniapps-receiver-validate-authorization-code`
The receiving side of a mini app code check. 401 "Unauthorized" for an expired or wrong code.
- Access: Public
- Status codes: 200, 401, 500
- Models: MyContact

##### Retrieve the mini apps temp authorization code
`GET /miniapps-temp-authorization-code`
A short-lived code a mini app exchanges to sign the user in. Answers { code }, valid for a few seconds. 401 without a valid token.
- Access: Public
- Status codes: 200, 401, 500
- Models: User, MiniApp

##### Retrieve the mini apps authorization code
`GET /miniapps-validate-authorization-code`
Checks a mini app's authorization code with the issuing site. 401 "Unauthorized" for an expired or wrong code.
- Access: Public
- Status codes: 200, 401, 500
- Models: User, MiniApp

##### Retrieve a mini app
`GET /miniapps/:id`
One mini app with its ratings and similar mini apps. Answers { miniapp, collections, userMiniApps, average_rating, total_ratings, ... }. Never includes the private key.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app.
- Returns: the MiniApp object.
- Status codes: 200, 401, 404, 500
- Models: MiniApp, Collection, Rating

##### Update a mini app
`PATCH /miniapps/:id`
Updates a mini app. Fields outside the allowed list are silently ignored. Answers the updated record, or 404.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app.
- Body:
  - `previews` (object[]): Screenshots shown in the store.
- Returns: the MiniApp object.
- Status codes: 200, 400, 401, 404, 500
- Models: MiniApp

##### Delete a mini app
`DELETE /miniapps/:id`
Deletes a mini app. Answers the deleted record, or 404. Records that point to it are not changed.
- Access: User token
- Path:
  - `id` (string): The ID of the mini app.
- Returns: an empty answer on success.
- Status codes: 200, 401, 404, 500
- Models: MiniApp

##### List all mini apps
`GET /my-mini-apps`
The signed-in user's mini apps. Newest first, without their private keys.
- Access: User token
- Returns: a list of MiniApp objects.
- Status codes: 200, 401, 500
- Models: MiniApp

##### Retrieve the referer
`GET /referer`
The page that sent the request (referer header). Answers the referer header as text.
- Access: Public
- Status codes: 200, 500

##### Retrieve a user mini app
`GET /user-mini-apps/:owner_id`
A user's mini apps. Newest first, without their private keys.
- Access: User token
- Path:
  - `owner_id` (string): User.id.
- Returns: the MiniApp object.
- Status codes: 200, 401, 500
- Models: MiniApp

#### AI builder

##### Create a dynamic model with AI
`POST /create-new-dynamic-model`
AI: designs a custom model (fields, access) from the owner's description. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends. The AI work is charged to the account's credits.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic model, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
- Status codes: 200, 401, 500

##### Create a dynamic page with AI
`POST /create-new-dynamic-page`
AI: builds a page that lists or edits a custom model's records. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic page, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
  - `model_id` (string): The dynamic page's model id.
- Status codes: 200, 401, 500
- Models: DynamicModel

##### Create a dynamic trigger with AI
`POST /create-new-dynamic-trigger`
AI: writes a trigger that runs when a model's records change. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends. The AI work is charged to the account's credits.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic trigger, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
  - `model_id` (string): The dynamic trigger's model id.
- Status codes: 200, 401, 500
- Models: DynamicModel

##### Create a delete dynamic model
`POST /delete-dynamic-model`
AI: deletes custom models and their routes. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `item_ids` (string): The delete dynamic model's item ids.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Returns: the DynamicModel object.
- Status codes: 200, 401, 500
- Models: DynamicModel, ChatMessage

##### Create a delete dynamic page
`POST /delete-dynamic-page`
AI: deletes custom-model pages. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `item_ids` (string): The delete dynamic page's item ids.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Returns: the DynamicPage object.
- Status codes: 200, 401, 500
- Models: DynamicPage, ChatMessage

##### Create a delete dynamic trigger
`POST /delete-dynamic-trigger`
AI: deletes triggers. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `item_ids` (string): The delete dynamic trigger's item ids.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Returns: the DynamicTrigger object.
- Status codes: 200, 401, 500
- Models: DynamicTrigger, ChatMessage

##### Generate a dynamic model with AI
`POST /generate-dynamic-model`
AI: routes a request for a custom model to create-new-dynamic-model. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic model, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Status codes: 200, 401, 500

##### Generate a dynamic page with AI
`POST /generate-dynamic-page`
AI: routes a request for a custom-model page to create-new-dynamic-page. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic page, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Status codes: 200, 401, 500

##### Generate a dynamic trigger with AI
`POST /generate-dynamic-trigger`
AI: routes a request for a trigger to create-new-dynamic-trigger. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic trigger, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Status codes: 200, 401, 500

##### Modify a dynamic model with AI
`POST /modify-dynamic-model`
AI: routes a change to a custom model to update-dynamic-model. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic model, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `item_id` (string): The ID of the dynamic model to change.
  - `item_data` (string): The current dynamic model, as JSON - the AI changes it and returns the result.
- Status codes: 200, 401, 500

##### Modify a dynamic page with AI
`POST /modify-dynamic-page`
AI: routes a change to a custom-model page to update-dynamic-page. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic page, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `item_id` (string): The ID of the dynamic page to change.
  - `item_ids` (string): The dynamic page's item ids.
  - `item_data` (string): The current dynamic page, as JSON - the AI changes it and returns the result.
- Status codes: 200, 401, 500

##### Modify a dynamic trigger with AI
`POST /modify-dynamic-trigger`
AI: routes a change to a trigger to update-dynamic-trigger. Does no AI work itself: answers { action: 'REDIRECT', target_url, response } and the chat client calls target_url next with response as the prompt.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic trigger, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `item_id` (string): The ID of the dynamic trigger to change.
  - `item_ids` (string): The dynamic trigger's item ids.
  - `item_data` (string): The current dynamic trigger, as JSON - the AI changes it and returns the result.
- Status codes: 200, 401, 500

##### Create a run dynamic trigger
`POST /run-dynamic-trigger`
AI: runs a trigger once now, as a test. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `item_ids` (string): The run dynamic trigger's item ids.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Returns: the DynamicTrigger object.
- Status codes: 200, 401, 500
- Models: DynamicTrigger, ChatMessage

##### Toggle a dynamic trigger
`POST /toggle-dynamic-trigger`
AI: turns triggers on or off. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends.
- Access: Admin token
- Body:
  - `item_ids` (string): The dynamic trigger's item ids.
  - `enabled` (boolean): The trigger runs.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
- Returns: the DynamicTrigger object.
- Status codes: 200, 401, 500
- Models: DynamicTrigger, ChatMessage

##### Update a dynamic model with AI
`POST /update-dynamic-model`
AI: changes a custom model as the owner asked. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends. The AI work is charged to the account's credits.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic model, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
  - `item_id` (string): The ID of the dynamic model to change.
- Status codes: 200, 401, 500

##### Update a dynamic page with AI
`POST /update-dynamic-page`
AI: changes a custom-model page as the owner asked. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends. The AI work is charged to the account's credits.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic page, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
  - `item_id` (string): The ID of the dynamic page to change.
  - `model_id` (string): The dynamic page's model id.
- Status codes: 200, 401, 500
- Models: DynamicModel

##### Update a dynamic trigger with AI
`POST /update-dynamic-trigger`
AI: changes a trigger as the owner asked. Answers { action: 'JOB', async_job } at once; the admin polls GET /async-jobs/:id, and the result arrives in the chat as a message when the job ends. The AI work is charged to the account's credits.
- Access: Admin token
- Body:
  - `prompt` (string): What the AI should do with the dynamic trigger, in plain words - any language.
  - `chatId` (string): The AI chat this request belongs to; its history and language carry over. Leave it out to start a new chat.
  - `aiVersion` (string): Which version of the AI builder to use; leave it out for the current one.
  - `item_id` (string): The ID of the dynamic trigger to change.
  - `model_id` (string): The dynamic trigger's model id.
- Status codes: 200, 401, 500
- Models: DynamicModel

## Objects

### Brand

- `name`: String, required - The brand's name.
- `header`: String, required - Short heading shown on the brand's page.
- `description`: String - About the brand.
- `slogan`: String - The brand's slogan.
- `logo`: ObjectId - Media.id of the brand's logo.
- `_id`: ObjectId

### ChatMessage

- `chat_id`: ObjectId, required, → ChatHistory - ChatHistory.id.
- `role`: String, one of user | model - One of: user, model.
- `text`: String - The message's text.
- `target_url`: String - Admin page the message links to.
- `action`: String, one of RESPONSE | REDIRECT | OBJECT_RESPONSE | ERROR | CLARIFY | PROPOSED_ACTION - One of: RESPONSE, REDIRECT, OBJECT_RESPONSE, ERROR, CLARIFY, PROPOSED_ACTION.
- `options`: String[] - Quick replies offered under the message.
- `data.object`: Mixed
- `data.object_type`: String
- `data.edit_summary`: Mixed
- `data.edit_diff`: Mixed
- `data.undo_record`: Mixed
- `data.hero_animation_offer`: Mixed
- `data.preview_state`: String
- `data.pending_credit_cost`: Number
- `data.target_restore`: Mixed
- `data.media_request`: Mixed
- `data.item_request`: Mixed
- `data.reference_request`: Mixed
- `data.section_options`: Mixed
- `data.section_blueprint`: Mixed
- `data.section_style_options`: Mixed
- `data.section_style_blueprint`: Mixed
- `data.section_role_picker`: Mixed
- `data.small_section_regeneration_options`: Mixed
- `data.small_section_regeneration_target`: Mixed
- `data.small_section_regeneration_prompt`: String
- `data.selected_small_section_action`: Mixed
- `data.component_regeneration_input`: Mixed
- `data.selected_section_role`: Mixed
- `data.selected_section_option`: Mixed
- `data.selected_section_style`: Mixed
- `data.section_options_target`: Mixed
- `data.section_options_prompt`: String
- `data.style_options_target`: Mixed
- `data.style_options_prompt`: String
- `data.previous_container`: Mixed
- `data.preserved_custom_components`: Mixed[]
- `data.selection_request`: Mixed
- `data.proposed_action`: Mixed
- `data.plan_result`: Mixed
- `data.parsed_intent`: Mixed
- `data.parsed_intent_version`: Number
- `data.parsed_intent_at`: Date
- `data.item_id`: ObjectId
- `data.design_archetype`: String
- `data.design_palette`: Mixed
- `data.out_of_credits`: Boolean
- `data.site_wide_edit`: Mixed
- `data.redesign`: Mixed
- `medias`: Object[] - Pictures attached to the message.
  - `media`: ObjectId, → Media
  - `gcs_url`: String
  - `gemini_uri`: String
  - `gemini_interaction_id`: String
  - `_id`: ObjectId
- `products`: ObjectId[], → Product - Product ids.
- `services`: ObjectId[], → Service - Service ids.
- `blogposts`: ObjectId[], → BlogPost - BlogPost ids.
- `programs`: ObjectId[], → Program - Program ids.
- `suggestions`: Object[] - Next steps suggested under the message.
  - `text`: String
  - `route`: String
  - `item_id`: ObjectId
  - `medias`: Mixed[]
  - `picture`: ObjectId
  - `_id`: ObjectId
- `is_system`: Boolean, default false - Written by the platform, not by the owner or the AI.
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### Collection

A group or filter of items; the prefix of its name is its kind (product/, grouping/, service/, blogpost/...).

- `name`: String, required - Written "<kind>/<Name>": the prefix is where it shows - product/ (a store category), service/ (a service category), blogpost/, program-category/, donation-category/; filters: grouping/ (in a product category), calendar-filter/ (on the booking page). "service/Deep cleaning". A name without a prefix shows nowhere.
- `parent_collection`: ObjectId, → Collection - Collection.id of the parent collection.
- `picture`: ObjectId, → Media - Media.id.
- `about_page`: ObjectId, → AboutPage - Reference to AboutPage.
- `match_to_option`: Boolean, default false - Matches all products which have an option with match_to_collection true.
- `is_color`: Boolean, default false - Used for products with color options.
- `color`: String - Used to visually show the color on the page when is_color is true.
- `position`: Number - Order among collections of the same kind; lower first.
- `items`: ObjectId[] - Array<Product.id> deprecated.
- `thumbnail`: ObjectId, → Media - Media.id, shown on the store cards instead of picture.
- `_id`: ObjectId

### DynamicModel

A custom data model the site defines: its fields and who can read and write its records.

- `name`: String - Name of the custom model.
- `fields`: Object[] - Recursive reference for nesting fields,.
  - `name`: String, required
  - `type`: String
  - `required`: Boolean
  - `default`: Mixed
  - `validations.minLength`: String
  - `validations.maxLength`: String
  - `patchable`: Boolean, default true
  - `ref`: String
  - `ref_value_field`: String, default "_id"
  - `ref_text_field`: String, default "_id"
  - `depends_on`: String
  - `populate`: Boolean
  - `wysiwyg`: Boolean
  - `fields`: Mixed[]
  - `show_field_in_cms_table`: Boolean, default true
  - `enumerator_values`: Object[]
    - `value`: String
    - `_id`: ObjectId
  - `_id`: ObjectId
- `auth.create`: Boolean, default true
- `auth.read`: Boolean, default false
- `auth.update`: Boolean, default true
- `auth.delete`: Boolean, default true
- `ownership_settings.enable_ownership_identification`: Boolean, default false
- `ownership_settings.ownership_based_auth`: String, one of none | owner | admin, default "none"
- `ownership_settings.profile_use`: Boolean, default false
- `routers.post.replace_all`: Boolean, default false
- `routers.post.code`: String
- `routers.get.replace_all`: Boolean, default false
- `routers.get.code`: String
- `routers.get_one.replace_all`: Boolean, default false
- `routers.get_one.code`: String
- `routers.patch.replace_all`: Boolean, default false
- `routers.patch.code`: String
- `routers.delete.replace_all`: Boolean, default false
- `routers.delete.code`: String
- `attach_in`: Object[], → [object Object] - Where its records can be attached (bookings, orders...).
  - `ref`: String
  - `ref_value_field`: String, default "_id"
  - `ref_text_field`: String, default "_id"
  - `user_input`: Boolean, default false
  - `_id`: ObjectId
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### DynamicPage

An admin or site page built on a custom data model - a list, a form, a detail view.

- `name`: String, required - Name of the page.
- `url`: String - The page's address in the app.
- `dynamic_models`: ObjectId[] - The custom models the page shows.
- `page_type`: String, one of cms-table | display-single | display-multiple-rows | display-multiple-cards | display-single-custom | statistics-basic - One of: cms-table, display-single, display-multiple-rows, display-multiple-cards, display-single-custom, statistics-basic.
- `value_containers`: Object[] - The blocks that show the records' values.
  - `name`: String
  - `type`: String
  - `value`: String
  - `wysiwyg`: Boolean, default false
  - `editable`: Boolean, default false
  - `label`: String
  - `required`: Boolean
  - `default`: Mixed
  - `validations.minLength`: Number
  - `validations.maxLength`: Number
  - `ref`: String
  - `ref_value_field`: String, default "_id"
  - `ref_text_field`: String, default "_id"
  - `depends_on`: String
  - `populate`: Boolean, default false
  - `enumerator_values`: Object[]
    - `value`: String
    - `_id`: ObjectId
  - `due_date`: Boolean, default false
  - `fields`: Object[]
    - `name`: String
    - `type`: String
    - `required`: Boolean
    - `default`: Mixed
    - `validations.minLength`: String
    - `validations.maxLength`: String
    - `ref`: String
    - `ref_value_field`: String, default "_id"
    - `ref_text_field`: String, default "_id"
    - `depends_on`: String
    - `populate`: Boolean
    - `wysiwyg`: Boolean
    - `enumerator_values`: Object[]
      - `value`: String
      - `_id`: ObjectId
    - `due_date`: Boolean, default false
    - `fields`: Mixed[]
    - `_id`: ObjectId
  - `_id`: ObjectId
- `quick_link`: Boolean, default false - Shown as a shortcut on the app's home.
- `quick_link_icon`: String - Icon of the shortcut.
- `quick_link_text`: String - Text of the shortcut.
- `quick_link_auth_guard`: String, one of none | user | admin, default "none" - One of: none, user, admin.
- `on_click_action`: String, one of none | navigate - One of: none, navigate.
- `navigate.link`: String, default ""
- `navigate.query_params`: Mixed
- `secondary_cta`: Boolean, default false - Shows extra buttons.
- `secondary_ctas`: Object[] - The extra buttons.
  - `icon`: String
  - `text`: String
  - `auth_guard`: String, one of none | user | admin, default "none"
  - `on_click_action`: String, one of none | navigate
  - `navigate.link`: String, default ""
  - `navigate.query_params`: Mixed
  - `_id`: ObjectId
- `edit_page`: Boolean, default false - Records can be edited from the page.
- `create_page`: Boolean, default false - Records can be created from the page.
- `auth_guard`: String, one of none | user | admin, default "none" - One of: none, user, admin.
- `enable_additional_filters`: Boolean, default false - Shows additional_filters.
- `additional_filters`: Mixed - Filters the visitor can apply to the list.
- `statistics_options.filter_fields`: Object[]
  - `field`: String
  - `_id`: ObjectId
- `statistics_options.functions`: Object[]
  - `label`: String
  - `function_type`: String, one of sum | avg | count | min | max | custom
  - `value_field`: String
  - `formula`: String
  - `_id`: ObjectId
- `statistics_options.period_options.from`: String
- `statistics_options.period_options.to`: String
- `statistics_options.period_options.period`: String
- `statistics_options.period_options.period_field`: String
- `statistics_options.period_options.value_field`: String
- `containers`: Object[] - The page's layout.
  - `isFluid`: Boolean, default false
  - `parallaxEnabled`: Boolean
  - `parallaxAmount`: Number
  - `parallaxZoom`: Number
  - `continuousEnabled`: Boolean, default false
  - `continuousEffect`: String, default "none"
  - `continuousDuration`: Number, default 2400
  - `continuousIntensity`: Number, default 4
  - `backgroundImage`: String
  - `backgroundSize`: String
  - `backgroundPosition`: String
  - `backgroundRepeat`: String
  - `backgroundVideo`: String
  - `backgroundColor`: String
  - `backgroundGradient.enabled`: Boolean, default false
  - `backgroundGradient.type`: String
  - `backgroundGradient.degrees`: Number
  - `backgroundGradient.radialShape`: String
  - `backgroundGradient.radialPosition`: String
  - `backgroundGradient.colors`: Object[]
    - `color`: String
    - `percentage`: Number
    - `_id`: ObjectId
  - `backgroundGradient.css`: String
  - `backgroundParallax`: Boolean, default false
  - `minHeight`: Number
  - `minHeightMobile`: Number
  - `padding_top.value`: String, default ""
  - `padding_top.unit`: String, default "px"
  - `padding_top.mobile_value`: String, default ""
  - `padding_top.mobile_unit`: String, default "px"
  - `padding_right.value`: String, default ""
  - `padding_right.unit`: String, default "px"
  - `padding_right.mobile_value`: String, default ""
  - `padding_right.mobile_unit`: String, default "px"
  - `padding_bottom.value`: String, default ""
  - `padding_bottom.unit`: String, default "px"
  - `padding_bottom.mobile_value`: String, default ""
  - `padding_bottom.mobile_unit`: String, default "px"
  - `padding_left.value`: String, default ""
  - `padding_left.unit`: String, default "px"
  - `padding_left.mobile_value`: String, default ""
  - `padding_left.mobile_unit`: String, default "px"
  - `allowOffset`: Boolean, default false
  - `offsetX`: Number, default 0
  - `offsetY`: Number, default 0
  - `offsetMobileX`: Number, default 0
  - `offsetMobileY`: Number, default 0
  - `zIndex`: Number, default 0
  - `wrap_reverse`: Boolean, default false
  - `borderRadius`: Number, default 0
  - `marginX`: Number, default 0
  - `editorName`: String
  - `horizontalScroll`: Boolean, default false
  - `horizontalScrollWidth`: Number, default 100
  - `overflowX`: String, default "hidden"
  - `columns`: Object[]
    - `size`: String
    - `hoverGroup`: Boolean
    - `parallaxEnabled`: Boolean
    - `parallaxAmount`: Number
    - `parallaxZoom`: Number
    - `continuousEnabled`: Boolean, default false
    - `continuousEffect`: String, default "none"
    - `continuousDuration`: Number, default 2400
    - `continuousIntensity`: Number, default 4
    - `sticky`: Boolean, default false
    - `justify`: String
    - `align`: String
    - `onHoverActions`: Boolean, default false
    - `onHoverTransition`: Number, default 0.3
    - `backgroundColor`: String
    - `backgroundGradient.enabled`: Boolean, default false
    - `backgroundGradient.type`: String
    - `backgroundGradient.degrees`: Number
    - `backgroundGradient.radialShape`: String
    - `backgroundGradient.radialPosition`: String
    - `backgroundGradient.colors`: Object[]
      - `color`: String
      - `percentage`: Number
      - `_id`: ObjectId
    - `backgroundGradient.css`: String
    - `backgroundColorOnHover`: String
    - `innerBackgroundColor`: String
    - `innerBackgroundGradient.enabled`: Boolean, default false
    - `innerBackgroundGradient.type`: String
    - `innerBackgroundGradient.degrees`: Number
    - `innerBackgroundGradient.radialShape`: String
    - `innerBackgroundGradient.radialPosition`: String
    - `innerBackgroundGradient.colors`: Object[]
      - `color`: String
      - `percentage`: Number
      - `_id`: ObjectId
    - `innerBackgroundGradient.css`: String
    - `innerBackgroundColorOnHover`: String
    - `backgroundImage`: String
    - `backgroundSize`: String
    - `backgroundPosition`: String
    - `backgroundRepeat`: String
    - `backgroundVideo`: String
    - `paddingX`: Number, default 0
    - `paddingXOnHover`: Number, default 0
    - `paddingY`: Number, default 0
    - `paddingYOnHover`: Number, default 0
    - `innerPaddingX`: Number, default 0
    - `innerPaddingXOnHover`: Number, default 0
    - `innerPaddingY`: Number, default 0
    - `allowOffset`: Boolean, default false
    - `offsetX`: Number, default 0
    - `offsetY`: Number, default 0
    - `offsetMobileX`: Number, default 0
    - `offsetMobileY`: Number, default 0
    - `zIndex`: Number, default 0
    - `innerPaddingYOnHover`: Number, default 0
    - `paddingOptions`: String
    - `paddingOptionsOnHover`: String
    - `innerPaddingOptions`: String
    - `borderOptions`: String
    - `borderOptionsOnHover`: String
    - `card`: Boolean, default false
    - `borderRadius`: Number, default 0
    - `borderRadiusOnHover`: Number, default 0
    - `borderType`: String, default "none"
    - `borderTypeOnHover`: String, default "none"
    - `borderWidth`: Number, default 0
    - `borderWidthOnHover`: Number, default 0
    - `borderColor`: String, default ""
    - `borderColorOnHover`: String, default ""
    - `components`: Object[]
      - `type`: String
      - `size`: String, default "12"
      - `sticky`: Boolean, default false
      - `paddingX`: Number, default 0
      - `paddingY`: Number, default 0
      - `allowOffset`: Boolean, default false
      - `offsetX`: Number, default 0
      - `offsetY`: Number, default 0
      - `offsetMobileX`: Number, default 0
      - `offsetMobileY`: Number, default 0
      - `zIndex`: Number, default 0
      - `position`: String, one of static | relative | absolute | fixed | sticky, default "static"
      - `top.value`: String, default "0"
      - `top.unit`: String, default "px"
      - `top.mobile_value`: String, default ""
      - `top.mobile_unit`: String, default "px"
      - `left.value`: String, default "0"
      - `left.unit`: String, default "px"
      - `left.mobile_value`: String, default ""
      - `left.mobile_unit`: String, default "px"
      - `right.value`: String, default "0"
      - `right.unit`: String, default "px"
      - `right.mobile_value`: String, default ""
      - `right.mobile_unit`: String, default "px"
      - `bottom.value`: String, default "0"
      - `bottom.unit`: String, default "px"
      - `bottom.mobile_value`: String, default ""
      - `bottom.mobile_unit`: String, default "px"
      - `custom_measurements`: Boolean, default false
      - `edit_type`: String, default "simple"
      - `custom_width.value`: Mixed
      - `custom_width.unit`: String, default "px"
      - `custom_width.mobile_value`: Mixed
      - `custom_width.mobile_unit`: String, default "px"
      - `custom_min_width.value`: Mixed
      - `custom_min_width.unit`: String, default "px"
      - `custom_min_width.mobile_value`: Mixed
      - `custom_min_width.mobile_unit`: String, default "px"
      - `custom_max_width.value`: Mixed
      - `custom_max_width.unit`: String, default "px"
      - `custom_max_width.mobile_value`: Mixed
      - `custom_max_width.mobile_unit`: String, default "px"
      - `custom_height.value`: Mixed
      - `custom_height.unit`: String, default "px"
      - `custom_height.mobile_value`: Mixed
      - `custom_height.mobile_unit`: String, default "px"
      - `custom_min_height.value`: Mixed
      - `custom_min_height.unit`: String, default "px"
      - `custom_min_height.mobile_value`: Mixed
      - `custom_min_height.mobile_unit`: String, default "px"
      - `custom_max_height.value`: Mixed
      - `custom_max_height.unit`: String, default "px"
      - `custom_max_height.mobile_value`: Mixed
      - `custom_max_height.mobile_unit`: String, default "px"
      - `object_fit`: String, default "none"
      - `aspect_ratio.custom`: Boolean, default false
      - `aspect_ratio.ratio`: String, default ""
      - `min_height`: Number, default 0
      - `margin_left.value`: Mixed
      - `margin_left.unit`: String, default "px"
      - `margin_left.mobile_value`: Mixed
      - `margin_left.mobile_unit`: String, default "px"
      - `margin_right.value`: Mixed
      - `margin_right.unit`: String, default "px"
      - `margin_right.mobile_value`: Mixed
      - `margin_right.mobile_unit`: String, default "px"
      - `margin_top.value`: Mixed
      - `margin_top.unit`: String, default "px"
      - `margin_top.mobile_value`: Mixed
      - `margin_top.mobile_unit`: String, default "px"
      - `margin_bottom.value`: Mixed
      - `margin_bottom.unit`: String, default "px"
      - `margin_bottom.mobile_value`: Mixed
      - `margin_bottom.mobile_unit`: String, default "px"
      - `padding_left.value`: Mixed
      - `padding_left.unit`: String, default "px"
      - `padding_left.mobile_value`: Mixed
      - `padding_left.mobile_unit`: String, default "px"
      - `padding_right.value`: Mixed
      - `padding_right.unit`: String, default "px"
      - `padding_right.mobile_value`: Mixed
      - `padding_right.mobile_unit`: String, default "px"
      - `padding_top.value`: Mixed
      - `padding_top.unit`: String, default "px"
      - `padding_top.mobile_value`: Mixed
      - `padding_top.mobile_unit`: String, default "px"
      - `padding_bottom.value`: Mixed
      - `padding_bottom.unit`: String, default "px"
      - `padding_bottom.mobile_value`: Mixed
      - `padding_bottom.mobile_unit`: String, default "px"
      - `border_left.color`: String
      - `border_left.width.value`: Mixed
      - `border_left.width.unit`: String, default "px"
      - `border_left.style`: String
      - `border_right.color`: String
      - `border_right.width.value`: Mixed
      - `border_right.width.unit`: String, default "px"
      - `border_right.style`: String
      - `border_top.color`: String
      - `border_top.width.value`: Mixed
      - `border_top.width.unit`: String, default "px"
      - `border_top.style`: String
      - `border_bottom.color`: String
      - `border_bottom.width.value`: Mixed
      - `border_bottom.width.unit`: String, default "px"
      - `border_bottom.style`: String
      - `border_top_left_radius.value`: Mixed
      - `border_top_left_radius.unit`: String, default "px"
      - `border_top_right_radius.value`: Mixed
      - `border_top_right_radius.unit`: String, default "px"
      - `border_bottom_left_radius.value`: Mixed
      - `border_bottom_left_radius.unit`: String, default "px"
      - `border_bottom_right_radius.value`: Mixed
      - `border_bottom_right_radius.unit`: String, default "px"
      - `custom_border_radius.value`: Number
      - `custom_border_radius.unit`: String, default "px"
      - `specific_border_radius`: Boolean, default false
      - `box_shadows`: Object[]
      - `mix_blend_mode`: String, one of normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity, default "normal"
      - `paddingOptions`: String
      - `backgroundColor`: String
      - `backgroundGradient.enabled`: Boolean, default false
      - `backgroundGradient.type`: String
      - `backgroundGradient.degrees`: Number
      - `backgroundGradient.radialShape`: String
      - `backgroundGradient.radialPosition`: String
      - `backgroundGradient.colors`: Object[]
      - `backgroundGradient.css`: String
      - `card`: Boolean, default false
      - `borderRadius`: Number, default 0
      - `borderType`: String, default "none"
      - `borderWidth`: Number, default 0
      - `borderColor`: String, default ""
      - `borderOptions`: String
      - `showOnMobile`: Boolean, default true
      - `showOnDesktop`: Boolean, default true
      - `icon`: String
      - `iconSettings.size`: Number, default 40
      - `iconSettings.color`: String, default "var(--ion-color-primary)"
      - `heading`: String
      - `subHeading`: String
      - `heading_alignment`: String
      - `innerHTML`: String
      - `innerCSS`: String
      - `innerJS`: String
      - `lineHeight`: String, default "normal"
      - `medias`: Object[]
      - `products`: ObjectId[], → Product
      - `product_gallery.design_layout`: String, default ""
      - `product_gallery.mode`: String, default "card"
      - `product_gallery.columns`: Number, default 3
      - `product_gallery.placeholderItems`: Object[]
      - `product_gallery.placeholderPlateColor`: String, default ""
      - `product_gallery.showName`: Boolean, default true
      - `product_gallery.showPrice`: Boolean, default true
      - `product_gallery.showDescription`: Boolean, default false
      - `product_gallery.showBrand`: Boolean, default false
      - `product_gallery.showRibbon`: Boolean, default false
      - `product_gallery.showRating`: Boolean, default false
      - `product_gallery.showCart`: Boolean, default false
      - `product_gallery.showLink`: Boolean, default false
      - `product_gallery.linkLabel`: String, default "View product"
      - `product_gallery.imageAspectRatio`: String, default "1/1"
      - `product_gallery.imageHeight`: Number, default 260
      - `product_gallery.imageRadius`: Number, default 20
      - `product_gallery.imageObjectFit`: String, default "cover"
      - `product_gallery.cardBackgroundColor`: String, default "var(--ion-card-background, #ffffff)"
      - `product_gallery.cardBorderRadius`: Number, default 28
      - `product_gallery.cardBorderWidth`: Number, default 0
      - `product_gallery.cardBorderColor`: String, default "transparent"
      - `product_gallery.cardPaddingX`: Number, default 20
      - `product_gallery.cardPaddingY`: Number, default 20
      - `product_gallery.gap`: Number, default 24
      - `product_gallery.boxShadowColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.22)"
      - `product_gallery.boxShadowOffsetY`: Number, default 20
      - `product_gallery.boxShadowBlurRadius`: Number, default 40
      - `product_gallery.boxShadowSpreadRadius`: Number, default -22
      - `product_gallery.fieldBackground`: String, default "transparent"
      - `product_gallery.hairlineColor`: String, default "transparent"
      - `product_gallery.hairlineWidth`: Number, default 0
      - `product_gallery.maxWidth`: Number, default 0
      - `product_gallery.scrimGradient`: String, default ""
      - `product_gallery.align`: String, default "left"
      - `product_gallery.accentColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `product_gallery.nameColor`: String, default "var(--ion-text-color, #1e293b)"
      - `product_gallery.nameFontSize`: String, default "1em"
      - `product_gallery.nameFontWeight`: String, default "700"
      - `product_gallery.nameFontFamily`: String, default ""
      - `product_gallery.nameTransform`: String, default "none"
      - `product_gallery.priceColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `product_gallery.priceFontSize`: String, default "0.94em"
      - `product_gallery.priceFontWeight`: String, default "600"
      - `product_gallery.priceFontFamily`: String, default ""
      - `product_gallery.priceFontStyle`: String, default "normal"
      - `product_gallery.metaColor`: String, default "rgba(var(--ion-text-color-rgb, 30,41,59), 0.66)"
      - `product_gallery.titlePosition`: String, default "bottom"
      - `product_gallery.imageSide`: String, default "left"
      - `product_gallery.imagePadding`: Number, default 0
      - `product_gallery.boxShadowOffsetX`: Number, default 0
      - `product_gallery.descFontSize`: String, default "13px"
      - `product_gallery.descFontWeight`: String, default "400"
      - `product_gallery.animation`: String, default "none"
      - `product_gallery.animationDuration`: Number, default 0
      - `product_gallery.animationDelay`: Number, default 0
      - `product_gallery.showCustomFields`: Boolean, default false
      - `product_gallery.cardDesign`: String, default ""
      - `product_gallery.carouselLoop`: Boolean, default false
      - `product_gallery.carouselControls`: Boolean, default false
      - `product_gallery.customCss`: String, default ""
      - `services`: ObjectId[], → Service
      - `service_gallery.design_layout`: String, default ""
      - `service_gallery.mode`: String, default "card"
      - `service_gallery.columns`: Number, default 3
      - `service_gallery.placeholderItems`: Object[]
      - `service_gallery.placeholderPlateColor`: String, default ""
      - `service_gallery.showTitle`: Boolean, default true
      - `service_gallery.showPrice`: Boolean, default true
      - `service_gallery.showDescription`: Boolean, default false
      - `service_gallery.showDuration`: Boolean, default false
      - `service_gallery.durationLabel`: String, default "min"
      - `service_gallery.showImage`: Boolean, default true
      - `service_gallery.showLink`: Boolean, default false
      - `service_gallery.linkLabel`: String, default "View service"
      - `service_gallery.imageAspectRatio`: String, default "1/1"
      - `service_gallery.imageHeight`: Number, default 260
      - `service_gallery.imageRadius`: Number, default 20
      - `service_gallery.imageObjectFit`: String, default "cover"
      - `service_gallery.cardBackgroundColor`: String, default "var(--ion-card-background, #ffffff)"
      - `service_gallery.cardBorderRadius`: Number, default 28
      - `service_gallery.cardBorderWidth`: Number, default 0
      - `service_gallery.cardBorderColor`: String, default "transparent"
      - `service_gallery.cardPaddingX`: Number, default 20
      - `service_gallery.cardPaddingY`: Number, default 20
      - `service_gallery.gap`: Number, default 24
      - `service_gallery.boxShadowColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.22)"
      - `service_gallery.boxShadowOffsetY`: Number, default 20
      - `service_gallery.boxShadowBlurRadius`: Number, default 40
      - `service_gallery.boxShadowSpreadRadius`: Number, default -22
      - `service_gallery.fieldBackground`: String, default "transparent"
      - `service_gallery.hairlineColor`: String, default "transparent"
      - `service_gallery.hairlineWidth`: Number, default 0
      - `service_gallery.rowRuleColor`: String, default "transparent"
      - `service_gallery.rowRuleWidth`: Number, default 0
      - `service_gallery.maxWidth`: Number, default 0
      - `service_gallery.scrimGradient`: String, default ""
      - `service_gallery.align`: String, default "left"
      - `service_gallery.accentColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `service_gallery.titleColor`: String, default "var(--ion-text-color, #1e293b)"
      - `service_gallery.titleFontSize`: String, default "1em"
      - `service_gallery.titleFontWeight`: String, default "700"
      - `service_gallery.titleFontFamily`: String, default ""
      - `service_gallery.titleTransform`: String, default "none"
      - `service_gallery.priceColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `service_gallery.priceFontSize`: String, default "0.94em"
      - `service_gallery.priceFontWeight`: String, default "600"
      - `service_gallery.priceFontFamily`: String, default ""
      - `service_gallery.priceFontStyle`: String, default "normal"
      - `service_gallery.metaColor`: String, default "rgba(var(--ion-text-color-rgb, 30,41,59), 0.66)"
      - `service_gallery.titlePosition`: String, default "bottom"
      - `service_gallery.imageSide`: String, default "left"
      - `service_gallery.imagePadding`: Number, default 0
      - `service_gallery.boxShadowOffsetX`: Number, default 0
      - `service_gallery.descFontSize`: String, default "13px"
      - `service_gallery.descFontWeight`: String, default "400"
      - `service_gallery.animation`: String, default "none"
      - `service_gallery.animationDuration`: Number, default 0
      - `service_gallery.animationDelay`: Number, default 0
      - `service_gallery.showCustomFields`: Boolean, default false
      - `service_gallery.cardDesign`: String, default ""
      - `service_gallery.carouselLoop`: Boolean, default false
      - `service_gallery.carouselControls`: Boolean, default false
      - `service_gallery.customCss`: String, default ""
      - `blog_gallery.design_layout`: String, default ""
      - `blog_gallery.mode`: String, default "card"
      - `blog_gallery.columns`: Number, default 3
      - `blog_gallery.leadLayout`: String, default "beside"
      - `blog_gallery.leadWidth`: Number, default 58
      - `blog_gallery.placeholderItems`: Object[]
      - `blog_gallery.placeholderPlateColor`: String, default ""
      - `blog_gallery.showTitle`: Boolean, default true
      - `blog_gallery.showExcerpt`: Boolean, default false
      - `blog_gallery.showDate`: Boolean, default true
      - `blog_gallery.showImage`: Boolean, default true
      - `blog_gallery.showLink`: Boolean, default false
      - `blog_gallery.linkLabel`: String, default "Read more"
      - `blog_gallery.showCustomFields`: Boolean, default false
      - `blog_gallery.imageAspectRatio`: String, default "1/1"
      - `blog_gallery.imageHeight`: Number, default 260
      - `blog_gallery.imageRadius`: Number, default 20
      - `blog_gallery.imageObjectFit`: String, default "cover"
      - `blog_gallery.imagePadding`: Number, default 0
      - `blog_gallery.imageSide`: String, default "left"
      - `blog_gallery.cardBackgroundColor`: String, default "var(--ion-card-background, #ffffff)"
      - `blog_gallery.cardBorderRadius`: Number, default 28
      - `blog_gallery.cardBorderWidth`: Number, default 0
      - `blog_gallery.cardBorderColor`: String, default "transparent"
      - `blog_gallery.cardPaddingX`: Number, default 20
      - `blog_gallery.cardPaddingY`: Number, default 20
      - `blog_gallery.gap`: Number, default 24
      - `blog_gallery.boxShadowColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.22)"
      - `blog_gallery.boxShadowOffsetX`: Number, default 0
      - `blog_gallery.boxShadowOffsetY`: Number, default 20
      - `blog_gallery.boxShadowBlurRadius`: Number, default 40
      - `blog_gallery.boxShadowSpreadRadius`: Number, default -22
      - `blog_gallery.fieldBackground`: String, default "transparent"
      - `blog_gallery.hairlineColor`: String, default "transparent"
      - `blog_gallery.hairlineWidth`: Number, default 0
      - `blog_gallery.rowRuleColor`: String, default "transparent"
      - `blog_gallery.rowRuleWidth`: Number, default 0
      - `blog_gallery.maxWidth`: Number, default 0
      - `blog_gallery.scrimGradient`: String, default ""
      - `blog_gallery.align`: String, default "left"
      - `blog_gallery.accentColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `blog_gallery.titleColor`: String, default "var(--ion-text-color, #1e293b)"
      - `blog_gallery.titleFontSize`: String, default "1em"
      - `blog_gallery.titleFontWeight`: String, default "700"
      - `blog_gallery.titleFontFamily`: String, default ""
      - `blog_gallery.titleTransform`: String, default "none"
      - `blog_gallery.dateColor`: String, default "var(--ion-color-primary, #3880ff)"
      - `blog_gallery.dateFontSize`: String, default "0.81em"
      - `blog_gallery.dateFontWeight`: String, default "600"
      - `blog_gallery.dateFontFamily`: String, default ""
      - `blog_gallery.dateFontStyle`: String, default "normal"
      - `blog_gallery.metaColor`: String, default "rgba(var(--ion-text-color-rgb, 30,41,59), 0.66)"
      - `blog_gallery.excerptFontSize`: String, default "0.88em"
      - `blog_gallery.excerptFontWeight`: String, default "400"
      - `blog_gallery.excerptFontFamily`: String, default ""
      - `blog_gallery.titlePosition`: String, default "bottom"
      - `blog_gallery.animation`: String, default "none"
      - `blog_gallery.animationDuration`: Number, default 0
      - `blog_gallery.animationDelay`: Number, default 0
      - `blog_gallery.cardDesign`: String, default ""
      - `blog_gallery.customCss`: String, default ""
      - `multi_booking.layout`: String, default "chips"
      - `multi_booking.columns`: Number, default 3
      - `multi_booking.pickerBackground`: String, default "transparent"
      - `multi_booking.gap`: Number, default 10
      - `multi_booking.align`: String, default "left"
      - `multi_booking.showPrice`: Boolean, default false
      - `multi_booking.showDuration`: Boolean, default false
      - `multi_booking.showDescription`: Boolean, default false
      - `multi_booking.showImage`: Boolean, default false
      - `multi_booking.itemPaddingX`: Number, default 18
      - `multi_booking.itemPaddingY`: Number, default 10
      - `multi_booking.itemRadius`: Number, default 100
      - `multi_booking.itemBorderWidth`: Number, default 0
      - `multi_booking.itemBorderColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.12)"
      - `multi_booking.itemBackground`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.10)"
      - `multi_booking.itemTextColor`: String, default "var(--ion-text-color, inherit)"
      - `multi_booking.itemSubTextColor`: String, default "var(--ion-color-medium, rgba(0,0,0,0.55))"
      - `multi_booking.selectedBackground`: String, default "var(--ion-color-primary)"
      - `multi_booking.selectedTextColor`: String, default "var(--ion-color-primary-contrast, #ffffff)"
      - `multi_booking.selectedSubTextColor`: String, default "rgba(var(--ion-color-primary-contrast-rgb, 255,255,255), 0.75)"
      - `multi_booking.selectedBorderColor`: String, default "var(--ion-color-primary)"
      - `multi_booking.hoverBackground`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.18)"
      - `multi_booking.titleFontSize`: Number, default 14
      - `multi_booking.titleFontWeight`: String, default "500"
      - `multi_booking.subFontSize`: Number, default 13
      - `multi_booking.dividerColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.12)"
      - `multi_booking.dividerWidth`: Number, default 1
      - `multi_booking.autoScroll`: Boolean, default true
      - `multi_booking.cardBackground`: String, default "var(--ion-card-color, #ffffff)"
      - `multi_booking.cardRadius`: Number, default 28
      - `multi_booking.cardBorderWidth`: Number, default 0
      - `multi_booking.cardBorderColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.12)"
      - `multi_booking.cardMaxWidth`: Number, default 820
      - `multi_booking.cardLayout`: String, default "stack"
      - `multi_booking.splitWidth`: Number, default 340
      - `multi_booking.shadowColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.18)"
      - `multi_booking.shadowOffsetY`: Number, default 20
      - `multi_booking.shadowBlur`: Number, default 40
      - `multi_booking.shadowSpread`: Number, default -22
      - `multi_booking.sectionPaddingX`: Number, default 40
      - `multi_booking.sectionPaddingY`: Number, default 32
      - `multi_booking.sectionDividerColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.12)"
      - `multi_booking.sectionDividerWidth`: Number, default 1
      - `multi_booking.showStepLabels`: Boolean, default true
      - `multi_booking.serviceStepLabel`: String, default "Step 1 — Service"
      - `multi_booking.dayStepLabel`: String, default "Step 2 — Day"
      - `multi_booking.staffStepLabel`: String, default "Step 3 — Staff member"
      - `multi_booking.timeStepLabel`: String, default "Step 4 — Time"
      - `multi_booking.stepLabelColor`: String, default "var(--ion-color-medium, rgba(0,0,0,0.55))"
      - `multi_booking.stepLabelSize`: Number, default 13
      - `multi_booking.stepLabelWeight`: String, default "700"
      - `multi_booking.stepLabelSpacing`: Number, default 0.04
      - `multi_booking.stepLabelUppercase`: Boolean, default true
      - `multi_booking.dayCount`: Number, default 6
      - `multi_booking.dayLayout`: String, default "strip"
      - `multi_booking.dayBorderColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.15)"
      - `multi_booking.dayShape`: String, default "circle"
      - `multi_booking.daySize`: Number, default 64
      - `multi_booking.dayGap`: Number, default 10
      - `multi_booking.dayShowWeekday`: Boolean, default false
      - `multi_booking.dayBackground`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.10)"
      - `multi_booking.dayTextColor`: String, default "var(--ion-text-color, inherit)"
      - `multi_booking.daySelectedBackground`: String, default "var(--ion-color-primary)"
      - `multi_booking.daySelectedTextColor`: String, default "var(--ion-color-primary-contrast, #ffffff)"
      - `multi_booking.dayDisabledOpacity`: Number, default 0.35
      - `multi_booking.timeGap`: Number, default 10
      - `multi_booking.timeRadius`: Number, default 100
      - `multi_booking.timeBorderWidth`: Number, default 0
      - `multi_booking.timeBorderColor`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.15)"
      - `multi_booking.timeSelectedBorderColor`: String, default "var(--ion-color-primary)"
      - `multi_booking.timePaddingX`: Number, default 18
      - `multi_booking.timePaddingY`: Number, default 10
      - `multi_booking.timeBackground`: String, default "rgba(var(--ion-color-primary-rgb, 56,128,255), 0.10)"
      - `multi_booking.timeTextColor`: String, default "var(--ion-text-color, inherit)"
      - `multi_booking.timeSelectedBackground`: String, default "var(--ion-color-primary)"
      - `multi_booking.timeSelectedTextColor`: String, default "var(--ion-color-primary-contrast, #ffffff)"
      - `multi_booking.buttonText`: String, default "Confirm my booking"
      - `multi_booking.buttonBackground`: String, default "var(--ion-color-primary)"
      - `multi_booking.buttonTextColor`: String, default "var(--ion-color-primary-contrast, #ffffff)"
      - `multi_booking.buttonRadius`: Number, default 100
      - `multi_booking.buttonPaddingY`: Number, default 16
      - `multi_booking.buttonFontSize`: Number, default 15
      - `multi_booking.buttonFontWeight`: String, default "700"
      - `multi_booking.buttonFullWidth`: Boolean, default true
      - `blogposts`: ObjectId[], → BlogPost
      - `videos`: ObjectId[], → Video
      - `price_plans`: ObjectId[], → PricePlan
      - `href`: String, default "#"
      - `router_link`: String
      - `buttonOptions.fill`: String
      - `buttonOptions.expand`: String
      - `buttonOptions.size`: String
      - `buttonOptions.color`: String
      - `buttonOptions.class`: String
      - `buttonOptions.customColor`: String
      - `buttonOptions.customColorContrast`: String
      - `buttonOptions.customColorHover`: String
      - `buttonOptions.hoverRoll`: Boolean
      - `buttonOptions.fontWeight`: Number
      - `buttonOptions.letterSpacing`: Number
      - `buttonOptions.fontSize`: Number, default 14
      - `buttonOptions.borderRadius`: Number, default 6
      - `buttonOptions.customColorA`: String
      - `buttonOptions.customColorB`: String
      - `buttonOptions.customColorC`: String
      - `buttonOptions.custom_measurements`: Boolean, default false
      - `buttonOptions.height.value`: String
      - `buttonOptions.height.unit`: String
      - `buttonOptions.width.value`: String
      - `buttonOptions.width.unit`: String
      - `carousel.showArrows`: Boolean, default true
      - `carousel.showIndicators`: Boolean, default true
      - `carousel.height`: Number, default 90
      - `carousel.automaticHeight`: Boolean, default true
      - `carousel.carouselMedias`: Object[]
      - `map`: String
      - `contactForm.name`: String
      - `contactForm.email`: String
      - `contactForm.subject`: String
      - `contactForm.body`: String
      - `contactForm.button`: String
      - `contactForm.hideNewsletter`: Boolean
      - `contactForm.placeholders.name`: String
      - `contactForm.placeholders.email`: String
      - `contactForm.placeholders.subject`: String
      - `contactForm.placeholders.body`: String
      - `contactForm.style.backgroundColor`: String
      - `contactForm.style.textColor`: String
      - `contactForm.style.accentColor`: String
      - `contactForm.style.border.color`: String
      - `contactForm.style.border.width.value`: Mixed
      - `contactForm.style.border.width.unit`: String
      - `contactForm.style.border.style`: String
      - `contactForm.style.borderRadius.value`: Mixed
      - `contactForm.style.borderRadius.unit`: String
      - `contactForm.style.padding.value`: Mixed
      - `contactForm.style.padding.unit`: String
      - `contactForm.style.labelFontSize.value`: Mixed
      - `contactForm.style.labelFontSize.unit`: String
      - `contactForm.style.consentFontSize.value`: Mixed
      - `contactForm.style.consentFontSize.unit`: String
      - `contactForm.style.inputBackgroundColor`: String
      - `contactForm.style.inputTextColor`: String
      - `contactForm.style.placeholderColor`: String
      - `contactForm.style.inputFocusColor`: String
      - `contactForm.style.inputFontSize.value`: Mixed
      - `contactForm.style.inputFontSize.unit`: String
      - `contactForm.style.inputPadding.value`: Mixed
      - `contactForm.style.inputPadding.unit`: String
      - `contactForm.style.textareaMinHeight.value`: Mixed
      - `contactForm.style.textareaMinHeight.unit`: String
      - `contactForm.style.inputMargin.value`: Mixed
      - `contactForm.style.inputMargin.unit`: String
      - `contactForm.style.inputBorder.color`: String
      - `contactForm.style.inputBorder.width.value`: Mixed
      - `contactForm.style.inputBorder.width.unit`: String
      - `contactForm.style.inputBorder.style`: String
      - `contactForm.style.inputBorderRadius.value`: Mixed
      - `contactForm.style.inputBorderRadius.unit`: String
      - `contactForm.style.buttonBackgroundColor`: String
      - `contactForm.style.buttonTextColor`: String
      - `contactForm.style.buttonAlign`: String
      - `contactForm.style.buttonFontSize.value`: Mixed
      - `contactForm.style.buttonFontSize.unit`: String
      - `contactForm.style.buttonBorder.color`: String
      - `contactForm.style.buttonBorder.width.value`: Mixed
      - `contactForm.style.buttonBorder.width.unit`: String
      - `contactForm.style.buttonBorder.style`: String
      - `contactForm.style.buttonBorderRadius.value`: Mixed
      - `contactForm.style.buttonBorderRadius.unit`: String
      - `contactForm.style.buttonWidth.value`: Mixed
      - `contactForm.style.buttonWidth.unit`: String
      - `contactForm.additional`: Object[]
      - `instagram.columns`: Number
      - `instagram.first`: Number
      - `instagram.skip`: Number
      - `instagram.showInfo`: Boolean
      - `instagram.gridType`: Number
      - `instagram.profile`: String
      - `facebook.url`: String
      - `facebook.width`: String
      - `facebook.height`: String
      - `animation`: String, default "none"
      - `animationDuration`: Number, default 0
      - `animationStrength`: Number
      - `parallaxEnabled`: Boolean
      - `parallaxAmount`: Number
      - `parallaxZoom`: Number
      - `textReveal`: String
      - `animationDelay`: Number, default 0
      - `animationReplay`: String, default "once"
      - `animationEasing`: String, default "power3.out"
      - `animationBreakpoint`: String, default "all"
      - `scrollScrubEnabled`: Boolean, default false
      - `scrollScrubAxis`: String, default "y"
      - `scrollScrubDistance`: Number, default 80
      - `motionOrigin`: String, default "center center"
      - `hoverEnabled`: Boolean, default false
      - `hoverEffect`: String, default "none"
      - `hoverDuration`: Number, default 240
      - `hoverIntensity`: Number, default 4
      - `continuousEnabled`: Boolean, default false
      - `continuousEffect`: String, default "none"
      - `continuousDuration`: Number, default 2400
      - `continuousIntensity`: Number, default 4
      - `statefulEnabled`: Boolean, default false
      - `statefulEffect`: String, default "none"
      - `statefulDuration`: Number, default 260
      - `statefulStagger`: Number, default 60
      - `transform.enabled`: Boolean, default false
      - `transform.translateX.value`: Number, default 0
      - `transform.translateX.unit`: String, default "px"
      - `transform.translateY.value`: Number, default 0
      - `transform.translateY.unit`: String, default "px"
      - `transform.scaleX`: Number, default 1
      - `transform.scaleY`: Number, default 1
      - `transform.rotate`: Number, default 0
      - `transform.rotateX`: Number, default 0
      - `transform.rotateY`: Number, default 0
      - `transform.skewX`: Number, default 0
      - `transform.skewY`: Number, default 0
      - `transform.perspective`: Number, default 0
      - `transform.originX`: String, default "center"
      - `transform.originY`: String, default "center"
      - `opacity`: Mixed
      - `transitionDuration`: Mixed
      - `transitionEasing`: String
      - `states`: Mixed
      - `useIframe`: Boolean, default false
      - `popup.text`: String
      - `popup.backgroundColor`: String
      - `popup.fullScreen`: Boolean, default false
      - `popup.about_page`: ObjectId, → AboutPage
      - `custom_component.html`: String
      - `custom_component.compiled_html`: String
      - `custom_component.css`: String
      - `custom_component.compiled_css`: String
      - `custom_component.js`: String
      - `custom_component.iframe`: Boolean, default false
      - `custom_component.variables`: Object[]
      - `hoverable_media.before_hover_media.media`: ObjectId, → Media
      - `hoverable_media.before_hover_media.gcs_url`: String
      - `hoverable_media.before_hover_media.vimeo_uri`: String
      - `hoverable_media.before_hover_media.vimeo_picture`: String
      - `hoverable_media.before_hover_media.youtube_url`: String
      - `hoverable_media.before_hover_media.vimeo_url`: String
      - `hoverable_media.before_hover_media.href`: String
      - `hoverable_media.before_hover_media.router_link`: String
      - `hoverable_media.before_hover_media.thumbnail`: String
      - `hoverable_media.before_hover_media.autoplay`: Boolean, default false
      - `hoverable_media.before_hover_media.loop`: Boolean, default false
      - `hoverable_media.before_hover_media.show_controls`: Boolean, default true
      - `hoverable_media.after_hover_media.media`: ObjectId, → Media
      - `hoverable_media.after_hover_media.gcs_url`: String
      - `hoverable_media.after_hover_media.vimeo_uri`: String
      - `hoverable_media.after_hover_media.vimeo_picture`: String
      - `hoverable_media.after_hover_media.youtube_url`: String
      - `hoverable_media.after_hover_media.vimeo_url`: String
      - `hoverable_media.after_hover_media.href`: String
      - `hoverable_media.after_hover_media.router_link`: String
      - `hoverable_media.after_hover_media.thumbnail`: String
      - `hoverable_media.after_hover_media.autoplay`: Boolean, default false
      - `hoverable_media.after_hover_media.loop`: Boolean, default false
      - `hoverable_media.after_hover_media.show_controls`: Boolean, default true
      - `collapsible_container`: Object[]
      - `collapsible_container_settings.enable_customization`: Boolean, default false
      - `collapsible_container_settings.title_color`: String
      - `collapsible_container_settings.title_color_checked`: String
      - `collapsible_container_settings.title_background`: String
      - `collapsible_container_settings.title_background_checked`: String
      - `collapsible_container_settings.title_font_size`: String
      - `collapsible_container_settings.title_border_radius`: String
      - `collapsible_container_settings.title_border_color`: String
      - `collapsible_container_settings.title_border_width`: String
      - `collapsible_container_settings.body_color`: String
      - `collapsible_container_settings.body_background`: String
      - `collapsible_container_settings.body_border_radius`: String
      - `collapsible_container_settings.body_border_color`: String
      - `collapsible_container_settings.body_border_width`: String
      - `collapsible_container_settings.body_font_size`: String
      - `collapsible_container_settings.icon_color`: String
      - `collapsible_container_settings.icon_glyph_color`: String
      - `collapsible_container_settings.icon_background_color`: String
      - `quiz.title`: String
      - `quiz.header`: String
      - `quiz.questions`: Object[]
      - `quiz.score_results`: Object[]
      - `quiz.show_right_answers`: Boolean, default false
      - `quiz.mutliple_choice`: Boolean, default false
      - `partners.defaultCollection`: String, default ""
      - `partners.defaultView`: String, default "locations"
      - `scroll_sequence.media`: ObjectId, → Media
      - `scroll_sequence.scroll_length`: Number, default 3
      - `scroll_sequence.fit`: String, one of cover | contain, default "cover"
      - `scroll_sequence.background_color`: String, default "#000000"
      - `scroll_sequence.overlay_color`: String, default ""
      - `scroll_sequence.overlay_opacity`: Number, default 0
      - `scroll_sequence.rows_over`: Boolean, default true
      - `scroll_sequence.rows_effect`: String, one of slide | pop, default "slide"
      - `scroll_sequence.snap_points`: Number[]
      - `scroll_sequence.snap_scroll_speed`: Number, default 1.5
      - `scroll_sequence.snap_down`: Boolean, default true
      - `scroll_sequence.snap_up`: Boolean, default false
      - `scroll_sequence.stop_momentum`: Boolean, default false
      - `smart_search.placeholder`: String, default ""
      - `smart_search.kinds`: String[]
      - `smart_search.collections`: String[]
      - `smart_search.items`: Object[]
      - `smart_search.showKindFilter`: Boolean, default true
      - `smart_search.showPriceFilter`: Boolean, default false
      - `smart_search.showDateFilter`: Boolean, default false
      - `smart_search.showSort`: Boolean, default true
      - `smart_search.dateMode`: String, one of created | updated | availability, default "created"
      - `smart_search.layout`: String, one of grid | list, default "grid"
      - `smart_search.columns`: Number, default 3
      - `smart_search.perPage`: Number, default 12
      - `smart_search.instant`: Boolean, default true
      - `smart_search.showImages`: Boolean, default true
      - `smart_search.showPrices`: Boolean, default true
      - `smart_search.showInitial`: Boolean, default false
      - `smart_search.showAnswer`: Boolean, default false
      - `countdown.targetDate`: String, default ""
      - `countdown.expiredText`: String, default "Offer has ended"
      - `countdown.hideOnExpire`: Boolean, default false
      - `countdown.textColor`: String, default ""
      - `countdown.boxColor`: String, default ""
      - `countdown.align`: String, one of  | left | center | right, default ""
      - `swiper.slides`: Object[]
      - `swiper.autoplay`: Boolean, default false
      - `swiper.loop`: Boolean, default false
      - `swiper.show_controls`: Boolean, default true
      - `swiper.dynamic_bullets`: Boolean, default false
      - `swiper.pagination`: Boolean, default true
      - `dynamicField`: String
      - `useDynamicField`: Boolean, default false
      - `text_auto_slider.speed`: Number
      - `text_auto_slider.aspect_ratio`: String
      - `text_auto_slider.text_color`: String
      - `text_auto_slider.background_color`: String
      - `text_auto_slider.delimiter_color`: String
      - `text_auto_slider.direction`: String
      - `text_auto_slider.width`: Number
      - `text_auto_slider.font_size`: Number
      - `text_auto_slider.text_transform`: String, one of none | uppercase, default "none"
      - `text_auto_slider.texts`: Object[]
      - `image_auto_slider.speed`: Number
      - `image_auto_slider.aspect_ratio`: String
      - `image_auto_slider.direction`: String
      - `image_auto_slider.width`: Number
      - `image_auto_slider.medias`: Object[]
      - `image_dragger.aspect_ratio`: String
      - `image_dragger.media_1.media`: ObjectId, → Media
      - `image_dragger.media_1.gcs_url`: String
      - `image_dragger.media_1.vimeo_uri`: String
      - `image_dragger.media_1.vimeo_picture`: String
      - `image_dragger.media_1.vimeo_url`: String
      - `image_dragger.media_1.youtube_url`: String
      - `image_dragger.media_2.media`: ObjectId, → Media
      - `image_dragger.media_2.gcs_url`: String
      - `image_dragger.media_2.vimeo_uri`: String
      - `image_dragger.media_2.vimeo_picture`: String
      - `image_dragger.media_2.vimeo_url`: String
      - `image_dragger.media_2.youtube_url`: String
      - `image_dragger.media_2.title`: String
      - `image_dragger.media_2.body`: String
      - `image_dragger.media_2.textColor`: String
      - `image_dragger.media_2.href`: String
      - `image_dragger.media_2.router_link`: String
      - `_id`: ObjectId
    - `sections`: Object[]
      - `position`: String, one of static | relative | absolute | fixed | sticky, default "static"
      - `overflow`: String, one of visible | hidden | scroll | auto, default "visible"
      - `editorName`: String
      - `top.value`: String, default "0"
      - `top.unit`: String, default "px"
      - `top.mobile_value`: String, default ""
      - `top.mobile_unit`: String, default "px"
      - `left.value`: String, default "0"
      - `left.unit`: String, default "px"
      - `left.mobile_value`: String, default ""
      - `left.mobile_unit`: String, default "px"
      - `right.value`: String, default "0"
      - `right.unit`: String, default "px"
      - `right.mobile_value`: String, default ""
      - `right.mobile_unit`: String, default "px"
      - `bottom.value`: String, default "0"
      - `bottom.unit`: String, default "px"
      - `bottom.mobile_value`: String, default ""
      - `bottom.mobile_unit`: String, default "px"
      - `display`: String, one of block | inline | flex, default "block"
      - `flex_direction`: String, one of row | row-reverse | column | column-reverse, default "row"
      - `flex_direction_mobile`: String, default ""
      - `backdropFilter`: String, default ""
      - `justify`: String, one of start | end | center | space-between | space-around | stretch, default "start"
      - `justify_mobile`: String, default ""
      - `align`: String, one of start | end | center | space-between | space-around | stretch, default "start"
      - `align_mobile`: String, default ""
      - `animation`: String, default "none"
      - `animationDuration`: Number, default 0
      - `animationStrength`: Number
      - `parallaxEnabled`: Boolean
      - `parallaxAmount`: Number
      - `parallaxZoom`: Number
      - `animationDelay`: Number, default 0
      - `animationReplay`: String, default "once"
      - `animationEasing`: String, default "power3.out"
      - `animationBreakpoint`: String, default "all"
      - `scrollScrubEnabled`: Boolean, default false
      - `scrollScrubAxis`: String, default "y"
      - `scrollScrubDistance`: Number, default 80
      - `motionOrigin`: String, default "center center"
      - `hoverEnabled`: Boolean, default false
      - `hoverEffect`: String, default "none"
      - `hoverDuration`: Number, default 240
      - `hoverIntensity`: Number, default 4
      - `continuousEnabled`: Boolean, default false
      - `continuousEffect`: String, default "none"
      - `continuousDuration`: Number, default 2400
      - `continuousIntensity`: Number, default 4
      - `statefulEnabled`: Boolean, default false
      - `statefulEffect`: String, default "none"
      - `statefulDuration`: Number, default 260
      - `statefulStagger`: Number, default 60
      - `transform.enabled`: Boolean, default false
      - `transform.translateX.value`: Number, default 0
      - `transform.translateX.unit`: String, default "px"
      - `transform.translateY.value`: Number, default 0
      - `transform.translateY.unit`: String, default "px"
      - `transform.scaleX`: Number, default 1
      - `transform.scaleY`: Number, default 1
      - `transform.rotate`: Number, default 0
      - `transform.rotateX`: Number, default 0
      - `transform.rotateY`: Number, default 0
      - `transform.skewX`: Number, default 0
      - `transform.skewY`: Number, default 0
      - `transform.perspective`: Number, default 0
      - `transform.originX`: String, default "center"
      - `transform.originY`: String, default "center"
      - `opacity`: Mixed
      - `transitionDuration`: Mixed
      - `transitionEasing`: String
      - `states`: Mixed
      - `hoverGroup`: Boolean
      - `flex_wrap`: String, one of nowrap | wrap | wrap-reverse, default "wrap"
      - `flex_wrap_mobile`: String, default ""
      - `width.value`: String, default ""
      - `width.unit`: String, default "px"
      - `width.mobile_value`: String, default ""
      - `width.mobile_unit`: String, default "px"
      - `min_width.value`: String, default ""
      - `min_width.unit`: String, default "px"
      - `min_width.mobile_value`: String, default ""
      - `min_width.mobile_unit`: String, default "px"
      - `max_width.value`: String, default ""
      - `max_width.unit`: String, default "px"
      - `max_width.mobile_value`: String, default ""
      - `max_width.mobile_unit`: String, default "px"
      - `height.value`: String, default ""
      - `height.unit`: String, default "px"
      - `height.mobile_value`: String, default ""
      - `height.mobile_unit`: String, default "px"
      - `min_height.value`: String, default ""
      - `min_height.unit`: String, default "px"
      - `min_height.mobile_value`: String, default ""
      - `min_height.mobile_unit`: String, default "px"
      - `max_height.value`: String, default ""
      - `max_height.unit`: String, default "px"
      - `max_height.mobile_value`: String, default ""
      - `max_height.mobile_unit`: String, default "px"
      - `object_fit`: String
      - `aspect_ratio.custom`: Boolean, default false
      - `aspect_ratio.ratio`: String, default ""
      - `margin_left.value`: String, default "0"
      - `margin_left.unit`: String, default "px"
      - `margin_left.mobile_value`: String, default ""
      - `margin_left.mobile_unit`: String, default "px"
      - `margin_right.value`: String, default "0"
      - `margin_right.unit`: String, default "px"
      - `margin_right.mobile_value`: String, default ""
      - `margin_right.mobile_unit`: String, default "px"
      - `margin_top.value`: String, default "0"
      - `margin_top.unit`: String, default "px"
      - `margin_top.mobile_value`: String, default ""
      - `margin_top.mobile_unit`: String, default "px"
      - `margin_bottom.value`: String, default "0"
      - `margin_bottom.unit`: String, default "px"
      - `margin_bottom.mobile_value`: String, default ""
      - `margin_bottom.mobile_unit`: String, default "px"
      - `padding_left.value`: String, default "0"
      - `padding_left.unit`: String, default "px"
      - `padding_left.mobile_value`: String, default ""
      - `padding_left.mobile_unit`: String, default "px"
      - `padding_right.value`: String, default "0"
      - `padding_right.unit`: String, default "px"
      - `padding_right.mobile_value`: String, default ""
      - `padding_right.mobile_unit`: String, default "px"
      - `padding_top.value`: String, default "0"
      - `padding_top.unit`: String, default "px"
      - `padding_top.mobile_value`: String, default ""
      - `padding_top.mobile_unit`: String, default "px"
      - `padding_bottom.value`: String, default "0"
      - `padding_bottom.unit`: String, default "px"
      - `padding_bottom.mobile_value`: String, default ""
      - `padding_bottom.mobile_unit`: String, default "px"
      - `border_left.style`: String, default "none"
      - `border_left.color`: String
      - `border_left.width.value`: String, default "0"
      - `border_left.width.unit`: String, default "px"
      - `border_right.style`: String, default "none"
      - `border_right.color`: String
      - `border_right.width.value`: String, default "0"
      - `border_right.width.unit`: String, default "px"
      - `border_top.style`: String, default "none"
      - `border_top.color`: String
      - `border_top.width.value`: String, default "0"
      - `border_top.width.unit`: String, default "px"
      - `border_bottom.style`: String, default "none"
      - `border_bottom.color`: String
      - `border_bottom.width.value`: String, default "0"
      - `border_bottom.width.unit`: String, default "px"
      - `border_top_left_radius.value`: String, default "0"
      - `border_top_left_radius.unit`: String, default "px"
      - `border_top_right_radius.value`: String, default "0"
      - `border_top_right_radius.unit`: String, default "px"
      - `border_bottom_left_radius.value`: String, default "0"
      - `border_bottom_left_radius.unit`: String, default "px"
      - `border_bottom_right_radius.value`: String, default "0"
      - `border_bottom_right_radius.unit`: String, default "px"
      - `border_radius.value`: String, default "0"
      - `border_radius.unit`: String, default "px"
      - `specific_border_radius`: Boolean, default false
      - `box_shadows`: Object[]
      - `backgroundColor`: String
      - `backgroundGradient.enabled`: Boolean, default false
      - `backgroundGradient.type`: String
      - `backgroundGradient.degrees`: Number
      - `backgroundGradient.radialShape`: String
      - `backgroundGradient.radialPosition`: String
      - `backgroundGradient.colors`: Object[]
      - `backgroundGradient.css`: String
      - `backgroundVideo`: String
      - `backgroundImage`: String
      - `backgroundSize`: String
      - `backgroundPosition`: String
      - `backgroundRepeat`: String
      - `showOnMobile`: Boolean, default true
      - `showOnDesktop`: Boolean, default true
      - `components`: Object[]
      - `sections`: Object[]
      - `_id`: ObjectId
    - `_id`: ObjectId
  - `ai_meta`: Mixed
  - `_id`: ObjectId
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### DynamicTrigger

An automation that runs when records of a custom data model change.

- `enabled`: Boolean, default false - The trigger runs.
- `name`: String - Name of the trigger.
- `model`: String - The model whose changes start it.
- `type`: String, one of CREATE | READ | UPDATE | DELETE - Which change of a model's record starts it. One of: CREATE, READ, UPDATE, DELETE.
- `code`: String - The script the trigger runs.
- `last_run_at`: Date - When it last ran.
- `last_run_status`: String, one of  | success | failed - One of: success, failed.
- `last_error`: String - The error of the last run, if it failed.
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### Media

An uploaded image, video or file.

- `file`: Buffer - (buffer) base64 image data.
- `file_path`: String - The bytes in Cloud Storage instead of `file` (utils/media-store.js): the object - named by the bytes' sha256, so identical pictures share one - and that hash.
- `alt`: String - Img alt (same as filename if not specified).
- `filename`: String - Name of the stored file.
- `filetype`: String - MIME type of the file.
- `vimeo_uri`: String - The video's Vimeo id, for videos hosted there.
- `vimeo_picture`: String - Thumbnail Vimeo made.
- `duration`: Number - Length of a video in seconds.
- `vimeo_url`: String - Link to the video on Vimeo.
- `youtube_url`: String - Link to the video on YouTube.
- `youtube_picture`: String - Thumbnail of the YouTube video.
- `thumbnail`: String - Small preview of the file.
- `gcs_url`: String - The file's address in cloud storage.
- `gcs_480_30_url`: String - The video transcoded to 480p.
- `gcs_720_30_url`: String - The video transcoded to 720p.
- `gcs_1080_30_url`: String - The video transcoded to 1080p.
- `picture_gcs_url`: String - The picture's address in cloud storage.
- `transcode_status`: String, default "not_started" - 'not_started' | 'pending' | 'error' | 'completed'.
- `owner_id`: ObjectId, → User - User id of owner.
- `sequence.status`: String
- `sequence.started_at`: Date
- `sequence.updated_at`: Date
- `sequence.step`: String
- `sequence.done`: Number
- `sequence.total`: Number
- `sequence.ext`: String
- `sequence.trim_start`: Number
- `sequence.trim_end`: Number
- `sequence.build_id`: String
- `sequence.previous_base`: String
- `sequence.frame_count`: Number
- `sequence.fps`: Number
- `sequence.width`: Number
- `sequence.height`: Number
- `sequence.desktop_path`: String
- `sequence.mobile_path`: String
- `sequence.portrait_path`: String
- `sequence.poster`: String
- `sequence.error`: String
- `source_url`: String - Site-clone real-asset reuse: the original URL this Media was downloaded from (asset-fetcher.js). Lets repeated/duplicate clone runs reuse the same stored asset instead of re-downloading it.
- `source_framing`: String - How a downloaded picture was prepared before it was stored (asset-fetcher.js FRAMING): a copy made before the frame its site baked in was taken off is not reused for one made after.
- `origin`: String - 'ai' for images the AI builder generated (validators.js). The site-readiness checklist counts the items still showing one, so people know which photos to replace with their own. Unset on uploads and on media created before it.
- `width`: Number - Displayed size of an image file (utils/image-size.js), so pages can give the <img> width/height and reserve its space before it loads. Set on save; older media by scripts/backfill-media-size.js.
- `height`: Number - Height of the picture in pixels.
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### MiniApp

- `title`: String, required - The mini app's name.
- `header`: String, required - Short heading shown in the app store.
- `description`: String - About the mini app.
- `referer`: String, required - The address the mini app is served from.
- `requiresLogin`: Boolean, default true - Only signed-in users can open it.
- `icon`: ObjectId, → Media - Media.id.
- `previews`: Object[] - Screenshots shown in the store.
  - `media`: ObjectId, → Media
  - `_id`: ObjectId
- `owner_id`: ObjectId, required, → User - User.id.
- `reviewed`: Boolean, default false - A moderator reviewed it.
- `approved`: Boolean, default false - Approved to be listed.
- `total_opened`: Number, default 0 - How many times it was opened.
- `opened_by`: ObjectId[], → User - User ids.
- `minimum_age`: Number - Youngest age allowed to use it.
- `language`: String[] - Languages it supports.
- `collection_id`: ObjectId, → Collection - Collection.id.
- `total_views`: Number, default 0 - How many times its store page was viewed.
- `views`: ObjectId[], → User - User ids.
- `privacy_policy`: String, required - Link to its privacy policy.
- `data_collection.contact_info`: String[]
- `data_collection.health_info`: String[]
- `data_collection.payment_info`: String[]
- `data_collection.location_info`: String[]
- `data_collection.other_info`: String[]
- `data_collection.user_info`: String[]
- `data_collection.identifiers_info`: String[]
- `data_collection.usage_data`: String[]
- `data_collection.diagnostics_data`: String[]
- `support_url`: String - Link to support.
- `marketing_url`: String - Link to its marketing site.
- `copyright`: String - Copyright line.
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### Model

- `name`: String, required
- `make`: ObjectId, → Make
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### MyContact

The site's name and contact details.

- `zip_code`: String, required - Postal/ZIP code.
- `city`: String, required - The city where the business is located.
- `address`: String, required - The street address.
- `contact`: String - Html format.
- `email`: String, required - The public contact email address.
- `google_app`: String - Link to the business's app on Google Play.
- `workhours`: String - Opening hours as text.
- `logo`: ObjectId - Media.id.
- `site_name`: String, required - The business/site name shown publicly.
- `domain`: String, required - The site's own domain.
- `stock_domain`: String - The platform subdomain the site always answers on.
- `isMarket`: Boolean, required, default false - The site is a marketplace.
- `forbidden_collections`: Object[] - Array of {collection_id}.
  - `collection_id`: ObjectId, → Collection
  - `_id`: ObjectId
- `isInit`: Boolean, default false - The site was set up from a template.
- `template`: String, default "0000" - The template the site was made from.
- `lang`: String - 2-letter language code (e.g. "en", "fr", "de").
- `allowedComponents.services`: Boolean, default true
- `allowedComponents.products`: Boolean, default true
- `allowedComponents.blogposts`: Boolean, default true
- `allowedComponents.programs`: Boolean, default true
- `favicon`: ObjectId - Media.id of the browser tab icon.
- `cacheEnabled`: Boolean - Pages are cached for faster loading.
- `has_completed_startup_prompt`: Boolean, default true - The owner went through the first AI prompt.
- `gmb_location`: String - The Google Business Profile location linked to the site.
- `ga_property`: String - The Google Analytics property linked to the site.
- `site_generated_at`: Date - When the AI builder first built this site (a plan that made the homepage). Written once (runPlan); starts the admin's 15-minute launch offer.
- `admin_first_opened_at`: Date - When the owner first opened the admin; with admin_last_opened_at it tells "never opened it" from "opened it and left".
- `admin_last_opened_at`: Date - When the owner last opened the admin.
- `admin_in_app_browser`: Boolean - The owner last opened the admin inside an app's browser.
- `domain_suggestions`: Mixed - Domain names suggested to the owner, kept so they are not suggested again.
- `_id`: ObjectId

### Rating

- `email`: String, required - Email of who rated.
- `text`: String - The review's text.
- `medias`: Object[] - Pictures added to the review.
  - `media`: ObjectId, → Media
  - `_id`: ObjectId
- `stars`: Number, required - Rating from 1 to 5.
- `product`: ObjectId, → Product - Product.id.
- `service`: ObjectId, → Service - Service.id.
- `program`: ObjectId, → Program - Program.id.
- `price_plan`: ObjectId, → PricePlan - PricePlan.id.
- `name`: String - Name of who rated.
- `miniapp`: ObjectId, → MiniApp - MiniApp.id.
- `staff_member`: ObjectId, → User - User.id.
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date

### User

A person with an account on the site - customer, staff member or admin.

- `name`: String - Display name.
- `email`: String, required - Email, used to sign in.
- `avatar`: ObjectId - Media.id of the profile picture.
- `social_avatar`: String - Profile picture from a social sign-in.
- `isAdmin`: Boolean, default false - Can use the admin.
- `isStaff`: Boolean, default false - A staff member who can be booked.
- `owner_id`: ObjectId - User.id of the admin who added this user.
- `organization_name`: String - Company name.
- `firstname`: String - First name.
- `lastname`: String - Last name.
- `phone_number`: String - Phone number.
- `address`: String - Address.
- `city`: String - City.
- `state`: String - State or region.
- `country`: String - Country.
- `zip_code`: String - Postal code.
- `tax_identification_number`: String - Tax number.
- `notes`: String - Notes kept on the user.
- `isConfirmed`: Boolean, default false - The email was confirmed.
- `number_of_sales`: Number, default 0 - How many sales the user made (marketplace).
- `stripe_id`: String - The user's id in Stripe.
- `paypal_id`: String - The user's PayPal id.
- `access_level`: Number - Admin permission level; 0 is full access.
- `can_edit.site`: Boolean, default true
- `can_edit.crm`: Boolean, default true
- `can_edit.store`: Boolean, default true
- `can_edit.payments`: Boolean, default true
- `can_edit.staff`: Boolean, default true
- `validation_code`: String - Code sent to confirm the email.
- `price_plans`: ObjectId[], → PricePlan - PricePlan ids.
- `emotion`: String - Positive, neutral, negative.
- `political_spectrum`: String - Far left, left, far right, right, middle.
- `language`: Mixed - Multiple objects inside - key: value - example - "EN": 5 (5 being the weight);.
- `interests`: Mixed - Multiple objects inside - key: value - example - "hobby": 5 (5 being the weight);.
- `app_category`: Mixed - Multiple objects inside - key: value - example - "hobby": 5 (5 being the weight);.
- `user_profile`: Mixed - Multiple objects inside - key: value - example - "intelligent": 5 (5 being the weight);.
- `user_economics`: String - Low, middle, high.
- `user_education`: String - Low, middle, high.
- `marital_status`: String - Single, married.
- `age`: String - 0-18, 18-25, 25-30, 30-35, 35-45, 45-60.
- `sex`: String - Male - female.
- `attractiveness`: String - Low, medium, high.
- `following`: ObjectId[], → User - User ids.
- `followers`: ObjectId[], → User - User ids.
- `handle`: String - Unique @handle.
- `saved_posts`: ObjectId[], → Post - Post ids.
- `blocked_users`: ObjectId[], → User - User ids.
- `blocked_by`: ObjectId[], → User - User ids.
- `bio`: String - About the user.
- `wallet`: Number, default 0 - Balance in the user's wallet.
- `ip_location.city`: String
- `ip_location.continent_name`: String
- `ip_location.country_name`: String
- `ip_location.country_subdivision`: String
- `ip_location.location_latitude`: Number
- `ip_location.location_longitude`: Number
- `ip_location.last_updated`: Date
- `ip_location.ip_address`: String
- `ip_location.zip_code`: String
- `ip_location.dialing_code`: String
- `location`: Embedded - The user's chosen location.
  - `type`: String, one of Point
  - `coordinates`: Number[]
  - `_id`: ObjectId
- `checkout_recovery_at`: Date - When we last emailed them about a Premium checkout they left (utils/checkout-recovery).
- `search_history`: String[] - What the user searched for.
- `isRestricted`: Boolean, default false - Restricted by a moderator.
- `isMonetized`: Boolean, default false - Earns from their content.
- `monetization_request.isRequested`: Boolean, default false
- `monetization_request.requested_on`: Date
- `sentFollowersEmail`: Date - When the followers summary email was last sent.
- `isShadowbanned`: Boolean, default false - Their content is hidden from others without telling them.
- `custom_url`: Object[] - Custom addresses for their profile.
  - `heading`: String
  - `url`: String
  - `_id`: ObjectId
- `isWhitelisted`: Boolean, default false - Skips moderation.
- `isReviewedWhitelist`: Boolean, default false - The whitelist request was reviewed.
- `advertiser_id`: String - Id as an advertiser.
- `selected_events`: String[] - Events the user follows.
- `profile_feed_style`: String, default "list" - How their profile feed looks (grid, list).
- `hidden_users`: ObjectId[], → User - User ids whose posts are hidden from this user's feed.
- `isVerified`: Boolean, default false - Shows the verified badge.
- `saved_products`: ObjectId[], → Product - Product ids the user saved.
- `saved_programs`: ObjectId[], → Program - Program ids the user saved.
- `saved_blogposts`: ObjectId[], → Blogpots - BlogPost ids the user saved.
- `saved_services`: ObjectId[], → Service - Service ids the user saved.
- `last_used_miniapps`: ObjectId[], → MiniApp - MiniApp ids.
- `has_disabled_seen`: Boolean, default false - Read receipts are off.
- `other_address`: Object[] - Other saved addresses.
  - `firstname`: String
  - `lastname`: String
  - `email`: String
  - `phone_number`: String
  - `address`: String
  - `city`: String
  - `state`: String
  - `country`: String
  - `zip_code`: String
  - `_id`: ObjectId
- `is_disabled`: Boolean, default false - The account is disabled.
- `delete_date`: Date - When the account will be deleted.
- `trial_price_plans`: Object[] - Price plans the user is trying, and until when.
  - `end_date`: Date
  - `start_date`: Date
  - `price_plan`: ObjectId, → PricePlan
  - `_id`: ObjectId
- `lastActivity`: Date - When the user was last active.
- `attached_dynamic_models`: Object[] - Records of custom models attached to the user.
  - `model`: ObjectId, → DynamicModel
  - `document`: ObjectId
  - `_id`: ObjectId
- `_id`: ObjectId
- `updatedAt`: Date
- `createdAt`: Date
