{"openapi":"3.1.0","info":{"title":"Product management public API","version":"1.0.0","description":"Anonymous catalog and customer APIs. Successes use { data }; errors use { error: { code, message, fields? } }. Business API responses are no-store. Register, then log in; native apps explicitly request token mode. Browser HttpOnly session cookies are sent automatically; only optional bearer tokens need the Authorize dialog. Refresh account-specific price caches after login/logout or account changes. Administrative APIs are excluded."},"servers":[{"url":"/","description":"Current host"}],"tags":[{"name":"Authentication","description":"Canonical shared login, session and logout."},{"name":"Customers","description":"Registration, customer profile and private attachments."},{"name":"Products","description":"Published catalog and current customer tier pricing."},{"name":"Categories","description":"Anonymous category listing."},{"name":"Settings","description":"Public identity/contact settings and registration options."},{"name":"App Content","description":"Published content lookup by application key."}],"paths":{"/api/auth/login":{"post":{"tags":["Authentication"],"operationId":"login","summary":"Sign in using email or phone","description":"Shared customer/staff login. Cookie mode (default) requires Origin matching BETTER_AUTH_URL and delivers an HttpOnly cookie without a JSON token. Token mode permits native requests without Origin and returns data.token; cookie delivery is also attempted. Any supplied Origin must be trusted. Identifier/password verification is limited to 10 attempts per minute per Better Auth limiter key, in one process. Invalid credentials or inactive accounts return 401. Reload account-specific catalog caches after login.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginInput"},"example":{"identifier":"01712345678","password":"example-password","responseMode":"token"}}}},"responses":{"200":{"description":"Safe identity and expiry, with token only when requested; Set-Cookie delivers the session cookie.","headers":{"Set-Cookie":{"schema":{"type":"string"},"description":"HttpOnly auth cookie; never read it from browser JavaScript."}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/LoginResult"}}},"examples":{"cookie":{"summary":"Default cookie mode","value":{"data":{"user":{"id":"customer-id","name":"Shop owner","email":null,"phoneNumber":"+8801712345678","image":null,"accountType":"CUSTOMER","customerType":"RETAIL","permissions":[]},"expiresAt":"2026-10-09T12:00:00.000Z"}}},"token":{"summary":"Explicit token mode","value":{"data":{"user":{"id":"customer-id","name":"Shop owner","email":null,"phoneNumber":"+8801712345678","image":null,"accountType":"CUSTOMER","customerType":"RETAIL","permissions":[]},"expiresAt":"2026-10-09T12:00:00.000Z","token":"example-session-token"}}}}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"429":{"description":"RATE_LIMITED: Too many attempts. Try again later.","headers":{"Retry-After":{"description":"Retry delay, when supplied by the authentication provider.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Too many attempts. Try again later."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/auth/session":{"get":{"tags":["Authentication"],"operationId":"getSession","summary":"Read current identity and session expiry","description":"Checks live session/account state. Explicit Authorization overrides cookies, even if invalid. Returns safe identity and current permissions, never a session token or customer document metadata; use customer/me for business details.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthSession"}}},"example":{"data":{"user":{"id":"customer-id","name":"Shop owner","email":null,"phoneNumber":"+8801712345678","image":null,"accountType":"CUSTOMER","customerType":"RETAIL","permissions":[]},"expiresAt":"2026-10-09T12:00:00.000Z"}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"operationId":"logout","summary":"Revoke the selected session and clear cookies","description":"No request body. Cookie logout requires trusted Origin. Bearer requests may omit Origin; any supplied Origin must be trusted. Explicit Authorization takes precedence. Repeated logout and expired/revoked/inactive sessions still succeed and clear cookies. Database/provider failures remain errors. Discard stored credentials and account-specific price caches after success.","security":[{},{"bearerAuth":[]}],"responses":{"200":{"description":"Session revoked if present; auth cookies cleared.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/LogoutResult"}}},"example":{"data":{"success":true}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/auth/customer/register":{"post":{"tags":["Customers"],"operationId":"registerCustomer","summary":"Create a customer account","description":"Multipart phone/password-only registration is valid. Optional details and up to three files, one per type; signatures/MIME must match and each file is at most 10 MiB. Duplicate/unknown fields and client-supplied roles are rejected. Unselected empty file controls are ignored. Phone/email uniqueness spans all accounts. Any browser Origin must match BETTER_AUTH_URL. Creates the account without logging in: next call /api/auth/login. After an ambiguous network timeout, try login before registering again. Let the client set the multipart boundary.","security":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CustomerRegistration"},"encoding":{"shopImage":{"contentType":"image/jpeg, image/png, image/webp"},"nidDocument":{"contentType":"image/jpeg, image/png, image/webp, application/pdf"},"tradeLicenseDocument":{"contentType":"image/jpeg, image/png, image/webp, application/pdf"}}}}},"responses":{"201":{"description":"Customer created. No session is issued.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CustomerProfile"}}},"example":{"data":{"id":"customer-id","name":"","email":null,"phoneNumber":"+8801712345678","shopName":null,"shopAddress":null,"shopType":null,"customerType":"RETAIL","documents":[]}}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"409":{"description":"CONFLICT: This email, phone number or name is already in use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"CONFLICT","message":"This email, phone number or name is already in use."}}}}},"413":{"description":"PAYLOAD_TOO_LARGE: Choose a file of 10 MiB or smaller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"PAYLOAD_TOO_LARGE","message":"Choose a file of 10 MiB or smaller."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}},"502":{"description":"STORAGE_ERROR: Storage is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"STORAGE_ERROR","message":"Storage is unavailable."}}}}}}}},"/api/auth/customer/me":{"get":{"tags":["Customers"],"operationId":"getCustomerProfile","summary":"Read current customer's business profile","description":"Requires an active customer with no staff role. Returns profile and private document metadata, not credential hashes or storage coordinates. Invalid explicit bearer never falls back to cookies.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CustomerProfile"}}},"example":{"data":{"id":"customer-id","name":"","email":null,"phoneNumber":"+8801712345678","shopName":null,"shopAddress":null,"shopType":null,"customerType":"RETAIL","documents":[]}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/customers/me/documents/{type}":{"get":{"tags":["Customers"],"operationId":"downloadCustomerDocument","summary":"Download an attachment owned by the current customer","description":"Requires an active customer. Owner is resolved from the session; no owner ID is accepted. Downloads as an attachment with no-store and nosniff. Missing documents return 404. Self-service replacement is not supported.","security":[{"bearerAuth":[]}],"parameters":[{"name":"type","in":"path","required":true,"schema":{"type":"string","enum":["shop-image","nid","trade-license"]},"description":"Attachment type: shop photo, NID or trade license."}],"responses":{"200":{"description":"Binary attachment in its stored MIME type (shop photo cannot be PDF).","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"attachment; includes the original filename."},"Content-Length":{"schema":{"type":"integer"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"404":{"description":"NOT_FOUND: Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"NOT_FOUND","message":"Resource not found."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}},"502":{"description":"STORAGE_ERROR: Storage is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"STORAGE_ERROR","message":"Storage is unavailable."}}}}}}}},"/api/products":{"get":{"tags":["Products"],"operationId":"listPublicProducts","summary":"Search published products","description":"Only published products. Unsupported query keys and invalid ranges return 400. Search/filter/count/sort apply before pagination. Anonymous/staff/role-less users receive no prices; price queries require an active customer. Pricing uses the live database tier and rounded discounted final price. minPrice must not exceed maxPrice. Invalid explicit bearer and inactive authenticated accounts return 401. A missing category yields empty results. sort=price requires customer authentication.","security":[{},{"bearerAuth":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":150,"default":""},"description":"Trimmed, case-insensitive literal name search."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000000,"default":1},"description":"One-based page. Pages beyond the last return an empty items array."},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Number of items per page."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["name","createdAt","price"],"default":"createdAt"},"description":"Sort the complete filtered dataset before pagination; ties use ascending ID."},{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort direction."},{"name":"categoryId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":100},"description":"Main category ID includes directly assigned products and its subcategories; a subcategory ID includes only its products. uncategorized selects products with no category."},{"name":"minPrice","in":"query","required":false,"schema":{"type":"string","pattern":"^(?:0|[1-9]\\d{0,9})(?:\\.\\d{1,2})?$"},"description":"Inclusive minimum rounded final price in BDT; customer authentication required. Nonnegative decimal string, at most 10 integer digits and two decimals."},{"name":"maxPrice","in":"query","required":false,"schema":{"type":"string","pattern":"^(?:0|[1-9]\\d{0,9})(?:\\.\\d{1,2})?$"},"description":"Inclusive maximum rounded final price in BDT; customer authentication required."}],"responses":{"200":{"description":"Filtered products and pagination. Empty datasets have totalPages=0.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ProductList"}}},"examples":{"anonymous":{"value":{"data":{"items":[{"id":"product-id","name":"বাংলা পণ্য","slug":"বাংলা-পণ্য","category":null,"images":["https://images.example/product.png"],"wholesaleMinOrderQuantity":12,"retailMinOrderQuantity":1,"descriptionHtml":null}],"pagination":{"page":1,"pageSize":20,"total":1,"totalPages":1}}}},"customer":{"value":{"data":{"items":[{"id":"product-id","name":"বাংলা পণ্য","slug":"বাংলা-পণ্য","category":null,"images":["https://images.example/product.png"],"wholesaleMinOrderQuantity":12,"retailMinOrderQuantity":1,"descriptionHtml":null,"pricing":{"currency":"BDT","basePrice":"100.00","discount":{"type":"PERCENTAGE","value":"10.00"},"finalPrice":"90.00"}}],"pagination":{"page":1,"pageSize":20,"total":1,"totalPages":1}}}},"empty":{"value":{"data":{"items":[],"pagination":{"page":1,"pageSize":20,"total":0,"totalPages":0}}}}}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"403":{"description":"FORBIDDEN: Request origin or account access is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN","message":"Request origin or account access is not allowed."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/products/{slug}":{"get":{"tags":["Products"],"operationId":"getPublicProduct","summary":"Read a published product by slug","description":"Missing or draft products return 404. List and detail include sanitized descriptionHtml; detail additionally includes validated Tiptap JSON. Pricing is omitted for anonymous/staff/role-less callers and includes only the current customer's tier. Invalid supplied credentials return 401.","security":[{},{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Exact published product slug, including Unicode slugs; URL-encode it."}],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PublicProductDetail"}}},"example":{"data":{"id":"product-id","name":"বাংলা পণ্য","slug":"বাংলা-পণ্য","category":null,"images":["https://images.example/product.png"],"wholesaleMinOrderQuantity":12,"retailMinOrderQuantity":1,"descriptionHtml":null,"pricing":{"currency":"BDT","basePrice":"100.00","discount":{"type":"PERCENTAGE","value":"10.00"},"finalPrice":"90.00"},"description":null}}}}},"401":{"description":"UNAUTHENTICATED: Please sign in again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHENTICATED","message":"Please sign in again."}}}}},"404":{"description":"NOT_FOUND: Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"NOT_FOUND","message":"Resource not found."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/categories":{"get":{"tags":["Categories"],"operationId":"listPublicCategories","summary":"Search categories","description":"Anonymous read of main categories and subcategories. productCount includes published products only, including immediate subcategories for main categories. Search matches the category or its parent name; parentId=root searches main categories and their child names. Sorting uses display name and ascending ID ties before pagination. Only sort=name is supported. Unknown query keys (including admin hasImage) return 400. Empty results return items=[] and totalPages=0. Use category detail for all immediate children.","security":[],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":150,"default":""},"description":"Trimmed, case-insensitive literal name search."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000000,"default":1},"description":"One-based page. Pages beyond the last return an empty items array."},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Number of items per page."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["name"],"default":"name"},"description":"Sort the complete filtered dataset before pagination; ties use ascending ID."},{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"asc"},"description":"Sort direction."},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":100},"description":"root for main categories, or a category ID for its immediate children. Omit to list both levels."}],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CategoryList"}}},"example":{"data":{"items":[{"id":"category-id","name":"Groceries","description":null,"image":null,"parentId":null,"parent":null,"childCount":0,"productCount":5}],"pagination":{"page":1,"pageSize":20,"total":1,"totalPages":1}}}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/categories/{id}":{"get":{"tags":["Categories"],"operationId":"getPublicCategory","summary":"Read a category and its subcategories","description":"Anonymous read. Returns safe category fields, productCount (published only), and all immediate children sorted by name and ID. Main totals include immediate child products. A subcategory has children=[] and a direct productCount. Missing categories return 404; private asset metadata is omitted.","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Exact category ID."}],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PublicCategoryDetail"}}},"example":{"data":{"id":"category-id","name":"Electronics","description":null,"image":null,"parentId":null,"parent":null,"childCount":1,"productCount":8,"children":[{"id":"subcategory-id","name":"Accessories","description":null,"image":null,"parentId":"category-id","parent":{"id":"category-id","name":"Electronics"},"childCount":0,"productCount":3}]}}}}},"404":{"description":"NOT_FOUND: Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"NOT_FOUND","message":"Resource not found."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/settings":{"get":{"tags":["Settings"],"operationId":"getPublicSettings","summary":"Read public settings and registration options","description":"Anonymous bootstrap read; no filters, sorting or pagination. Unset text/logo values are null. shopTypes/customerTypes are static bilingual options, independent of product categories; display labelEn/labelBn and submit value. Provider configuration and storage secrets are excluded.","security":[],"responses":{"200":{"description":"Successful response.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PublicSettings"}}},"example":{"data":{"siteName":"Product management","seoTitle":null,"seoDescription":null,"contactEmail":null,"contactPhone":null,"contactAddress":null,"facebookUrl":null,"youtubeUrl":null,"whatsappUrl":null,"play_store":null,"app_store":null,"logo":null,"shopTypes":[{"value":"GROCERY","labelEn":"Grocery","labelBn":"মুদি"},{"value":"TEA_SHOP","labelEn":"Tea shop","labelBn":"চায়ের দোকান"},{"value":"STATIONERY","labelEn":"Stationery","labelBn":"স্টেশনারি"},{"value":"PHARMACY","labelEn":"Pharmacy","labelBn":"ফার্মেসী"},{"value":"HARDWARE","labelEn":"Hardware","labelBn":"হার্ডওয়্যার"},{"value":"ELECTRICAL","labelEn":"Electrical","labelBn":"ইলেক্ট্রিক"},{"value":"CROCKERY","labelEn":"Crockery","labelBn":"ক্রোকারিজ"},{"value":"COSMETICS","labelEn":"Cosmetics","labelBn":"কসমেটিক্স"},{"value":"OTHER","labelEn":"Other","labelBn":"অন্যান্য"}],"customerTypes":[{"value":"WHOLESALE","labelEn":"Wholesale","labelBn":"হোলসেল"},{"value":"RETAIL","labelEn":"Retail","labelBn":"রিটেইলার"}]}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}},"/api/app-content/{key}":{"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9_-]+$"},"description":"Exact protocol key: lowercase letters, digits, hyphens or underscores. No normalization."}],"get":{"tags":["App Content"],"operationId":"getPublicAppContent","summary":"Look up published content by key","description":"Anonymous dynamic/no-store lookup, no list filters. GET registers an unknown valid key as a draft without overwriting existing content. Unknown, draft and empty published records return HTTP 200 with data.content=null, not 404. Published text is returned as sanitized bodyHtml; editor JSON is omitted to avoid duplicate content. Images are ordered URL objects. Avoid probing invented keys against production. HEAD is unsupported and returns 405 without registering a key.","security":[],"responses":{"200":{"description":"Published content or null.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AppContentLookup"}}},"examples":{"published":{"value":{"data":{"content":{"key":"about","title":"About us","bodyHtml":null,"images":[],"externalUrl":null}}}},"unavailable":{"value":{"data":{"content":null}}}}}}},"400":{"description":"VALIDATION_ERROR: Check the highlighted fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Check the highlighted fields."}}}}},"500":{"description":"INTERNAL_ERROR: Something went wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong."}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Session token returned by POST /api/auth/login with responseMode=token. Explicit Authorization takes precedence over cookies, even when invalid."}},"schemas":{"ApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["VALIDATION_ERROR","UNAUTHENTICATED","FORBIDDEN","NOT_FOUND","CONFLICT","PAYLOAD_TOO_LARGE","RATE_LIMITED","INTERNAL_ERROR","STORAGE_ERROR"]},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}},"Pagination":{"type":"object","required":["page","pageSize","total","totalPages"],"properties":{"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1,"maximum":100},"total":{"type":"integer","minimum":0},"totalPages":{"type":"integer","minimum":0}}},"RichText":{"type":["object","null"],"description":"Validated Tiptap document (up to 100 KiB). Empty text becomes null. Supports paragraphs, headings 2/3, lists, blockquotes and supported inline marks; links are HTTP/HTTPS.","required":["type"],"properties":{"type":{"const":"doc"},"content":{"type":"array","items":{"$ref":"#/components/schemas/RichTextNode"}}}},"RichTextNode":{"type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"type":"string"},"text":{"type":"string"},"attrs":{"type":"object","additionalProperties":true},"marks":{"type":"array","items":{"type":"object","required":["type"],"properties":{"type":{"type":"string"},"attrs":{"type":"object","additionalProperties":true}}}},"content":{"type":"array","items":{"$ref":"#/components/schemas/RichTextNode"}}}},"LoginInput":{"type":"object","additionalProperties":false,"required":["identifier","password"],"properties":{"identifier":{"type":"string","minLength":1,"maxLength":254,"description":"Trimmed lowercase email or Bangladesh mobile number (01[3-9]xxxxxxxx / +8801[3-9]xxxxxxxx)."},"password":{"type":"string","minLength":8,"maxLength":128,"format":"password"},"rememberMe":{"type":"boolean","description":"Passed to Better Auth to choose session persistence."},"responseMode":{"type":"string","enum":["cookie","token"],"default":"cookie","description":"Cookie mode returns no token. Native clients must explicitly request token mode."}}},"AuthIdentity":{"type":"object","required":["id","name","email","phoneNumber","image","accountType","customerType","permissions"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":["string","null"]},"phoneNumber":{"type":["string","null"]},"image":{"type":["string","null"]},"accountType":{"type":"string","enum":["STAFF","CUSTOMER","USER"]},"customerType":{"type":["string","null"],"enum":["WHOLESALE","RETAIL",null]},"permissions":{"type":"array","items":{"type":"string"},"description":"Current server-owned permission codes. Customers and role-less users receive an empty array."}}},"AuthSession":{"type":"object","required":["user","expiresAt"],"properties":{"user":{"$ref":"#/components/schemas/AuthIdentity"},"expiresAt":{"type":"string","format":"date-time"}}},"LoginResult":{"allOf":[{"$ref":"#/components/schemas/AuthSession"},{"type":"object","properties":{"token":{"type":"string","description":"Present only in explicit token mode. Store securely; do not treat as JWT."}}}]},"LogoutResult":{"type":"object","required":["success"],"properties":{"success":{"const":true}}},"CustomerRegistration":{"type":"object","additionalProperties":false,"required":["phoneNumber","password"],"properties":{"phoneNumber":{"type":"string","maxLength":30,"pattern":"^(?:01[3-9]\\d{8}|\\+8801[3-9]\\d{8})$","example":"01712345678","description":"Required; trimmed and normalized to +880 format."},"password":{"type":"string","format":"password","minLength":8,"maxLength":128},"name":{"type":"string","maxLength":120,"default":"","description":"Optional; trimmed, omitted/blank becomes empty string."},"email":{"type":"string","maxLength":254,"description":"Optional valid email; trimmed/lowercase, omitted/blank becomes null."},"shopName":{"type":"string","maxLength":200,"description":"Optional; omitted/blank becomes null."},"shopAddress":{"type":"string","maxLength":1000,"description":"Optional; omitted/blank becomes null."},"customerType":{"type":"string","enum":["WHOLESALE","RETAIL"],"default":"RETAIL","description":"Optional; omitted/blank defaults to RETAIL."},"shopType":{"type":"string","enum":["GROCERY","TEA_SHOP","STATIONERY","PHARMACY","HARDWARE","ELECTRICAL","CROCKERY","COSMETICS","OTHER"],"description":"Optional English option code; omitted/blank becomes null."},"shopImage":{"type":"string","format":"binary","description":"Optional JPEG/PNG/WebP; maximum 10 MiB; PDF is not accepted."},"nidDocument":{"type":"string","format":"binary","description":"Optional JPEG/PNG/WebP/PDF; maximum 10 MiB."},"tradeLicenseDocument":{"type":"string","format":"binary","description":"Optional JPEG/PNG/WebP/PDF; maximum 10 MiB."}}},"CustomerDocument":{"type":"object","required":["type","originalName","mimeType","byteSize"],"properties":{"type":{"type":"string","enum":["SHOP_IMAGE","NID","TRADE_LICENSE"]},"originalName":{"type":"string","maxLength":255},"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/webp","application/pdf"]},"byteSize":{"type":"integer","minimum":1,"maximum":10485760}}},"CustomerProfile":{"type":"object","required":["id","name","email","phoneNumber","shopName","shopAddress","shopType","customerType","documents"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":["string","null"]},"phoneNumber":{"type":["string","null"]},"shopName":{"type":["string","null"]},"shopAddress":{"type":["string","null"]},"shopType":{"type":["string","null"],"enum":["GROCERY","TEA_SHOP","STATIONERY","PHARMACY","HARDWARE","ELECTRICAL","CROCKERY","COSMETICS","OTHER",null]},"customerType":{"type":"string","enum":["WHOLESALE","RETAIL"],"default":"RETAIL"},"documents":{"type":"array","items":{"$ref":"#/components/schemas/CustomerDocument"},"description":"Metadata only; absent attachments produce an empty array. No public URLs or storage keys."}}},"ProductPricing":{"type":"object","required":["currency","basePrice","discount","finalPrice"],"properties":{"currency":{"const":"BDT"},"basePrice":{"type":"string","pattern":"^\\d+\\.\\d{2}$"},"finalPrice":{"type":"string","pattern":"^\\d+\\.\\d{2}$","description":"Current customer's discounted price, rounded half-up to two decimals."},"discount":{"type":["object","null"],"required":["type","value"],"properties":{"type":{"type":"string","enum":["FIXED","PERCENTAGE"]},"value":{"type":"string","pattern":"^\\d+\\.\\d{2}$"}}}}},"PublicProduct":{"type":"object","required":["id","name","slug","category","images","wholesaleMinOrderQuantity","retailMinOrderQuantity","descriptionHtml"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"wholesaleMinOrderQuantity":{"type":"integer","minimum":1,"maximum":2147483647,"description":"Minimum wholesale order quantity. Defaults to 1."},"retailMinOrderQuantity":{"type":"integer","minimum":1,"maximum":2147483647,"description":"Minimum retail order quantity. Defaults to 1."},"category":{"$ref":"#/components/schemas/CategoryOption"},"images":{"type":"array","items":{"type":"string"},"description":"Ordered direct image URLs."},"descriptionHtml":{"type":["string","null"],"description":"Sanitized HTML rendering of description. Always present; null when no description is saved."},"pricing":{"$ref":"#/components/schemas/ProductPricing","description":"Present only for a customer with a current tier. Omitted for anonymous/staff/role-less identities."}}},"PublicProductDetail":{"allOf":[{"$ref":"#/components/schemas/PublicProduct"},{"type":"object","required":["description"],"properties":{"description":{"$ref":"#/components/schemas/RichText"}}}]},"ProductList":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicProduct"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"CategoryParent":{"type":["object","null"],"required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"CategoryOption":{"type":["object","null"],"required":["id","name","parentId","parent"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"type":["string","null"]},"parent":{"$ref":"#/components/schemas/CategoryParent"},"productCount":{"type":"integer","minimum":0,"description":"Included by category choice endpoints; published products including immediate subcategories."}}},"PublicCategory":{"type":"object","required":["id","name","description","image","parentId","parent","childCount","productCount"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"type":["string","null"]},"parent":{"$ref":"#/components/schemas/CategoryParent"},"childCount":{"type":"integer","minimum":0},"productCount":{"type":"integer","minimum":0,"description":"Published products only. Main categories include products assigned to immediate subcategories; subcategories count direct assignments."},"description":{"type":["string","null"]},"image":{"type":["string","null"],"description":"Direct image URL or null."}}},"PublicCategoryDetail":{"allOf":[{"$ref":"#/components/schemas/PublicCategory"},{"type":"object","required":["children"],"properties":{"children":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategory"},"description":"All immediate subcategories, sorted by name and ID. Empty for a subcategory."}}}]},"CategoryList":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategory"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"RegistrationOption":{"type":"object","required":["value","labelEn","labelBn"],"properties":{"value":{"type":"string"},"labelEn":{"type":"string"},"labelBn":{"type":"string"}}},"PublicSettings":{"type":"object","required":["siteName","seoTitle","seoDescription","contactEmail","contactPhone","contactAddress","facebookUrl","youtubeUrl","whatsappUrl","play_store","app_store","logo","shopTypes","customerTypes"],"additionalProperties":false,"properties":{"siteName":{"type":["string","null"]},"seoTitle":{"type":["string","null"]},"seoDescription":{"type":["string","null"]},"contactEmail":{"type":["string","null"]},"contactPhone":{"type":["string","null"]},"contactAddress":{"type":["string","null"]},"facebookUrl":{"type":["string","null"]},"youtubeUrl":{"type":["string","null"]},"whatsappUrl":{"type":["string","null"]},"play_store":{"type":["string","null"]},"app_store":{"type":["string","null"]},"logo":{"type":["string","null"]},"shopTypes":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/RegistrationOption"},{"properties":{"value":{"enum":["GROCERY","TEA_SHOP","STATIONERY","PHARMACY","HARDWARE","ELECTRICAL","CROCKERY","COSMETICS","OTHER"]}}}]}},"customerTypes":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/RegistrationOption"},{"properties":{"value":{"enum":["WHOLESALE","RETAIL"]}}}]}}}},"PublicAppContent":{"type":["object","null"],"required":["key","title","bodyHtml","images","externalUrl"],"properties":{"key":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9_-]+$"},"title":{"type":["string","null"]},"bodyHtml":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"images":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string"}}},"description":"Ordered direct image URL objects."}}},"AppContentLookup":{"type":"object","required":["content"],"properties":{"content":{"$ref":"#/components/schemas/PublicAppContent"}}}}}}