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": "clx9d1n6t0000rw4f7g2h5j8k",
"accountId": "acc_9f2c1b7a",
"enabled": true,
"defaultGoogleProductCategory": "Apparel & Accessories > Clothing",
"includeUnpublished": false,
"marketingCampaignId": null,
"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. |
marketingCampaignId |
string | null | Optional parent marketing campaign — the feed then shows up under that campaign's feeds. null detaches. A campaign id from another workspace returns 400 VALIDATION with the message marketingCampaignId not found in this account. |
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_9f2c1b7a</title>
<link>https://storlaunch.com/s/acc_9f2c1b7a</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_9f2c1b7a/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 | Bare cuid (e.g. clx9d1n6t0000rw4f7g2h5j8k) — no prefix. |
accountId |
string | Unique — one row per merchant. |
enabled |
boolean | Master switch. |
defaultGoogleProductCategory |
string | null | Fallback category. |
includeUnpublished |
boolean | Include items with available: false. |
marketingCampaignId |
string | null | Parent marketing campaign, if any. |
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 | Bare cuid — no prefix. |
accountId |
string | Owning merchant. |
externalSource |
string | Which system mirrored the row (storlaunch). Part of the (accountId, externalSource, externalRef) unique key, so the same product ID from two sources doesn't collide. |
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 | |
tags |
string[] | Product tags synced from Storlaunch. Not used by the feed itself, but load-bearing elsewhere: a discount code with scope: "tags" resolves eligibility by looking these up on externalRef whenever the incoming cart snapshot doesn't carry per-line tags. |
brand |
string | null | |
googleProductCategory |
string | null | Per-product override. |
metadata |
object | Free-form JSON from the sync. Defaults to {}. |
archivedAt |
ISO 8601 | null | Items with archivedAt != null are excluded from the feed regardless of available or includeUnpublished. |
createdAt, updatedAt |
ISO 8601 | Most-recent-first feed ordering uses updatedAt. |
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.