Skip to main content
Some products on an ad-server source have no price the platform can sell them at. The wholesale avails & pricing module closes that gap: you upload a small, structured feed, one row per ad-server selector (a GAM ad unit, a SpringServe supply tag, a FreeWheel site section, an AdsWizz zone, …) with your percentile CPMs and monthly availability. The platform validates it, stores it with a freshness date, and uses it only for products whose own pricing is unresolved. A price already set on a product is kept.
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.
No ad-server adapter runs on Semicola yet, so no product is synced with ad-server selectors. Until one does, every preview and commit reports the selector match as unavailable (selectorDiffAvailable: false). That is not a mismatch: the check runs once products exist.

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:
  1. Download pre-filled template, then add the CPM percentiles and monthly impressions.
  2. 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).
  3. 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 same task imports a modular source’s static avails feed when it’s opened from that source. It isn’t a general pricing editor: rate cards and buyer discounts keep their own flows. The section itself shows where the source stands:
  • 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.
  1. Template (optional): GET /inventory-sources/{sourceId}/wholesale-pricing/template returns a CSV with one GLOBAL row per selector your products reference, your settlement currency, cpm, the current quarter and a dated source_version. Fill in the percentiles and impressions.
  2. Module (optional): POST /inventory-sources/{sourceId}/wholesale-pricing/module with {"minimumLineItemSpend": 5000} sets your minimum line item up front.
  3. Preview: POST /inventory-sources/{sourceId}/wholesale-pricing with csvText (or rawRows) and no confirmed. The response has acceptedRowCount, rejectedRowCount, a rejected list (row, column, code, why), advisories, and the selector-match fields.
  4. 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 check unmatchedSelectorCount rather than treating a 200 as proof the feed priced something.
The canonical feed transport works too: register 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, unmatchedSelectorCount and unmatchedSelectors (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; selectorDiffNote says so.

How pricing is used

  • The floor comes from p25; p50 and p75 are guidance. A p25 of 0 sells at open auction.
  • Prices and availability stay per country: the product carries a forecast per priced country.
  • A country whose month of avails at p75 can’t reach your minimum line item is suppressed and named in the product’s ext.wholesale_suppressed_countries.
  • A product the feed can’t price stays in your catalog with ext.wholesale_priced: false and a reason: feed_stale, no_selectors, no_matching_pricing_rows, mixed_currency, all_countries_suppressed or incompatible_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.