Feeds
A product feed is the XML or CSV file ad networks poll to keep their copy of your catalog in sync. Ripllo currently emits a Google Merchant Center-compatible XML feed at a per-merchant URL; CSV and TikTok/Pinterest variants are on the roadmap.
The feed reads from MirroredProduct rows in Ripllo's database, which Storlaunch populates via storlaunch.product.* webhooks. Ripllo is the canonical feed emitter; Storlaunch is the canonical product owner. Keeping them on opposite sides of the partner boundary lets Storlaunch's catalog evolve without coupling to feed emission, and lets Ripllo serve the feed even when Storlaunch is down for maintenance.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/feeds/config |
Get feed config |
PATCH |
/api/v1/feeds/config |
Update feed config |
GET |
/api/v1/feeds/google/:accountId.xml |
Google Merchant Center feed (XML, no auth) |
Get feed config
GET /api/v1/feeds/config
Returns the merchant's feed configuration. Defaults to enabled because the feed URL itself is the opt-in — ad networks won't poll until the merchant submits the URL in their respective dashboards.
Response
{
"data": {
"id": "mfc_01HX...",
"accountId": "acc_01HX...",
"enabled": true,
"defaultGoogleProductCategory": "Apparel & Accessories > Clothing",
"includeUnpublished": false,
"createdAt": "2026-05-01T10:42:00.000Z",
"updatedAt": "2026-05-13T10:11:00.000Z"
},
"error": null,
"meta": { "requestId": "...", "timestamp": "..." }
}
If no config row exists yet, the default stub is returned:
{
"enabled": true,
"defaultGoogleProductCategory": null,
"includeUnpublished": false
}
Update feed config
PATCH /api/v1/feeds/config
Upserts the merchant's record. Partial; only the fields you supply are updated.
Request body
| Field | Type | Notes |
|---|---|---|
enabled |
boolean | When false, the public feed endpoint returns 404 feed disabled even with valid accountId. |
defaultGoogleProductCategory |
string (≤200) | null | A Google Product Category string applied to every item that doesn't have its own. Lookup tables live at google.com/basepages/producttype/taxonomy.en-US.txt. |
includeUnpublished |
boolean | When false (default), only products with available: true are emitted. Set to true for full-catalog inventory feeds. |
Response
The full updated MerchantFeedConfig object.
await ripllo.feeds.config.update({
defaultGoogleProductCategory: 'Apparel & Accessories > Clothing',
enabled: true,
});
Google Merchant Center feed
GET /api/v1/feeds/google/:accountId.xml
No auth, returns XML. The merchant submits this URL to Google Merchant Center, which polls it every 1–24 hours. The endpoint is intentionally public:
- The URL itself is the secret. It contains the merchant's
accountId, which is opaque from the outside. - The data exposed is what the merchant already publishes on their storefront. There's no leak.
Response — success
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
<channel>
<title>Storefront acc_01HX...</title>
<link>https://storlaunch.com/s/acc_01HX...</link>
<description>Product feed</description>
<item>
<g:id>prod_storlaunch_19823</g:id>
<g:title>Cotton t-shirt — medium</g:title>
<g:description>100% organic cotton, garment-dyed. Pre-shrunk.</g:description>
<g:link>https://storlaunch.com/s/acc_01HX.../products/cotton-tshirt-medium</g:link>
<g:image_link>https://cdn.storlaunch.com/...</g:image_link>
<g:availability>in_stock</g:availability>
<g:price>250000 IDR</g:price>
<g:brand>ExampleCo</g:brand>
<g:google_product_category>Apparel & Accessories > Clothing</g:google_product_category>
</item>
<!-- … up to 5,000 items, sorted by updatedAt desc … -->
</channel>
</rss>
The feed is capped at the most recently-updated 5,000 products. For merchants with larger catalogs, paginated feeds are on the roadmap; for now, the cap is documented and stable.
Response — disabled
HTTP/1.1 404 Not Found
Content-Type: text/plain
feed disabled
The merchant flipped enabled: false. The plain-text body is intentional — Google Merchant Center retries on 404 with backoff, which is the right behavior for a temporary disable.
Price formatting
| Currency | Format | Example |
|---|---|---|
| IDR | <cents> IDR |
250000 IDR |
| Everything else | <dollars>.<cc> <UPPERCASE> |
25.00 USD |
IDR is treated as a zero-decimal currency consistent with the rest of Ripllo — the underlying database stores it in rupiah, no cents. Other currencies are stored in cents and divided by 100 for display.
Field mapping
The feed pulls from the MirroredProduct table, which Storlaunch syncs via webhooks. The mapping is direct:
| Feed field | Source | Notes |
|---|---|---|
g:id |
externalRef |
Storlaunch's product ID. |
g:title |
name |
|
g:description |
description ?? name |
Falls back to the title if Storlaunch hasn't sent a description. |
g:link |
computed | <storefrontBaseUrl>/s/<accountId>/products/<slug> |
g:image_link |
imageUrl |
Omitted if null. |
g:availability |
available ? 'in_stock' : 'out_of_stock' |
|
g:price |
priceCents, currency |
See Price formatting. |
g:brand |
brand |
Omitted if null. |
g:google_product_category |
googleProductCategory ?? config.defaultGoogleProductCategory |
Per-product override, else config default. Omitted if both are null. |
Storefront base URL
The feed's <link> field is built from STOREFRONT_BASE_URL (defaults to https://storlaunch.com). Storlaunch redirects /s/<accountId> to the canonical slug-based URL because Ripllo doesn't know the merchant's slug — that's a Storlaunch concern. Acceptable for now; will tighten when Ripllo gains a direct-tenant UI of its own.
The merchant feed config object
| Field | Type | Notes |
|---|---|---|
id |
string | mfc_ + ULID. |
accountId |
string | Unique — one row per merchant. |
enabled |
boolean | Master switch. |
defaultGoogleProductCategory |
string | null | Fallback category. |
includeUnpublished |
boolean | Include items with available: false. |
createdAt, updatedAt |
ISO 8601 |
The mirrored product object (read-only)
You can't write to MirroredProduct directly — Storlaunch owns it via webhooks. But the feed renders from it, so understanding the shape helps debug missing items:
| Field | Type | Notes |
|---|---|---|
id |
string | mp_ + ULID. |
accountId |
string | Owning merchant. |
externalRef |
string | Storlaunch's product ID. Used as g:id. |
slug |
string | Used in the canonical URL. |
name |
string | |
description |
string | null | |
priceCents |
integer | Smallest currency unit. |
currency |
string | ISO 4217. |
available |
boolean | |
imageUrl |
string | null | |
brand |
string | null | |
googleProductCategory |
string | null | Per-product override. |
archivedAt |
ISO 8601 | null | Items with archivedAt != null are excluded from the feed regardless of available or includeUnpublished. |
updatedAt |
ISO 8601 | Most-recent-first ordering uses this. |
Submitting the feed to Google
- In Google Merchant Center, Products → Feeds → Add primary feed.
- Source: Scheduled fetch.
- URL:
https://ripllo.com/api/v1/feeds/google/<your-accountId>.xml. Get youraccountIdfrom the dashboard URL orGET /api/v1/feeds/config. - Fetch frequency: daily is fine for most catalogs.
For TikTok, Pinterest, and Meta Catalog, point them at the same URL — they all accept Google Merchant feed format, though some fields are ignored.
Events
The feeds resource doesn't emit outbox events. The feed is recomputed on every request; there's no "feed updated" lifecycle to broadcast.