Skip to main content
A modular source is an inventory source assembled from modules instead of one upstream sales agent. Use it when what you can sell lives in a spreadsheet or an export, and booking, trafficking and reporting happen in other systems or by hand. Buyers still see one storefront and buy through its usual media-buy flow. Every modular source starts with two built-in modules: an avails feed (the static-avails-feed:v1 profile) and a booking ledger. By default it also gets a provider-neutral manual execution module. Nothing is offered to buyers until you commit avails rows.
Modular sources need a Merchandising plan (MERCHANDISING, MERCHANDISING_DISTRIBUTION or ENTERPRISE_MERCHANDISING). On any Listing plan, or with no plan, the Modular source choice is unavailable and creating one is refused with “Custom modular sources are included with the Merchandising profile. Your plan includes Listing only.” A source you already have keeps working after a downgrade: you can still read its readiness and commit feeds.

The three surfaces

Open them from Seller setup → Inventory sources → Add a source → Modular source, from the source’s Open workspace button, from the rail’s inventory source list, or by asking Semi (“Add a modular source.”). From an agent client, call open_page with the page name; pass the page’s context in arguments ({"page": "modular_inventory_source", "arguments": {"sourceId": "sample-ctv"}}). Creating a source doesn’t ask how you book, traffic, clear creative or report: the workspace derives that per lifecycle stage from the modules attached. The completion operations behind the pages (create_feed_modular_inventory_source, preview_inventory_feed, commit_inventory_feed, get_inventory_feed_template) are page-only: the page calls them after you confirm, and feed files stay in the page, so the model never receives the bytes.

Create a source

POST /api/v2/storefront/inventory-sources/modular/feed (admins only):
Never send secrets here. The response is 201 with inventorySourceId, sourceId and the source’s readiness projection. With execution.mode upstream_system or none, no execution module is attached and the response adds a NO_EXECUTION_MODULE warning (“This source cannot finalize buyer bookings until a compatible trafficking module is attached.”). A sourceId already used on your storefront is 409; a malformed one is 400.

Check readiness

GET /api/v2/storefront/inventory-sources/{sourceId}/modular returns the runtime projection (the Modular inventory source page shows the same data). {sourceId} is the storefront-scoped ID or Semicola’s internal source id; an unknown one is 404.
  • modules[]: each module’s kind (INVENTORY_FEED, BOOKING_LEDGER, TRAFFICKING, …), status, lifecycle stages with their mode (AUTOMATED, HITL or UNSUPPORTED) and missing setup fields.
  • lifecycleSummary[]: one row per stage for INGEST_AVAILS, GET_PRODUCTS, RESERVE_AVAILS, RELEASE_BOOKING, FINALIZE_BOOKING, SYNC_CREATIVES and IMPORT_REPORTING.
  • activeAvailCount: committed rows whose endTime hasn’t passed. openWorkItemCount: the source’s open work items (each module also reports openWorkItemCountsByKind).
  • catalogMappingReadiness: three sections (Set up inventory, Make it merchandisable, Prove it), each row with its requirement, status, evidence and one next action, plus progress, requiredProgress and nextAction (null once every required row is ready). It checks your committed rows, the Property Roster, products, CPMs, your Playbook and AI Business Rules.
RESERVE_AVAILS and RELEASE_BOOKING are READY once active avails exist. With the manual execution module, FINALIZE_BOOKING and SYNC_CREATIVES are person-run (HITL) stages: READY, or HITL_PENDING while a work item of their kind is open. IMPORT_REPORTING stays NOT_DECLARED: a final-report work item records delivered impressions on the booking, but nothing imports them into delivery reporting yet.

Products

Each committed row becomes one product, avail:<availId>, priced by CPM, with the row’s formats, properties and channel. GET /api/v2/storefront/inventory-sources/{sourceId}/modular/products lists them with capacity (impressionsCapacity, heldImpressions, bookedImpressions, availableImpressions), cadent: null and active. Once a row’s endTime passes it isn’t sold: discovery skips it and a media buy for it is refused. The seller setup source card reads “Healthy · N active avails” or “Awaiting avails”.

Book capacity

The booking ledger holds capacity against one avail without overbooking.
  • POST …/modular/reservations takes productId or availId, mediaBuyId, packageId, buyerCustomerId, requestedImpressions and an optional holdMinutes. It’s idempotent per source, avail, media buy and package. Overbooking is 409 with details.availableImpressions; an ended avail is 409; a source without an active ledger module is 422.
  • POST …/modular/bookings/release releases a held booking or cancels a booked one, returning the capacity to the avail, and cancels the booking’s open work items.
  • A media buy on a modular product holds its capacity at intake (budget ÷ CPM × 1,000), and one that would overbook is refused. A buyer cancel or your rejection releases it. A feed commit never takes an avail’s capacity below what’s held or booked.

Finalize and work items

With the manual execution module (the default), POST …/modular/bookings/finalize prepares a held booking for execution: it opens three work items for your team, Book the order in your system (EXECUTE_UPSTREAM_BOOKING), Send the creative to your system (SYNC_UPSTREAM_CREATIVE) and Upload the final delivery report (UPLOAD_FINAL_REPORT). The booking stays HELD and the response says PENDING_TRAFFICKING. When you accept a buy, its held bookings are finalized for you.
  • Completing the first work item moves the booking to BOOKED; the final report moves it to DELIVERED. Each completion needs its required result (upstreamOrderId, creativeRefs, deliveredImpressions) and records it on the booking.
  • GET …/modular/work-items lists them (by default OPEN, IN_PROGRESS and BLOCKED); GET …/work-items/{workItemId} adds the event history; PATCH changes status, assignee, blocked reason or notes, but can’t complete; POST …/work-items/{workItemId}/complete completes one. Completing twice returns changed: false.
  • The Work items section of the Modular inventory source page lists them and completes them. Pending Operations groups them by media buy under manual source work.

The work queue in chat

search({ "kind": "work_item" }) reads every task waiting on you: modular source follow-ups, media-buy approvals and creative reviews. Each result’s workItemKind (modular_source, media_buy_approval, creative_review) sets its status vocabulary; follow-ups also carry requiredResultFields, and creative reviews a contentDigest. The response’s coverage says which queues were searched and how many modular sources the scan reached. A status one queue can’t have leaves that queue out and says so. To read one source’s queue, pass filter.sourceId with filter.workItemKind: "modular_source" (a sourceId alone is refused). save_work_item completes or updates one item: a follow-up with status: "COMPLETED" and its result, a media-buy approval or creative review with approved or rejected (a creative review also needs expectedContentDigest). Saving the same result again returns "action": "unchanged"; a conflicting correction is refused and the original record stays. Re-recording an approval never re-sends a forward.

Feed profiles (REST bulk transport)

Integrations send avails over REST instead of the Import inventory feed Task.
  1. Register the feed: POST …/inventory-sources/{sourceId}/feeds (or create_inventory_feed) with profileId: "static-avails-feed" and profileVersion: "v1". Registration is idempotent, the canonical key is static-avails-feed:v1, and no credential is issued: use your account API key. v1-push is accepted when the source’s avails-feed module declares availsSource.type: "api".
  2. POST …/feed/preview (JSON rawRows, csvText or jsonText) or POST …/feed/upload (multipart file) returns a PREVIEW revision (facts, diagnostics, validationStatus) and a signed previewBaseline.
  3. POST …/feed/commit with the feedId, previewBaseline and revision as returned. If another commit moved the feed since the preview, it’s 422 STALE_PREVIEW_BASELINE; preview again.
  4. With v1-push, POST …/feed/push parses, validates and commits in one call (dryRun: true validates only).
GET …/feeds and GET …/feeds/{feedId} (or get_inventory_feed_status) report the head, its transport, sync health and the latest attempt as counts, digests and diagnostic codes, never rows. Pull, schedules, provider triggers, feed credentials and signed transfers are reported as UNSUPPORTED_OPERATION.

Preview discovery for one source

When Get products is Ready, select Open read-only discovery preview in the source’s workspace, enter a representative buyer brief and select Run read-only preview. The Task shows only the products that source would return through your storefront. Each run rechecks that the source is enabled and Get products is still Ready, and stops otherwise. The preview is recorded for later review; it doesn’t create a media buy, change live products or source setup, or turn on transacting.

Not available yet

  • The wholesale-avails-pricing feed profile, module configuration or credentials, importing final reports into delivery reporting, and failed-order cleanups for ad-server sources.
  • A no-spend source test campaign, and Semi drafting rows from an uploaded document.
  • Deleting or archiving a single committed row.