Merchant Catalog Feed (scheduled feed sync — specified, not yet built)
Status: specified here, not yet implemented.
listing-servicecurrently only supports the real-time API path (POST/PUT/DELETE /merchant/listings, seemerchant-integration-guide.mdstep 4). This document specifies the second, feed-based sync path referenced indocs/business/business-model.md's "two adoption paths" principle, so thin-engineering merchants (thrift/consignment shops, classifieds sites) aren't required to write API-calling code to get their catalog into TBBN. Seeservices/listing-service/README.md's "Not yet built" section for the implementation-side status of this spec.
Why a second sync path at all
business-model.md already commits TBBN to two first-class adoption paths — API/SDK for
engineering-capable merchants, and widgets/plugins for thin-engineering ones — specifically
because excluding either would cut out a large share of the named target market (thrift shops,
consignment businesses, classifieds sites). Widgets solve the consumer-facing half of that
problem (no negotiation UI to build); they don't solve the catalog sync half — a merchant still
needs some way to tell TBBN what they have for sale/trade. Most of that segment already
maintains a Google Merchant Center and/or Meta Commerce Manager feed for their own ads/shopping
listings. Reusing that same feed shape for TBBN, instead of requiring a bespoke API integration,
is the lowest-lift onboarding path available to them.
This is not a novel design — it deliberately mirrors how Google and Meta themselves solve the exact same "some merchants can call an API, most can only export a file" problem, cross-checked against how Amazon and Shopify differ from that pattern (see "Why not just copy Amazon or Shopify," below).
The two sync paths, side by side
| Direct API (built) | Catalog feed (this doc) | |
|---|---|---|
| Who it's for | Merchants with engineering capacity | Thin-engineering merchants who already export a Google/Meta-style feed |
| Mechanism | POST/PUT/DELETE /merchant/listings, real-time | TBBN pulls a merchant-hosted feed URL on a schedule |
| Latency | Immediate | Up to the refresh interval, or on-demand via manual trigger |
| Auth | sk_live_... API key, per-request | feedUsername/feedPassword on the feed-source config, sent as HTTP Basic Auth when TBBN fetches the URL |
| Best for | Catalogs that change constantly, or merchants who want full control | Catalogs that change a few times a day, exported from an existing PIM/ecommerce platform |
A merchant may use either path, or both (e.g. bulk catalog via feed, high-velocity individual items via the API) — TBBN treats every resulting listing identically once ingested; the sync path is not visible anywhere downstream of ingestion.
Feed formats
XML (RSS 2.0-shaped, TBBN-namespaced) and CSV/TSV, mirroring the format set both Google Merchant
Center and Meta Commerce Manager support. Field names deliberately reuse Google's established
vocabulary (id, title, price, condition, image_link, availability) rather than
inventing new ones — Shopify has no native authenticated feed feature of its own, so its entire
third-party feed-app ecosystem (Feedyio, FeedFusion, LitCommerce, etc.) already outputs
Google/Meta-shaped feeds. A merchant already running one of those apps can point it at TBBN by
changing the destination URL, not by building anything new.
Feed field reference
| Field | Required | Modeled on | Note |
|---|---|---|---|
item_id | Yes | Google/Meta id | Unique key; anchors create/update/delete on re-fetch |
category, sub_category | Yes | Google's google_product_category (standard taxonomy) + product_type (merchant free text) | Two-tier split enables cross-catalog matching while preserving the merchant's own organization |
brand | Conditional | Google/Meta brand | Required unless the merchant declares a no-brand category (matches Google's conditional rule; Meta's stricter unconditional rule is not adopted here) |
condition | Yes | Meta's unconditional requirement | TBBN requires this unconditionally, stricter than Google's conditional rule — barter fairness (the Trade Compatibility Score) depends on condition being present for every item, not just used/refurbished ones |
original_price | Yes | Google/Meta price | Numeric + ISO 4217 currency code (e.g. 19.99 USD) — same shape computeTradeValue already expects, no reformatting needed at ingestion |
listing_type | Yes | No Google/Meta analog | Closed enum SALE / TRADE / BOTH — not a boolean, since "either" is a real, common case. Implemented for real on Listing.listingType (Phase 26/30) — a SALE listing is discovery-only and never offer-able; TBBN redirects to product_link instead of orchestrating a trade for it. |
wants / trade preferences | No | No Google/Meta analog | Structured array reusing the category/sub_category taxonomy above — kept structured, not free text, so the Wants Engine can match on it the same way it matches API-submitted listings |
image_link + additional_image_link[] | Yes (primary), No (rest) | Google/Meta pattern | One required primary image, optional array of secondary images |
quantity, availability | Yes | Google/Meta (kept as two separate fields: an enum status plus an integer count) | The trade-session reservation flow needs a real count, not just an in-stock/out-of-stock boolean |
product_link | No | Google Merchant Center's link field | URL to the product's page on the merchant's own website — distinct from image_link. Maps to Listing.merchantItemUrl (Phase 30): for a SALE-only listing, Landing-page renders this as the sole outbound CTA in place of an offer/checkout flow — Google-Merchant-product-card behavior exactly. Also lets a "reach-only" merchant (Merchant.tradingEnabled: false, Phase 22) surface a product for discovery without TBBN ever handling its checkout; a trading-enabled merchant can include it too, purely as a reference back to its own listing. |
feed_username/feed_password live on the feed-source config (the merchant→feed-URL mapping,
alongside merchant_settings), never on the item schema itself — this mirrors how both Google and
Meta separate transport auth from the product-data payload, and keeps credentials out of every
individual item record.
Authentication: separate credential fields, not a URL-embedded secret
TBBN's feed puller sends Authorization: Basic using a feedUsername/feedPassword pair stored
on the merchant's feed-source config — never credentials embedded directly in the feed URL string.
This is the same shape both Google (DatafeedFetchSchedule.fetchUrl/username/password on the
Content API) and Meta ("if password protected, provide username/password" in Commerce Manager's
scheduled-feed setup) use, and for the same reason: URL-embedded credentials are routinely dropped
or mishandled by HTTP clients on redirect, and they leak into server access logs. feedUrl and the
credential pair are three separate fields, full stop.
Refresh cadence and the manual trigger
24-hour scheduled pull, plus an on-demand manual "fetch now" trigger — gated behind the merchant's production API key the same way every other write path is credential-gated. This matches how both reference platforms actually behave, not just how they're marketed:
- Google's own scheduled-fetch UI only offers daily/weekly/monthly granularity — there is no
hourly option — plus a manual "Fetch now" button and a
fetchnow-equivalent API method. Google explicitly advises against calling it more than once a day; merchants who need tighter latency are pointed at the real-time Products API instead. - Meta's Commerce Manager offers hourly/daily/weekly scheduling plus its own "Fetch Now" button, and separately offers a Catalog Batch API for merchants who need to update more often than once an hour.
TBBN's 24-hour default plus manual trigger is therefore the direct baseline case both platforms converge on; a merchant whose catalog changes faster than that should use the direct API path (above) for those items instead of raising the feed's refresh frequency — same advice Google gives its own merchants, for the same reason (a tight polling loop against a merchant-hosted file is a worse experience for everyone than a push call TBBN can process immediately).
Why not just copy Amazon or Shopify
Both were checked as contrast cases, not as alternative designs to adopt:
- Amazon SP-API is push-only over OAuth2 to an Amazon-owned endpoint; there's no merchant-hosted pull-URL concept at all, and its legacy XML feed types are being retired in favor of a JSON-only feed format. Copying this would mean no low-lift path for merchants who only have a static feed export to offer — precisely the segment this design exists to serve.
- Shopify has no native authenticated pull-feed feature of its own; its ecosystem of third-party feed apps generating Google/Meta-shaped feeds is exactly why this spec reuses Google's field names in the first place (see "Feed formats," above).
Net effect: pull + HTTP Basic Auth + scheduled/manual refresh is the dominant pattern among the platforms merchants already use to reach shoppers, not a TBBN invention — adopting it is what makes "point your existing feed at a new URL" actually true for most of the target segment.
Open items
- Uncertain, flagged rather than assumed: Meta's exact wording for its username/password fields and its claimed hourly-schedule granularity are corroborated from third-party feed-vendor documentation, not fetched directly from Meta's own (JS-rendered) help center — treat as reasonably confident, not verified primary-source fact.
- Whether a "sub-hourly" Meta refresh option genuinely exists is a single, unconfirmed source — do not build a TBBN feature around matching it without re-verifying first.
- Implementation is not scoped to a specific phase yet — see
services/listing-service/README.mdfor the current "Not yet built" note. Building it means: a feed-source config table (feed URL + credentials, keyed tomerchant_settings), an XML/CSV parser producing the same normalized listing shapePOST /merchant/listingsalready validates against, a scheduler (24h default, configurable), and the manual-trigger endpoint. No new ingestion logic should diverge from the validation@tbbn/core-listingsalready enforces for the direct API path — a fed-in listing and an API-submitted listing must be indistinguishable to every downstream consumer.