# LaysanX Frontend Delivery API

This document describes the headless/frontend delivery API for LaysanX.

Use this API when:
- content is managed in LaysanX
- the website frontend is hosted on another server or platform
- the frontend needs to read site content and submit forms back into LaysanX

Base path:

```text
/api/v1
```

Example base URL:

```text
https://cms.yourdomain.com/api/v1
```

## Authentication

All delivery requests require a public key:

```http
X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Origin: https://www.yourfrontenddomain.com
```

Private sync endpoints additionally require a secret key:

```http
X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Notes:
- API access must be enabled for the client account
- `AllowedDomains` is enforced for browser-based and Postman/API-client usage
- `OPTIONS` preflight requests are allowed for browser frontends
- secret keys are stored securely and only shown once when generated
- `Origin` or `Referer` should match a domain allowed in Client Admin > API Access

### Public Delivery vs Private Sync

Public delivery mode:
- `GET /api/v1/*`
- `POST /api/v1/forms/{id}/submissions`
- requires `X-Public-Key`
- allowed-domain checks still apply
- `X-Secret-Key` is optional

Private sync mode:
- `GET /api/v1/form-submissions`
- `GET /api/v1/forms/{id}/submissions`
- `GET /api/v1/form-submissions/{id}`
- requires both `X-Public-Key` and `X-Secret-Key`

### Sample Authenticated Request

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/site/config" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Origin: https://www.yourfrontenddomain.com"
```

## CORS

The API supports browser-based standalone frontends.

If a client frontend is hosted on another domain:
- the browser may send an `OPTIONS` preflight request
- the API allows this
- the actual request must still pass API credential validation and allowed-domain checks

## Media URLs

Media/file fields returned by the API are normalized to absolute URLs when they contain upload paths such as:

- `/uploads/...`
- `uploads/...`
- `/saas-uploads/...`

This makes standalone frontends easier to build because they do not need to prepend the CMS domain manually.

## Runtime-rendered Custom Sections

LaysanX also supports reusable custom sections in the client panel through:

- `Client Admin > Custom Sections`
- `Client Admin > Section Fields`
- `Client Admin > Section Items`

These are currently rendered by the packaged runtime/themes through custom HTML tokens such as:

```text
[[dynamic-section slug="your-route-slug" limit="6" style="service-card"]]
```

Notes:
- there is currently **no dedicated public delivery endpoint** for custom sections
- runtime rendering resolves the saved section, fields, items, and optional item detail pages
- if an item has a slug, the website can expose `/{section-route-slug}/{item-slug}`
- section fields support `Fileupload`, so rendered values may include stored media/file URLs

## Storefront Runtime Boundaries

The storefront ecommerce flow is currently handled by the hosted client runtime rather than the public `/api/v1` delivery API. Frontend and QA teams should validate these routes on the live client website:

- `/cart`
- `/checkout`
- `/order-success?orderId={id}`
- `/orders`
- `/invoice/{orderNumber}` or the invoice link exposed from order pages
- `/returns`
- `/currency`
- `/customer/wishlist`

Current storefront behavior:

- Cart checkout creates one main order and one linked sub-order per product/vendor-style line grouping.
- Each sub-order can have its own invoice, payment status, fulfillment status, and order status in Client Admin.
- Cart is cleared after a successful order.
- The order-success page confirms the placed order and links to invoice/order tracking.
- Pricing-plan CTAs bypass the cart page and link directly to checkout with `/checkout?planId={id}` or the tenant-prefixed storefront route.
- Checkout creates pricing-plan cart lines internally with no product variant (`VariantId = null`), so plans do not use fake variant ids.
- Currency selection is stored in a storefront cookie, updates active cart currency, and displays symbol-first prices across product and pricing-plan surfaces.
- Payment gateways receive the active selected currency where the gateway supports that currency.
- Product variant images are included in the product gallery and the selected variant image changes the main gallery image.
- Wishlist actions are handled by the customer runtime and return to the current product/listing page.

These are runtime pages, so they are not included as `/api/v1` Postman delivery endpoints. The Postman collection includes documentation and widget examples that exercise the public parts of this flow where applicable.

## Common Response Behavior

Successful responses:

- `200 OK`

Missing resource:

- `404 Not Found`

Missing/invalid credentials:

- `401 Unauthorized`

Blocked domain / disabled API:

- `403 Forbidden`

Example error response:

```json
{
  "error": "Invalid API credentials."
}
```

Another example:

```json
{
  "error": "Request origin is not allowed."
}
```

## Cache Behavior After Publish

The client website and SaaS website send revalidation/no-cache headers for HTML and static assets. After publishing new code and restarting both apps, browsers and proxy layers should revalidate pages and assets instead of keeping stale CSS, JavaScript, or section-layout output.

Recommended production restart after publishing:

```bash
sudo systemctl restart laysanx-client
sudo systemctl restart laysanx-saas
```

## Core Endpoints

### Site Config

Returns the shared site configuration needed by most frontends.

```http
GET /api/v1/site/config
```

Includes:
- current theme selection
- general settings
- social settings
- topbar settings
- footer settings
- home page settings
- section controls
- page SEO settings
- menus with nested menu items

This shared config can also include home-page custom HTML placement data, scoped section layout controls, and built-in contact-page settings such as assigned contact form and configurable headings/subheadings.

Example request:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/site/config" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Example response:

```json
{
  "clientId": 1001,
  "theme": {
    "themeId": 1,
    "themeName": "TheLiaison",
    "folderName": "TheLiaison"
  },
  "settings": {
    "siteName": "Laysan Technologies",
    "siteLogo": "https://cms.yourdomain.com/uploads/logo.png",
    "siteFavicon": "https://cms.yourdomain.com/uploads/favicon.ico",
    "primaryColor": "#18b6c8"
  },
  "social": {
    "facebook": "https://facebook.com/example",
    "linkedin": "https://linkedin.com/company/example"
  },
  "topbar": {
    "status": true,
    "backgroundColor": "#0f172a"
  },
  "footer": {
    "status": true,
    "sectionCount": 4
  },
  "menus": [
    {
      "id": 1,
      "menuTitle": "Header Menu",
      "position": "Header",
      "status": true,
      "items": [
        {
          "id": 11,
          "title": "Home",
          "url": "/",
          "icon": null,
          "sortOrder": 1,
          "children": []
        }
      ]
    }
  ]
}
```

### Site Routes

Returns canonical routes plus generated content routes for static generation, prerendering, and crawl planning.

```http
GET /api/v1/site/routes
```

Includes:
- canonical routes
- service routes
- product routes
- project routes
- blog routes
- news routes
- event routes
- career routes
- notification routes
- dynamic CMS pages

Custom section detail pages can also exist on the live website through `/{section-route-slug}/{item-slug}` when a custom section item has its own slug, but those routes are runtime-driven today rather than published from a dedicated custom-section endpoint family.

### Site Engagement

Returns reusable engagement payloads for standalone frontends.

```http
GET /api/v1/site/engagement
```

Includes:
- welcome popup configuration
- assigned popup form definition
- chat widget delivery config

### Home Page Payload

```http
GET /api/v1/site/home
```

Includes:
- sliders
- services
- products
- projects
- teams
- blogs
- galleries
- faqs
- marketing sections
- auxiliary sections
- home page settings
- section controls

The home settings payload can include reusable custom HTML sections that finalized themes place after the selected home/module position. Home section layout controls are scoped separately from dynamic page layout controls, so home can use its own column, row, and count values.

Example request:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/site/home" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Example response:

```json
{
  "clientId": 1001,
  "homePage": {
    "sliderSectionActive": true,
    "introductionContent": "<p>Welcome to our company.</p>",
    "introductionImage": "https://cms.yourdomain.com/uploads/home-builder/intro.png"
  },
  "sliders": [
    {
      "id": 1,
      "title": "Digital Growth",
      "subTitle": "Build faster",
      "image": "https://cms.yourdomain.com/uploads/sliders/slide-1.jpg",
      "status": true
    }
  ],
  "services": [
    {
      "id": 2,
      "title": "Software Development",
      "slug": "software-development",
      "image": "https://cms.yourdomain.com/uploads/services/service-1.jpg"
    }
  ]
}
```

## Content Endpoints

### Pages

```http
GET /api/v1/pages
GET /api/v1/pages/{slug}
GET /api/v1/pages/{slug}/view
```

Example:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/pages/about-us" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Sample page response:

```json
{
  "id": 7,
  "clientId": 1001,
  "title": "About Us",
  "subTitle": "Who we are",
  "description": "<p>Dynamic page body content</p>",
  "slug": "about-us",
  "status": true,
  "formTitle": "Request Consultation",
  "pricingPlanTitle": "Business Plan",
  "serviceSectionActive": true,
  "productSectionActive": false,
  "projectSectionActive": true,
  "introductionSectionActive": true,
  "featuresSectionActive": true,
  "whyChooseUsSectionActive": true,
  "clientsSectionActive": true,
  "teamsSectionActive": false,
  "faqSectionActive": true,
  "gallerySectionActive": false,
  "blogSectionActive": true,
  "contactFormSectionActive": true,
  "pricingPlanSectionActive": false,
  "sectionSortOrder": "Introduction,Services,Projects,ContactForm",
  "marketingSectionTypes": "Hero Stat,Feature,CTA",
  "auxiliarySectionTypes": "Partner,Testimonial",
  "customHtmlSectionsJson": "[{\"position\":\"Services\",\"htmlContent\":\"<section class=\\\"promo-strip\\\"><div class=\\\"container\\\"><h3>Special Campaign</h3></div></section>\",\"sortOrder\":0}]",
  "customHtmlSections": [
    {
      "position": "Services",
      "htmlContent": "<section class=\"promo-strip\"><div class=\"container\"><h3>Special Campaign</h3></div></section>",
      "sortOrder": 0
    }
  ],
  "routePath": "/about-us"
}
```

`customHtmlSections` lets the frontend render multiple custom HTML blocks immediately after the selected page/module position. Valid positions follow the same dynamic page ordering slots such as `Introduction`, `Features`, `Why Choose Us`, `Services`, `Products`, `Projects`, `Clients`, `Teams`, `FAQs`, `Gallery`, `Blogs`, `Contact Form`, `Pricing Plans`, `Hero Stat`, `Feature`, `Benefit`, `Counter`, `Highlight`, `CTA`, `Testimonial`, `Partner`, `Client Review`, `Trust Badge`, `General`, `News`, `Events`, `Careers`, and `Notifications`.

The finalized runtime support for these placements is available in the shipped page runtimes for `TheLiaison`, `DustFree`, `BlueChipCorp`, `PressMaster`, `CrispCare`, and `CustomRuntime`. The API returns both the raw `customHtmlSectionsJson` string and the parsed `customHtmlSections` array so frontend teams can either trust the server shape directly or run their own transform.

The same custom HTML strategy is also used by the home page builder, where admin-defined HTML blocks can render after selected home sections and can call custom section tokens like `[[dynamic-section ...]]`.

Dynamic pages also support page-scoped section layout controls. Finalized themes can apply the saved sort order, column count, row count, and item count independently per page. These values are intentionally separate from home-page layout controls, so changing a dynamic page does not change the home page.

For migration work, client websites can also use page redirects configured in Client Admin. Old slugs can redirect to new slugs or full URLs with 301/302 behavior before normal page routing runs.

### Services

```http
GET /api/v1/services
GET /api/v1/services/{slug}
GET /api/v1/services/{slug}/view
```

Examples:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/services" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

curl --request GET "https://cms.yourdomain.com/api/v1/services/software-development" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Sample list response:

```json
[
  {
    "id": 2,
    "title": "Software Development",
    "slug": "software-development",
    "image": "https://cms.yourdomain.com/uploads/services/service-1.jpg",
    "status": true
  }
]
```

### Products

```http
GET /api/v1/products
GET /api/v1/products/{slug}
GET /api/v1/products/{slug}/view
```

Product delivery payloads expose the product content saved in Client Admin. The hosted storefront runtime adds ecommerce behavior on top of that payload, including:

- compact add/edit product admin workflow
- variant matrix with SKU, price, selling price, stock, low-stock threshold, weight, dimensions, image, and active/backorder/inventory flags
- product gallery images plus variant images in the detail slider
- selected-variant gallery switching
- wishlist actions and customer account integration

For external headless frontends that need full cart/checkout behavior, use the hosted storefront routes above or build against the runtime contract rather than assuming cart APIs exist under `/api/v1`.

### Projects

```http
GET /api/v1/projects
GET /api/v1/projects/{slug}
GET /api/v1/projects/{slug}/view
```

### Blogs

```http
GET /api/v1/blogs
GET /api/v1/blogs/{slug}
GET /api/v1/blogs/{slug}/view
```

### Jobs

```http
GET /api/v1/jobs
GET /api/v1/jobs/{slug}
GET /api/v1/jobs/{slug}/view
```

### News

```http
GET /api/v1/news
GET /api/v1/news/{id}
GET /api/v1/news/{id}/view
```

### Events

```http
GET /api/v1/events
GET /api/v1/events/{id}
GET /api/v1/events/{id}/view
```

### Notifications

```http
GET /api/v1/notifications
GET /api/v1/notifications/{id}
GET /api/v1/notifications/{id}/view
```

### Gallery

```http
GET /api/v1/gallery
```

### Sliders

```http
GET /api/v1/sliders
```

### FAQs

```http
GET /api/v1/faqs
```

### Teams

```http
GET /api/v1/teams
```

### Pricing Plans

```http
GET /api/v1/pricing-plans
GET /api/v1/pricing-plans/{id}
```

Responses include `checkoutUrl`, `directCheckoutUrl`, `guestCheckoutUrl`, `cartLineType: "pricing_plan"`, and null variant identifiers so pricing-plan buttons can redirect directly to checkout instead of the cart page.

Uploaded themes should use the rendered `plan.checkout_url`:

```html
<a class="btn btn-primary" href="{{ plan.checkout_url | default: plan.checkoutUrl | default: plan.direct_checkout_url | default: plan.directCheckoutUrl }}">
  {{ plan.cta_text | default: 'Buy Now' }}
</a>
```

### Commerce Runtime APIs

```http
GET /api/v1/commerce/currencies
GET /api/v1/products/{slug}/variants
```

`GET /api/v1/commerce/currencies` returns active currencies with name, symbol, conversion rate, and default flag. Storefront themes should display the selected currency symbol before product and pricing-plan amounts. Checkout/payment integrations should send the selected `currencyCode` and converted grand total to the gateway.

`GET /api/v1/products/{slug}/variants` returns active product variants with SKU, price, selling price, stock, image, and parsed attributes for product-detail selectors and variant image galleries.

### Private Commerce Operations

These endpoints require both `X-Public-Key` and `X-Secret-Key`.

```http
GET /api/v1/orders/{orderNumber}?email={customerEmail}
GET /api/v1/orders/{orderNumber}/invoice?email={customerEmail}
PATCH /api/v1/orders/{orderId}/status
GET /api/v1/returns?status=requested
GET /api/v1/returns/{requestNumber}
PATCH /api/v1/returns/{id}/status
GET /api/v1/support-tickets/{ticketNumber}?includeMessages=true
```

Order lookups return the main order, linked sub-orders, line items, independent payment/order/fulfillment status, and invoice summary. Use `PATCH /api/v1/orders/{orderId}/status` with a sub-order id when only one connected item order should change.

Return status updates support approve/reject, `resolutionNotes`, `refundAmount`, and `refundStatus` such as `pending`, `paid`, or `not_applicable`.

### Forms

```http
GET /api/v1/forms
GET /api/v1/forms/{id}
```

`GET /api/v1/forms/{id}` returns:
- form metadata
- active form fields

Example:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/forms/1" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Example response:

```json
{
  "id": 1,
  "clientId": 1001,
  "title": "Contact Form",
  "status": true,
  "fields": [
    {
      "id": 10,
      "formId": 1,
      "fieldName": "Your name",
      "fieldType": "Text",
      "predefinedData": null,
      "status": true
    },
    {
      "id": 11,
      "formId": 1,
      "fieldName": "Your Email",
      "fieldType": "Email",
      "predefinedData": null,
      "status": true
    }
  ]
}
```

### Marketing Sections

```http
GET /api/v1/marketing-sections
```

### Auxiliary Sections

```http
GET /api/v1/auxiliary-sections
```

## Form Submission APIs

### Submit Form Data from External Frontends

```http
POST /api/v1/forms/{id}/submissions
Content-Type: application/json
```

This endpoint supports both:
- `application/json`
- `multipart/form-data`

Example body:

```json
{
  "pageName": "/contact",
  "country": "India",
  "state": "Haryana",
  "city": "Gurugram",
  "pincode": "122001",
  "formData": {
    "Your name": "John Doe",
    "Your Mobile Number": "9999999999",
    "Your Email": "john@example.com",
    "Service Type": "Dry Clean"
  }
}
```

Example `curl`:

```bash
curl --request POST "https://cms.yourdomain.com/api/v1/forms/1/submissions" \
  --header "Content-Type: application/json" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --data '{
    "pageName": "/contact",
    "country": "India",
    "state": "Haryana",
    "city": "Gurugram",
    "pincode": "122001",
    "formData": {
      "Your name": "John Doe",
      "Your Mobile Number": "9999999999",
      "Your Email": "john@example.com",
      "Service Type": "Dry Clean"
    }
  }'
```

Behavior:
- validates that the form exists and is active for the authenticated client
- accepts only fields that belong to the form
- stores the lead in `FormSubmissions`
- triggers client email notification if configured
- supports browser-safe public delivery mode with allowed-domain checks

Example success response:

```json
{
  "success": true,
  "message": "Form submitted successfully.",
  "submission": {
    "id": 123,
    "formId": 2,
    "title": "Contact Form",
    "pageName": "/contact",
    "create_Date": "2026-05-04T17:42:13.0000000+05:30"
  }
}
```

Example validation error:

```json
{
  "error": "One or more submitted fields are not part of this form.",
  "fields": [
    "Unknown Field"
  ]
}
```

Important notes:
- this endpoint supports both JSON and multipart form submissions
- file upload fields should use `multipart/form-data`
- files can be posted using either the form field name or the generated field key
- uploaded files are validated by the shared upload policy and plan-aware file-size limits where enabled
- unknown field names return `400 Bad Request`
- reCAPTCHA is not currently applied to API submissions

### Submit Form Data with File Upload

Use `multipart/form-data` when the form contains `FileUpload` fields.

Example `curl`:

```bash
curl --request POST "https://cms.yourdomain.com/api/v1/forms/2/submissions" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --form "pageName=/career/software-developer" \
  --form "country=India" \
  --form "state=Haryana" \
  --form "city=Gurugram" \
  --form "pincode=122001" \
  --form "Your name=John Doe" \
  --form "Your Email=john@example.com" \
  --form "Resume=@/Users/example/Documents/resume.pdf"
```

If your frontend uses the generated field key instead of the human field name, that is also accepted. Example:

```text
field_11_Resume
```

### Read Form Submissions

```http
GET /api/v1/form-submissions
GET /api/v1/forms/{formId}/submissions
GET /api/v1/form-submissions/{id}
```

Supported filters on list endpoints:

- `formId`
- `from`
- `to`
- `page`
- `pageSize`

Example:

```http
GET /api/v1/form-submissions?formId=2&from=2026-05-01&to=2026-05-31&page=1&pageSize=50
GET /api/v1/forms/2/submissions?from=2026-05-01&to=2026-05-31&page=1&pageSize=50
```

Example `curl`:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/form-submissions?formId=2&from=2026-05-01&to=2026-05-31&page=1&pageSize=50" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

ERP-style form-specific endpoint:

```bash
curl --request GET "https://cms.yourdomain.com/api/v1/forms/2/submissions?from=2026-05-01&to=2026-05-31&page=1&pageSize=50" \
  --header "X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Origin: https://erp.yourdomain.com"
```

The request domain must be saved in the client's API allowed domain list. If the browser origin or referer is not allowed, the API returns:

```json
{"error":"Domain is not allowed."}
```

Response includes:
- `Filters`
- `Pagination`
- `Items`

Each item contains:
- submission id
- form id
- form title
- page name
- IP
- country
- state
- city
- pincode
- create date
- parsed `FormData`
- raw `RawFormData`

Example response:

```json
{
  "filters": {
    "formId": 2,
    "from": "2026-05-01T00:00:00",
    "to": "2026-05-31T00:00:00",
    "page": 1,
    "pageSize": 50
  },
  "pagination": {
    "totalCount": 2,
    "page": 1,
    "pageSize": 50,
    "totalPages": 1
  },
  "items": [
    {
      "id": 201,
      "clientId": 1001,
      "formId": 2,
      "formTitle": "Job Apply Form",
      "pageName": "/career/software-developer",
      "ip": "203.0.113.10",
      "country": "India",
      "state": "Haryana",
      "city": "Gurugram",
      "pincode": "122001",
      "create_Date": "2026-05-04T17:42:13.0000000+05:30",
      "formData": {
        "Your name": "John Doe",
        "Your Email": "john@example.com"
      },
      "rawFormData": "{\"Your name\":\"John Doe\",\"Your Email\":\"john@example.com\"}"
    }
  ]
}
```

## Chat Widget APIs

These runtime endpoints are separate from `/api/v1` and are used by the embedded knowledge-base widget.

Base path:

```text
/widget
```

### Bootstrap the Widget

```http
GET /widget/bootstrap?clientId=1001&widgetKey=widget_xxx&domain=www.example.com
```

Required query parameters:
- `clientId`
- `widgetKey`
- `domain`

Recommended header:

```http
Origin: https://www.example.com
```

Returns:
- widget title
- agent name
- greeting message
- input placeholder
- primary color
- lead capture settings
- voice capability flags

Example:

```bash
curl --request GET "https://cms.yourdomain.com/widget/bootstrap?clientId=1001&widgetKey=widget_xxx&domain=www.example.com" \
  --header "Origin: https://www.example.com"
```

### Ask a Widget Question

```http
POST /widget/ask
Content-Type: application/json
```

Example body:

```json
{
  "clientId": 1001,
  "widgetKey": "widget_xxx",
  "message": "What services do you offer?",
  "sessionId": "session-demo-001",
  "visitorId": "visitor-demo-001",
  "domain": "www.example.com"
}
```

Returns:
- `sessionId`
- `answer`
- `usedAi`
- `leadCapture`
- `sources`
- `branding`

### Submit a Widget Lead

```http
POST /widget/lead
Content-Type: application/json
```

Example body:

```json
{
  "clientId": 1001,
  "widgetKey": "widget_xxx",
  "name": "John Doe",
  "email": "john@example.com",
  "mobile": "9999999999",
  "originalQuestion": "I need pricing details",
  "sessionId": "session-demo-001",
  "domain": "www.example.com"
}
```

Notes:
- widget endpoints validate allowed domains
- `/widget/lead` requires a widget with an assigned form
- `/widget/ask` uses available AI allowance and can return token/plan-limit errors
- normal greetings and knowledge-base questions stay conversational and should not be forced into order/ticket lookup
- order lookup is triggered only when the message contains an order-status intent or a clear order number such as `ORD-1001-20260522224715159`
- support ticket lookup is triggered only when the message contains a ticket/case/issue status intent or a ticket number such as `LX-TKT-20260516-00002`

Example order-status message:

```json
{
  "clientId": 1001,
  "widgetKey": "widget_xxx",
  "message": "Check order status ORD-1001-20260522224715159",
  "sessionId": "session-demo-001",
  "domain": "www.example.com"
}
```

Example support-ticket message:

```json
{
  "clientId": 1001,
  "widgetKey": "widget_xxx",
  "message": "Share ticket status LX-TKT-20260516-00002",
  "sessionId": "session-demo-001",
  "domain": "www.example.com"
}
```

## Standalone Runtime Relay

Some standalone theme builds expose a relay endpoint that forwards form submissions into the main delivery API.

```http
POST /api/forms/submit
Content-Type: multipart/form-data
```

This endpoint lives on the standalone runtime host, not the CMS host.

Example:

```bash
curl --request POST "https://standalone.yourdomain.com/api/forms/submit" \
  --form "formId=12" \
  --form "pageName=/contact" \
  --form "Your name=John Doe" \
  --form "Your Email=john@example.com" \
  --form "Your Mobile Number=9999999999"
```

Behavior:
- accepts normal form field names
- forwards the submission to `POST /api/v1/forms/{id}/submissions`
- keeps the CMS as the system of record for saved submissions

## Example JavaScript Fetch

```js
const apiBase = "https://cms.yourdomain.com/api/v1";
const headers = {
  "X-Public-Key": "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "Origin": "https://www.yourfrontenddomain.com"
};

const config = await fetch(`${apiBase}/site/config`, { headers }).then(r => r.json());
const routes = await fetch(`${apiBase}/site/routes`, { headers }).then(r => r.json());
const engagement = await fetch(`${apiBase}/site/engagement`, { headers }).then(r => r.json());
const home = await fetch(`${apiBase}/site/home`, { headers }).then(r => r.json());
```

Example form submit:

```js
await fetch(`${apiBase}/forms/1/submissions`, {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    pageName: "/contact",
    formData: {
      "Your name": "John Doe",
      "Your Email": "john@example.com"
    }
  })
});
```

## Example Postman Setup

Use these common headers in your collection:

```text
X-Public-Key: {{public_key}}
Origin: {{origin}}
Content-Type: application/json
```

For private sync endpoints, also include:

```text
X-Secret-Key: {{secret_key}}
```

Suggested Postman environment variables:

```text
api_base_url = https://cms.yourdomain.com/api/v1
public_key   = pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
secret_key   = sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
origin       = https://www.yourfrontenddomain.com
```

## Implementation Notes

Current API strengths:
- versioned under `/api/v1`
- browser-friendly CORS support
- public delivery + private sync auth modes
- absolute media URLs
- content delivery + lead submission support
- standalone-theme friendly site config, route, and engagement payloads

Current production feature coverage:
- API allowed-domain checks apply to public delivery, private sync, and ERP lead readers; blocked origins receive `Domain is not allowed.`
- `/api/v1/forms/{formId}/submissions` supports form-specific ERP/CRM lead sync with pagination and date filters
- dynamic page payloads include selected module visibility, sorted sections, custom HTML placements, service/product/project display limits, and assigned marketing/auxiliary sections
- finalized themes can render selected services, products, projects, pricing plans, contact forms, custom HTML, logo sliders, testimonials/client reviews, partners, and trust badges
- media and file uploads should follow the client's plan limits and shared file validation rules
- client-side captcha settings must include the hosted route domain and the client's custom domain when a real domain goes live
- client billing, wallet, AI image generation, chat widget usage, and blog automation are plan/token aware but remain admin-panel features rather than public delivery endpoints

Current limitations to be aware of:
- some endpoints use `{slug}` while others use `{id}`
- secret keys should be regenerated for older clients that still use legacy plain-text stored values

## Recommended Integration Order

For a new standalone frontend, build in this order:

1. `GET /api/v1/site/config`
2. `GET /api/v1/site/routes`
3. `GET /api/v1/site/engagement`
4. `GET /api/v1/site/home`
5. listing/detail content endpoints
6. `GET /api/v1/forms/{id}`
7. `POST /api/v1/forms/{id}/submissions`
8. `GET /api/v1/forms/{id}/submissions` or `GET /api/v1/form-submissions` for ERP/CRM sync
