# Meshkee Website API — AI / designer brief Copy everything below into a new AI chat when building a Meshkee storefront. --- ## System context (paste this) You are building a **Meshkee business website (storefront)**. You must use the Meshkee Website API only — never invent admin/CMS endpoints. **Canonical docs (always prefer these):** - Hub: https://api.meshkee.com/docs/website - OpenAPI: https://api.meshkee.com/docs/website/openapi.json - Postman: https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json **API base URL:** `https://api.meshkee.com/api/v1` (Optional alias if configured: `https://api./api/v1` — same backend.) **This website’s apex domain:** `` (example: `sanihome.ir` — no `www.`, no `api.`, no `customer.`, no `business.`) ### Hard rules 1. Resolve tenant first: `GET /tenants/` → save `businessId` from `id`. 2. All public content uses `/tenants//...` (no auth). 3. Cart, orders, favorites use `/businesses//...` with `Authorization: Bearer `. 4. Customer register body must include `"domain": ""`. If the cell already exists on another Meshkee site and the password differs, API returns `409` with `CELL_EXISTS_OTHER_SITE:...`. Retry register with `"acknowledgeExistingAccount": true` to link that account (profile unchanged; **password is replaced** with the new signup password), then complete SMS OTP. 5. Cell numbers are E.164 (`+98912...`). 6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.). 7. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md ### Typical bootstrap sequence 1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`) 2. `GET /tenants/{domain}/website/favicon` → `faviconUrl` for `` / Next.js `metadata.icons` (falls back to `logoUrl` when no dedicated favicon). Also returns `logoUrl`, `logoDarkUrl`, `hasDedicatedFavicon`. 3. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`) 4. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc); each portfolio may include nullable `projectUrl` (external website link). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`). 5. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens). 6. Cart checkout with `addressId` or inline `shippingAddress` + `payment` - For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl` - Response includes `payment.redirect` `{ method, url, fields }` — POST/redirect shopper to the bank - Meshkee registers the bank `callback_url` on the **store apex** (`https://YOUR_WEBSITE_DOMAIN/meshkee/payments/{gateway}/callback`), which nginx proxies to the API. ZarinPal/Mellat domain checks must match the store domain, not `api.meshkee.com`. - After verify, API redirects the browser to `returnUrl?status=success|failed&orderId=…` - Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods` ### Favicon + logos - `GET /tenants/{domain}/website/favicon` → `{ faviconUrl, logoUrl, logoDarkUrl, hasDedicatedFavicon }` - Use `faviconUrl` in layout metadata (Next.js: `icons: [{ url: faviconUrl, type: 'image/png' }]` when set). - When `hasDedicatedFavicon` is false, `faviconUrl` equals the light `logoUrl` — still safe to use as tab icon. - Header/footer logos: `logoUrl` on light backgrounds, `logoDarkUrl` on dark (fallback to `logoUrl` in CSS when null). ### User products (customer listings) Public marketplace listings owned by customers — not catalog `products`. - `GET /tenants/{domain}/user-products` — list published (`name`/`q`, `categoryId`, `cityId`, `countryId`, `condition`, `promoted`, pagination) - `GET /tenants/{domain}/user-products/{slug}` — details + gallery - `GET /tenants/{domain}/user-products/{slug}/technical-info` — category form + values Use product categories from `GET /tenants/{domain}/categories?entityType=product` for filters. Creating/editing listings is customer-dashboard only (`/businesses/.../my-user-products`), not website-facing. ### Analytics (dashboard charts) Page views are recorded **automatically** when the website calls the normal public list/detail APIs (blogs, products, portfolios, videos, store-items, and homepage sliders). No extra website code is required for those. Optional explicit record (e.g. custom home without sliders): - `POST /tenants/{domain}/analytics/views` body `{ "kind": "home"|"blog_detail"|"product_detail"|..., "entityId"?: "...", "path"?: "/blog/my-post" }` - `kind` values: `home`, `portfolio_list`, `portfolio_detail`, `product_list`, `product_detail`, `store_item_list`, `store_item_detail`, `blog_list`, `blog_detail`, `video_list`, `video_detail` (legacy aliases `website`/`product`/`portfolio`/`blog` still accepted) - Events kept ~6 months for charts; lifetime totals kept forever in counters. ### Static images Named slots the business dashboard can replace. Fetch once per page: - `GET /tenants/{domain}/website/static-images` — all slots - `GET /tenants/{domain}/website/static-images?pageKey=home` — slots for one page (`home`, `products`, `about`, …) - `GET /tenants/{domain}/website/static-images/{key}` — one slot (e.g. `home-hero`) Use `slot.key` in the placeholder. Match `pageKey` to the website page. For a single image: `images[0]?.url`. For a list: map `images` (if `itemCount` is set, that many images are expected). Each image may include `titleFa`, `titleEn`, `subtext`, and `linkUrl`. If `linkUrl` is set, wrap in ``. Pick `titleFa` or `titleEn` from the site locale. If `images` is empty, keep the local fallback. **Also publish a slot catalog on this website** so the business dashboard Refresh button can import keys from the main domain: `GET https:///meshkee/static-image-slots` ```json { "slots": [ { "key": "slider", "label": "Homepage slider", "kind": "list", "pageKey": "home", "aspectRatio": "16:9", "itemCount": null, "recommendedWidth": 1440 }, { "key": "home-side-banner", "label": "Side banner", "kind": "single", "pageKey": "home", "aspectRatio": "12:19", "itemCount": 1, "recommendedWidth": 480 } ] } ``` `kind` is `single` (one image) or `list` (duplicatable). For a fixed row, set `itemCount` (e.g. `2`). Leave `itemCount` null for an unbounded slider. `aspectRatio` must look like `16:9`. Do not invent CMS/upload APIs. ### Sitemap + robots.txt (SEO) Meshkee generates both files on the API. Nginx on the storefront proxies them — **do not** ship `public/sitemap.xml` or `public/robots.txt` (and do not add Next.js `app/sitemap.ts` / `app/robots.ts` that override these paths). | URL on this site | Served by | |------------------|-----------| | `https:///sitemap.xml` | Meshkee API (proxied) | | `https:///robots.txt` | Meshkee API (proxied) — includes `Sitemap: https:///sitemap.xml` | **What the website must publish** (for static pages only): `GET https:///meshkee/sitemap-config.json` Prefer generating this **at build time from app routes** (scan public static pages). Do not hand-maintain long lists in prompts. Minimal example: ```json { "baseUrl": "https://", "staticPages": [ { "path": "/", "changefreq": "daily", "priority": 1.0 }, { "path": "/about", "priority": 0.6 }, { "path": "/contact", "priority": 0.5 } ] } ``` - Include public static routes automatically; exclude login, checkout, cart, account, and admin paths. - **Canonical detail URLs (Meshkee default for all sites):** - Product: `/products/{id}/{nameFaSlug}` — build `nameFaSlug` from `nameFa` (fallback `title`); resolve page via `GET /tenants/{domain}/products/by-id/{id}` (slug segment is SEO-only; redirect to canonical if it drifts). - Product category: `/products/category/{categoryId}/{nameFaSlug}` — build slug from `nameFa` (fallback `name`); resolve via `GET /tenants/{domain}/categories/by-id/{id}`, then list products with `categoryId`. - Blog: `/blog/{id}/{titleSlug}` — `GET /tenants/{domain}/blogs/by-id/{id}` - Portfolio: `/portfolios/{id}/{titleFaSlug}` — `GET /tenants/{domain}/portfolios/by-id/{id}` - When linking from lists/cards, use the same `{id}/{slug}` shape for details (slugify Farsi title: spaces → `-`, keep Persian letters). Category links use `/products/category/{id}/{nameFaSlug}`. - Omit `templates` in `sitemap-config.json` unless this site uses non-default paths. - **Sitemap files (proxied by nginx — do not ship local copies):** - `/sitemap.xml` — sitemap **index** (lists child sitemaps for enabled modules) - `/sitemap-main.xml` — static pages + product categories - `/sitemap-products.xml` — published products (when products module is enabled) - `/sitemap-blogs.xml` — published blogs (when blog module is enabled) - `/sitemap-portfolios.xml` — published portfolios (when portfolio module is enabled) - `/sitemap-videos.xml` — published videos (when videos module is enabled) - `/sitemap-instructions.xml` — published instructions (when instructions module is enabled) - `/sitemap-workshops.xml` — published workshops (when workshops module is enabled) - `/robots.txt` — points at `/sitemap.xml` ### On-page SEO (every public page) These are **required on every crawlable page** (home, listing, product/blog/portfolio detail, about, contact, etc.). Auth/checkout may be noindex. **Document title (``)** - Unique per page; include the primary topic + business name when space allows. - Prefer CMS fields when present (`title`, `nameFa`/`nameEn`, product title, blog title). Never leave the Next.js default title. **Meta description** - Unique `<meta name="description" content="…">` on every public page (roughly 120–160 characters). - Prefer CMS summary/excerpt/description when available; otherwise write a short page-specific sentence. Never empty, never identical across all pages. **Headings** - Exactly **one** `<h1>` per page — the main topic (product name, blog title, page title). Do not hide it with CSS-only “fake” headings. - Use `<h2>` (then `<h3>`…) for real section structure under the H1. Do not skip levels for styling (don’t use H4 as a visual label without H2/H3). - Do not use headings for nav logos, button labels, or decorative text. **Images** - Every meaningful `<img>` / Next.js `Image` must have a non-empty **`alt`** describing the image (product name, slide title, banner purpose). - Prefer CMS title / `titleFa` / `titleEn` / media alt when available; for decorative icons use `alt=""` only when the image adds no information. - Never leave missing `alt` on content images (hero, product gallery, blog cover, static-image slots, sliders). **Open Graph (recommended)** - Set `og:title`, `og:description`, and `og:image` on important pages (home + detail pages) from CMS media when available. **Checklist before shipping a page** 1. Unique `<title>` and meta description 2. One clear H1 + sensible H2 sections 3. All content images have alt text 4. Public URL is included via CMS sitemap and/or `sitemap-config.json` static pages If OpenAPI and this brief conflict, **OpenAPI wins**. --- ## What to tell each website team Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL.