Move blogs, portfolios, videos, instructions, and workshops out of sitemap-main into dedicated files like products, and allow saving migrated Farsi category color labels without wiping store-item links. Co-authored-by: Cursor <cursoragent@cursor.com>
12 KiB
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.<WEBSITE_DOMAIN>/api/v1 — same backend.)
This website’s apex domain: <WEBSITE_DOMAIN>
(example: sanihome.ir — no www., no api., no customer., no business.)
Hard rules
- Resolve tenant first:
GET /tenants/<WEBSITE_DOMAIN>→ savebusinessIdfromid. - All public content uses
/tenants/<WEBSITE_DOMAIN>/...(no auth). - Cart, orders, favorites use
/businesses/<businessId>/...withAuthorization: Bearer <accessToken>. - Customer register body must include
"domain": "<WEBSITE_DOMAIN>". If the cell already exists on another Meshkee site and the password differs, API returns409withCELL_EXISTS_OTHER_SITE:.... Retry register with"acknowledgeExistingAccount": trueto link that account (profile unchanged; password is replaced with the new signup password), then complete SMS OTP. - Cell numbers are E.164 (
+98912...). - Do not call dashboard/CMS routes (
/businesses/.../productswrite APIs, media upload, domain-admin, etc.). - Partner SMS (
POST /public/sms/send) is for external partner backends with an issuedX-Api-Keyonly — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
Typical bootstrap sequence
GET /tenants/{domain}→ branding +businessId+specialProductsSource(productorstore_item)GET /tenants/{domain}/website/favicon→faviconUrlfor<link rel="icon">/ Next.jsmetadata.icons(falls back tologoUrlwhen no dedicated favicon). Also returnslogoUrl,logoDarkUrl,hasDedicatedFavicon.- Homepage: business-info, static-images, sliders, category-groups, brand-groups, store-specials (
sourcerepeats the tenant setting; items are store listings whensourceisstore_item) - Catalog: categories, products (
GET /products/{slug}includesrelatedProducts: same category then same brand, in-stock first), store-items (nameinstant search: in-stock first, thenupdatedAt), user-products (customer stock listings). Portfolios list newest first (sortOrderdesc, thenpublishedAt/createdAtdesc); each portfolio may include nullableprojectUrl(external website link). List filters: products/blogs/portfolios/videos accept?tag=(exact match onmetadata.tags). - Auth: register/login → store tokens. Optional:
POST /auth/send-otpthenPOST /auth/login-otp(passwordless) orPOST /auth/reset-password(forgot password).POST /auth/verify-otponly marks the cell verified (no tokens). - Cart checkout with
addressIdor inlineshippingAddress+payment- For online pay:
payment.type = "e_payment_gate",gatewayType(e.g."mellat"or"zarinpal"), and absolutereturnUrl - Response includes
payment.redirect{ method, url, fields }— POST/redirect shopper to the bank - Meshkee registers the bank
callback_urlon 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, notapi.meshkee.com. - After verify, API redirects the browser to
returnUrl?status=success|failed&orderId=… - Enabled gateways:
GET /tenants/{domain}→ePayment, orGET /businesses/{businessId}/payments/methods
- For online pay:
Favicon + logos
GET /tenants/{domain}/website/favicon→{ faviconUrl, logoUrl, logoDarkUrl, hasDedicatedFavicon }- Use
faviconUrlin layout metadata (Next.js:icons: [{ url: faviconUrl, type: 'image/png' }]when set). - When
hasDedicatedFaviconis false,faviconUrlequals the lightlogoUrl— still safe to use as tab icon. - Header/footer logos:
logoUrlon light backgrounds,logoDarkUrlon dark (fallback tologoUrlin 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 + galleryGET /tenants/{domain}/user-products/{slug}/technical-info— category form + values Use product categories fromGET /tenants/{domain}/categories?entityType=productfor 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/viewsbody{ "kind": "home"|"blog_detail"|"product_detail"|..., "entityId"?: "...", "path"?: "/blog/my-post" }kindvalues:home,portfolio_list,portfolio_detail,product_list,product_detail,store_item_list,store_item_detail,blog_list,blog_detail,video_list,video_detail(legacy aliaseswebsite/product/portfolio/blogstill 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 slotsGET /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 <a href={linkUrl}>. 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://<WEBSITE_DOMAIN>/meshkee/static-image-slots
{
"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://<WEBSITE_DOMAIN>/sitemap.xml |
Meshkee API (proxied) |
https://<WEBSITE_DOMAIN>/robots.txt |
Meshkee API (proxied) — includes Sitemap: https://<WEBSITE_DOMAIN>/sitemap.xml |
What the website must publish (for static pages only):
GET https://<WEBSITE_DOMAIN>/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:
{
"baseUrl": "https://<WEBSITE_DOMAIN>",
"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}— buildnameFaSlugfromnameFa(fallbacktitle); resolve page viaGET /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 fromnameFa(fallbackname); resolve viaGET /tenants/{domain}/categories/by-id/{id}, then list products withcategoryId. - Blog:
/blog/{id}/{titleSlug}—GET /tenants/{domain}/blogs/by-id/{id} - Portfolio:
/portfolios/{id}/{titleFaSlug}—GET /tenants/{domain}/portfolios/by-id/{id}
- Product:
- 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
templatesinsitemap-config.jsonunless 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 (<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.jsImagemust have a non-emptyaltdescribing the image (product name, slide title, banner purpose). - Prefer CMS title /
titleFa/titleEn/ media alt when available; for decorative icons usealt=""only when the image adds no information. - Never leave missing
alton content images (hero, product gallery, blog cover, static-image slots, sliders).
Open Graph (recommended)
- Set
og:title,og:description, andog:imageon important pages (home + detail pages) from CMS media when available.
Checklist before shipping a page
- Unique
<title>and meta description - One clear H1 + sensible H2 sections
- All content images have alt text
- Public URL is included via CMS sitemap and/or
sitemap-config.jsonstatic 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.