Files
backend/docs/website-api/AI_PROMPT.md
T
Alireza HassaniandCursor ead16080b8 Add website favicon API and manual favicon profile updates.
Expose GET /tenants/{domain}/website/favicon for storefront tab icons, allow faviconMediaId on profile PATCH, and sync website API docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 00:51:11 +03:30

11 KiB
Raw Blame History

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):

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

  1. Resolve tenant first: GET /tenants/<WEBSITE_DOMAIN> → save businessId from id.
  2. All public content uses /tenants/<WEBSITE_DOMAIN>/... (no auth).
  3. Cart, orders, favorites use /businesses/<businessId>/... with Authorization: Bearer <accessToken>.
  4. Customer register body must include "domain": "<WEBSITE_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 <link rel="icon"> / 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.

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 <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} — 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
    • /sitemap-main.xml — static pages + categories + blogs + portfolios
    • /sitemap-products.xml — published products (when products 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.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.