Skip to content

Content, forms and contact ​

16. Content: CMS, blogs, events, forms, hubs ​

16.1 CMS content ​

A brand's app screens are published content, not app builds. The app asks for whatever is live for its platform and renders that, so a layout change ships without a release. Content is served by its own service and cached in Redis per brandId + platform + version.

GET /api/v2/brand/mobile/cms — the live content ​

Auth: Brand · Handler: getLiveContent (app/v2/controller/cms_controller.ts) Query: platform ✅ (string, min 1 — e.g. ios, android).

Returns 404, not the usual 200, when the brand has nothing published for that platform — so a client can tell "nothing published" apart from a failure. This is the endpoint the app renders from.

GET /api/v2/brand/mobile/cms/platform-state ​

Auth: Brand · Handler: getPlatformState · No params.

Which platforms have published content, and at what version. Poll it to decide whether the live content is worth re-fetching.

GET /api/v1/mobile/cms — the older content endpoint ​

Auth: Brand · Handler: getCMSByBrandId (cms_controller.js) Query: category, subCategory — comma-separated · name — split on ", " (comma + space) · version (string, default "0.0.0").

Still mounted, and still what brands predating the content service are served from. New mobile work reads the live content endpoint above instead.

16.2 GET /api/v1/mobile/blogs ​

Auth: Brand · Handler: getAllBlogs (blog_controller.js) Query: category (comma-separated keys of blogTypes, e.g. NEWS,NEW_RELEASES), tags (comma-separated), group_by (month | date), limit, offset, sortItem + sortType (comma-separated pairs of equal length; sortItem ∈ launchingDate, createdAt; sortType ∈ ASC, DESC).

16.3 GET /api/v1/mobile/blog/:blogId — path blogId (number) ✅ · Brand · getBlogById ​

16.4 GET /api/v1/mobile/blogs/:slug — path slug (string) ✅ · Brand · getBlogBySlug ​

⚠️ /mobile/blogs/:slug and /mobile/blogs are distinct routes, but a numeric-looking slug still resolves against :slug, not :blogId.

16.5 GET /api/v1/mobile/events (guest) · GET /api/v1/user/mobile/events (customer) ​

Handler: getAllEvents (event_controller.ts) — one handler, two mounts; the customer variant annotates per-user state.

Query (all optional): tags (comma-separated), limit, offset, search, group_by (month | date), includeExpired ("true"), expiredOnly ("true"), type (values of event_types: standard, drop), sortItem + sortType (comma-separated, equal length; sortItem ∈ start_date, end_date, createdAt, name). Default sort is relative to now: upcoming/in-progress first (soonest first), then finished (most recent first).

16.6 GET /api/v1/mobile/event/:event_id ​

Auth: Brand · Handler: getEventById · Path: event_id (number) ✅.

16.7 POST /api/v1/mobile/event/lead/:event_id (guest) · POST /api/v1/user/mobile/event/lead/:event_id (customer) ​

Handler: createEventLead — one handler, two mounts. Path: event_id (number) ✅. Body payload:

fieldtypereqnotes
emailstring(email)one-ofneeds email or phone_number
phone_numberstringone-ofrequires phone_prefix (.with)
phone_prefixstringconditional
first_name, last_name, source, push_tokenstring—
form_responsesarray—{ question_id ✅ (number), answer ✅ (any) }, default []

16.8 POST /api/v1/brand/mobile/form/response (customer) · POST /api/v1/brand/mobile/public/form/response (guest) ​

Handler: createFormResponse (form_controller.ts) — one handler, two mounts. The public variant records the response with no userId. Body payload: form_assignment_id ✅ (number), form_response ✅ (array of { question_id ✅ (number), answer ✅ (string) }).

16.9 GET /api/v1/brand/mobile/form/type ​

Auth: Brand · Handler: getFormByType Query — both required: id ✅ (number — the entity the form is attached to), type ✅ (keys of formType: scan_verify, scan_survey, product_review).

16.10 GET /api/v1/mobile/listed/hubs — store locator ​

Auth: Brand · Handler: getListedHubByBrand (listedHub_controller.js)

Query (all optional): show_on_nearest (boolean), limit, offset, search, lat + lng (must be sent together, .and), state, country (ISO code), city. limit falls back to the brand's siteConfiguration.listedHub.limit, then 10. Response: { count, limit, offset, ... } where count is the total match count.

⚠️ city triggers the group filter but is not forwarded into it — only state and country actually filter. See §23.

The web twin of this route, and the admin endpoints that create the records it returns, are in Locations API.


17. Contact & newsletter ​

Source: routes/v1/contactus_route.js → contactus_controller.js

17.1 POST /api/v1/mobile/contact-us ​

Auth: Brand · Handler: addToContactUs · Body payload: email ✅ (email), message ✅ (string), firstName, lastName, emailType, phoneNumber (+ phone_prefix required with it), additional_data (any). Returns a message string, not a record.

17.2 POST /api/v1/user/mobile/newsletter ​

Auth: Customer · Handler: addToNewsLetter · Body payload:

fieldtypereqnotes
emailstring(email)✅schema marks it required and has an or(email, phone_number)
categoryenum✅keys of newsletterTypes: NEWS, NEW_RELEASES, PRODUCT_UPDATE, COMPANY_ANNOUNCEMENT
pointingIdstringconditionalrequired when category is PRODUCT_UPDATE or NEW_RELEASES
first_name, last_name, sourcestring—
phone_numberstring—requires phone_prefix

Batch platform, Shopify app and API documentation