The module is opt-in per ad-server source: registering the feed or your first committed upload
attaches it. Modular sources use the static avails feed instead.
What you supply
One row per ad-server selector × country. Column order and header capitalization don’t matter; CSV and XLSX both work.
A selector uses either one
GLOBAL row or only country rows, never both: a mix rejects every row
of that selector with GLOBAL_COUNTRY_CONFLICT. Device columns (device_type, Device Type) are
rejected with WHOLESALE_V1_UNSUPPORTED_DEVICE_SCOPE.
Upload from the ad-server source
Open Get ready to sell, find the ad-server source, and use Import seller feed in its Wholesale price & avails section. This opens the shared Import inventory feed task for that source:- Download pre-filled template, then add the CPM percentiles and monthly impressions.
- Upload the completed CSV or XLSX, or paste the rows, and Preview. Nothing is written. You see the accepted and rejected row counts, each rejected row with its row number, column and why, any advisories, and the selector match (or why it’s unavailable).
- Commit stays disabled while any row is rejected. The commit is tied to the file you previewed on the source you chose: editing the rows or switching the source clears the preview, and a changed file is refused until you preview it again. The commit fully replaces the source’s prior feed and repeats the selector match, so a feed that prices nothing is still flagged.
- the active products that are ad-server or fixed priced, manually priced (your upload), and unresolved, with a request to upload pricing for the unresolved ones;
- “N products created, 0 visible to buyers — pricing unresolved” when every product is hidden for its price;
- the committed feed (rows, selectors, source version, when) or that none is committed yet;
- the advance warning from 7 days before the soonest row expires, and any suppressed markets;
- your Minimum line item, which you can edit there (it attaches the module).
Upload from chat
Attach the completed CSV or XLSX and say it’s a wholesale pricing upload (name the source if your storefront has more than one ad-server source; Semi lists them otherwise). Semi runs the same preview, writes nothing, and reports the accepted and rejected counts, every rejected row (row, column and why) and which selectors matched a product, or that the match is unavailable because no products have synced. Because the turn reads your file, Semi asks you to Approve before it runs the preview. Once the preview has zero rejected rows, ask Semi to commit it and Approve. The commit reads the file from where it was stored when you attached it, so approving on a later message commits exactly that file. A commit is refused while any row is rejected, and fully replaces the source’s prior feed. Use this only for the wholesale pricing template.Upload through the API
All paths are under/api/v2/storefront.
- Template (optional):
GET /inventory-sources/{sourceId}/wholesale-pricing/templatereturns a CSV with oneGLOBALrow per selector your products reference, your settlement currency,cpm, the current quarter and a datedsource_version. Fill in the percentiles and impressions. - Module (optional):
POST /inventory-sources/{sourceId}/wholesale-pricing/modulewith{"minimumLineItemSpend": 5000}sets your minimum line item up front. - Preview:
POST /inventory-sources/{sourceId}/wholesale-pricingwithcsvText(orrawRows) and noconfirmed. The response hasacceptedRowCount,rejectedRowCount, arejectedlist (row, column, code, why),advisories, and the selector-match fields. - Commit: the same request with
"confirmed": true. A commit is refused while any row is rejected. It fully replaces the source’s prior feed: a selector you leave out loses its uploaded fallback. The response repeats the selector-match fields, so checkunmatchedSelectorCountrather than treating a 200 as proof the feed priced something.
wholesale-avails-pricing v1 with
POST /inventory-sources/{sourceId}/feeds, then feed/preview (or feed/upload for a file) and
feed/commit with the signed baseline, exactly as for the static feed.
Each wholesale preview and commit carries a wholesale summary with the same fields.
Selector match
selectorDiffAvailable: true:matchedSelectorCount,unmatchedSelectorCountandunmatchedSelectors(the first 5, named). A committed feed whose selectors match nothing still succeeds, but it prices nothing.selectorDiffAvailable: false: the source has no products with selectors yet;selectorDiffNotesays so.
How pricing is used
- The floor comes from
p25;p50andp75are guidance. Ap25of0sells at open auction. - Prices and availability stay per country: the product carries a forecast per priced country.
- A country whose month of avails at
p75can’t reach your minimum line item is suppressed and named in the product’sext.wholesale_suppressed_countries. - A product the feed can’t price stays in your catalog with
ext.wholesale_priced: falseand a reason:feed_stale,no_selectors,no_matching_pricing_rows,mixed_currency,all_countries_suppressedorincompatible_pricing_option. Buyers don’t see it. - A product with its own complete price in your settlement currency is never repriced by the feed.
Readiness and freshness
Your rows’effective_end_date sets the expiry; an accepted upload is honoured for at most 365 days.
The Wholesale pricing check on the setup rail counts active ad-server products with their own
price, priced by your upload, and unresolved. From 7 days before the soonest row expires it warns
“pricing starts expiring in N days — re-upload to keep products visible”, and it lists suppressed
markets. When every product is hidden for pricing, Products ready to sell says
“N products created, 0 visible to buyers — pricing unresolved”.
Not available yet
- API push (
v1-push) for the wholesale feed, and the selector browser (browse_ad_server_selectors). - Automatic ad-server pricing and selector sync: they need an ad-server adapter.