Skip to content

Threads flow ​

Threads is the app's community feed, and the fifth tab beside home, shop, scan and rewards. A brand posts a photo and a note; shoppers like it, comment on it, reply to each other, and can start a thread of their own.

It is the only screen in the app where a customer writes something other customers read, which is why the questions at the foot of this page are mostly not about field names.

Play the flow ​

Three paths: reading and replying, composing a thread, and the image sheet.

1 / 6
4:4713
@rivera
Night Shift x First Pour
this run comes down at the end of the week. we need a name for the cross — drop yours below.
♡💬↗···
@delacroix
Strain of the year
✎
homeshopscanrewardsthreads

Space plays and pauses, ← and → step. The screens are the shipped design. What sits behind them is the problem.

Read this before building against it ​

Every call this flow needs is a gap. No thread, post, comment, like or feed route exists in either backend — not in the customer backend's routes/v1 or routes/v2, and not in the admin backend. So this page cannot tell you field names, and it does not guess at them: a screen needing data is not evidence that an endpoint returns it.

What that means in practice is that Threads is served by a separate service, and the trace it leaves in the customer backend is the one endpoint below.

The one documented call: POST /api/v1/feed/users ​

Not a mobile endpoint. It is service-to-service, and it is the feed asking the customer backend to turn a list of user ids into people.

Auth: HTTP Basic — Authorization: Basic <base64 user:pass>, checked by isValidInternalReq against a single username and password pair from the environment. Not the brand x-auth-token, not a customer JWT. Handler: getCustomers (customer_controller.js) → _getCustomers.

fieldtypereqnotes
user_idsarray of number✅under payload, as usual. Every item required

It answers { success: true, result: { customers: [...] } } with the full customer row minus password, otp, brandId and the timestamps.

Three things follow from reading it rather than its name:

  • It is not scoped to a brand. The query filters on userId and isVerified only, and brandId is stripped from what comes back — so ids from one brand's feed resolve against every brand's customers.
  • Unverified customers come back as nothing. A row that has not verified is filtered out rather than returned as a stub, so a feed holding a comment from one has no author to render and has to cope with the shortfall itself.
  • The feed holds the ids. It sends them; it is not handed them. Whatever stores a post already knows which customer wrote it.

Stages ​

API calls by stage ​

Every row marked Gap is a call the screen makes and no page can document.

StageActionAPI callAuth
1The feed page: posts, authors, images, countsGap—
1Resolving author names for the pagePOST /api/v1/feed/users — internal, and called by the feed service, not the appBasic
2Like, and unlikeGap—
3Open the commentsNo call — a client-side navigation—
3aComments, with one level of repliesGap—
3bPost a comment or a replyGap—
4Download the imageNo call — the image is already fetched—
5aAttach an imageGap, but POST /api/v1/user/mobile/uploadImage is the platform's one image upload and answers with a Cloudinary urlCustomer
5bCreate the threadGap—
—Share a postGap — whether the share sheet needs a server-side link at all is undecided—

The tab bar itself is ordinary: the other four tabs are the flows already documented — shop, scan, rewards — and home is published content.

What the design settles, and what it doesn't ​

Read off the screens rather than off any schema, so treat all of it as the requirement a real contract has to meet, not as the contract:

The screens showSo the data needs
Author avatar and display name on every post and commentA customer reference per row, resolved through feed/users
A post has a title line and a bodyTwo text fields, not one — the title is set in bolder type
One image per postA single url, not a gallery. Nothing in the design pages through images
Replies indent exactly one levelThreading is capped at depth one. A reply to a reply has nowhere to go
A parent comment shows ♡ 5 and Reply 3Per-comment like and reply counts on the parent, not computed from a fetched child list
Relative ages — 1y, 11moA timestamp per row; the app formats it
The overflow sheet has Download Image and CancelThere is no report, block, mute, delete or edit anywhere in the shipped design

That last row is the one to take seriously. A feed shipping without a report path is a product decision, not an oversight to route around in the client.

Open questions ​

  • Who may post? The compose screen is reachable from the feed, but every post in the design is from brand staff. If a shopper can post, nothing in the design moderates it; if they cannot, the compose button needs a gate and there is no field to gate it on.
  • Does anything moderate a comment? No approval state appears on any screen, and the sheet offers no way to report one. The screenshots this page was drawn from already show a bare link posted under an unidentified account, so the answer today is no.
  • Is a like idempotent per customer? The design has a filled and an unfilled heart and no count beside it on the post, so the client cannot tell a second tap from a double submission without the server saying which it was.
  • How does the feed paginate, and does a new post land at the top? Nothing in the design is a cursor, and after posting the app returns to the feed.
  • Can a post be deleted or edited? Not from the sheet, not from anywhere else in the design — including by its author.
  • Is the feed brand-scoped? The one endpoint underneath it is not, which makes this worth confirming rather than assuming.
  • Does a guest see the feed? The tab is reachable signed out on every other tab's pattern, but writing needs a customer session.
  • Does anything notify? A reply to your comment has no notification in the documented set, and no screen shows one.

Batch platform, Shopify app and API documentation