Skip to main content
The v3 seller RFP workflow turns one request into an immutable, auditable request-and-response turn. It works through /mcp/v3; no Interchange page is required. The authenticated account must be a Seller Account. An RFP is the seller’s durable commercial opportunity or exercise. An RFP turn is one immutable request and its response. The response is the seller proposal artifact inside that turn; clients do not save a second seller-side Proposal object.

Before creating an RFP

Response composition uses the Seller Account’s own active Storefront products. At least one product must have all of these catalog facts:
  • a name and at least one priced option declaring a guaranteed or non_guaranteed delivery type;
  • at least one channel, including a requested channel when the RFP specifies one, with a canonical format whose kind can execute in that channel;
  • an explicit positive fixed or floor price with pricing-option ID, pricing model, ISO currency, and a compatible delivery type; and
  • at least one canonical URL-free creative format (format_kind plus params).
Missing product, price, or format facts are never replaced with an empty offer. When no complete priced product exists, the turn finishes as needs_clarification and identifies the missing catalog facts instead. When a complete product exists but cannot execute the buyer’s declared channel or format, the turn finishes ready with a bounded counter_pitch: the decision keeps the literal request separate from the substitute and lists every gap. Composition scans at most 1,000 products and applies declared field, collection, and artifact-size limits. Request prose may contain tabs and line breaks. Unicode bidirectional controls, C1 controls, and unsafe C0 controls are rejected in prose. Leading/trailing Unicode whitespace (including TAB/LF/CR) is trimmed from structural identifiers and field names; the trimmed value must contain a visible character and no C0/C1 controls, bidi controls, or line/paragraph separators. A larger catalog or response also ends in explicit clarification; Interchange never returns a partial proposal as ready. The deterministic planner treats a channel label and a usable execution as separate facts. For example, a product labelled video does not satisfy a video request when its selected format options contain only image; it needs video_hosted or video_vast. Explicit formatKinds are matched against the same format kind that makes the declared channel executable, so unrelated formats on one product cannot be combined into a false match. New typed requests use origin.channels, origin.preset.channels, or request.dimensions.channels. Bounded legacy origin.preset.channel and request.dimensions.channel values remain accepted and normalized for compatibility; origin.channel is not part of the public save_rfp input schema. These structured declarations populate decision.requested.channels. A channel mentioned in brief prose does not add or override buyer intent. Only structured dimensions.productCount or constraints.productCount and dimensions.planRoles or constraints.planRoles populate certified buyer cardinality and roles; prose such as “one-line” or “anchor store” is not published as authored plan intent. The planner does not certify buyer-authored strategic roles from channel and format compatibility. It records structured planRoles as requested intent. When the seller is otherwise responding with a proposal, those roles add a requested_role_evidence_unavailable limitation and yield a counter_pitch, even when the catalog otherwise supports the request; an explicit seller pass remains a pass. Supported execution roles and rationales are separate seller-derived facts. Measurement intent is certified only from request.constraints.measurementRequirements (or its snake-case alias), never inferred from the brief. Declared reporting_capabilities.available_metrics can verify a supported measurement; missing or incomplete reporting evidence remains an explicit limitation. If the catalog cannot support the literal count, the response also names that limitation and clearly labels the supported product mix as a substitute. When both camel-case and snake-case list aliases are supplied, format kinds, plan roles, and measurement requirements are merged and deduplicated. Product count aliases are scalar synonyms: every supplied count across dimensions and constraints must match. Conflicting counts are treated as invalid cardinality intent and produce clarification rather than silently choosing one. The channel-to-format compatibility vocabulary is closed and deterministic: Supported structured measurement requirements and their accepted catalog metrics are: When multiple measurement requirements are supplied, every distinct requirement is preserved and verified independently; no requirement subsumes another. A ready semantic document exposes this result at decision: disposition is pitch or counter_pitch; requested records channels, format kinds, product count, and buyer-authored roles; supported.roles binds each verified execution role to a product and readable rationale without claiming that catalog-derived roles are buyer-authored; and limitations lists the exact gaps. Commercial allocations also carry that role and rationale. A single-product allocation explains why the only supported product receives 100%, rather than presenting an unexplained default split. Missing budget or request-required flight, audience, or creative inputs produces a bounded counter or clarification, never an apparently complete approval request. When the buyer requests an exact demographic age range that the seller’s catalog cannot execute, the decision is likewise a counter_pitch: it keeps the available proposal, names that age targeting was not applied, and lists the exact supported ranges (or states that none are declared). In semantic_document_json, commercial.pricing.currency is nullable only for an explicit product-free pass without a buyer budget; totalBudget is omitted in that case. buyer_proposal_json emits no budget guidance when no total is available. Its published Proposal shape and field set are unchanged: allocation productId contains the source-qualified wh: wholesale product identifier and pricingOptionId contains the corresponding round-trippable whpo: wholesale pricing-option identifier. Buyer integrations must preserve both values exactly when calling create_media_buy. The composer also reads seller-approved Material-owned positioning and case-study candidates from each Material’s current source revision. A candidate is eligible only after an explicit save_material accept or correct decision and only when its market, locale, channel, format, property, and visibility scope matches the RFP. Advertiser-confidential or otherwise advertiser-scoped candidates remain excluded until their eligibility can be bound to server-owned authorization rather than an advertiser label supplied in the RFP. Its exact Material revision and evidence locator are carried into the semantic document. Pending, rejected, withdrawn, stale-revision, mismatched, or missing-digest candidates are not supplied to the composer. Those evidence rules also apply when live narrative generation is unavailable. For a pitch or counter, the deterministic response cites eligible accepted positioning directly in its thesis and retains an eligible accepted case study in its proof section. Both carry the same Material ID, source revision, candidate ID, locator, and content digest. An explicit pass remains product-free and uses its validated seller-authored policy reason instead of repurposing catalog or Material evidence as the seller’s decision basis. If no eligible Material positioning or proof story remains after relevance, confidentiality, and required provenance checks, the semantic document records a bounded warning for each missing class instead of silently implying that seller proof was considered or available. Excluded evidence and its contents are never named by that warning. Deterministic proposal copy is localized from request.constraints.locale. nl-NL produces Dutch headings, proposal language metadata, commercial phrases, pass explanations, and next-step prompts; en-US and an omitted locale produce English. Other valid BCP-47 locales currently fall back to English, and the semantic document reports en-US as the language actually emitted instead of labelling English copy as the requested language. Currency, unit prices, allocation percentages, and rendered tables use that emitted locale. Buyer-facing next-step prose and labels describe the concrete decision needed; the machine-readable CTA target retains its established v3 operation reference for agents. A representation uses the immutable semantic document’s language; requesting another supported language is rejected because rendering does not translate the already-composed response. Append a turn with the new request locale when a translated response is needed. Previously stored story-first-v3 responses remain readable: JSON preserves the exact immutable document and its original locale, while human artifacts render that recipe’s legacy English copy with truthful en-US language metadata. request.budget is an exact currency amount, not an arbitrary decimal. Its currency must match the uppercase ISO 4217 contract published by the save_rfp input schema and be supported by its runtime refinement; amount must remain between the schema’s published minimum and maximum. Decimal precision follows that currency’s minor unit: for example USD accepts two decimal places, BHD accepts three, and JPY accepts none. An amount that cannot be represented in whole minor units is rejected before the RFP is saved. An empty budget object is accepted as an omitted budget for legacy clients; a non-empty budget must include both amount and currency. Zero is accepted so an incomplete brief can be recorded, but composition returns actionable needs_clarification; it never consumes worker retries or emits zero-value allocation lines. request.flight may contain only the bounded facts currently known. A partial start or end is preserved rather than rejected, and legacy ISO datetime values are canonicalized to their YYYY-MM-DD calendar date. Composition asks for any flight fact the request explicitly marks as required instead of rejecting the turn at save time. The legacy request.constraints.mustInclude list remains supported for compatibility. It accepts at most 16 non-empty, control-safe planning-text items, each up to 160 Unicode code points after trimming. Structural identifiers and field names use the same trimming rule: leading and trailing Unicode whitespace (including TAB/LF/CR) is trimmed, then the value must contain a visible character and no C0/C1 controls, bidi controls, or line/paragraph separators. Idempotency uses the trimmed identifier, so padded and unpadded forms identify the same save request.

Use a Quick RFP preset

Quick identifies the RFP origin; it does not change the request purpose. Its production cache is used only when origin.kind: "quick", purpose: "live", and a non-empty typed origin.preset are all present. Supported cache dimensions are buyer, advertiser, category, location, market, channels, objective, advertiser class, and budget band. Strings and channel collections are bounded by the published save_rfp schema. Existing bounded legacy preset records (including preset.id) and omitted presets remain accepted; they compose through the established lifecycle without populating or reading the production cache. Quick draft and evaluation requests are likewise accepted and bypass caching.
The cache stores a validated reusable selection/story skeleton and the truth-gated seller narrative produced by the first viable live composition. A cache hit skips both the full skeleton build and the narrative model call; it never stores buyer-authored brief prose for reuse. Every response still rescans and reloads current products, price options, product eligibility, and availability, then applies exact budget and currency facts. Audience, flight, constraints, and overrides remain recorded request context; the current planner checks required-input presence but does not claim that those values filtered inventory or overlaid catalog facts. A dependency digest binds the referenced Material revision, the complete set of current accepted Material positioning and case-study candidates, Playbook and acceptance-policy content, catalog and pricing versions and content, response recipe, model inputs, response-validation contract, and renderer contract. A changed dependency replaces the entry for that preset rather than serving stale facts. The terminal publication transaction takes a Seller Account customer-scoped dependency fence, re-reads the authoritative catalog, policy, Playbook, Product Marketing, and Material snapshot, and publishes the semantic document, receipts, and any cache update atomically. Dependency writers take the exclusive side of the same customer fence; unrelated Seller Accounts do not share a platform-wide lock. If the snapshot changed during composition, the attempt publishes neither a ready response nor a cache entry and retries from current facts. Cache hits do not change buyer-facing response shape, representation, or provenance. Certified plan intent is pre-release: its decision and role fields are not yet available to Seller Accounts. The seller-rfp-plan-intent flag remains disabled until the follow-up trigger-enforcement migration ships, so new turns retain the legacy response contract. The pre-release capability adds no entitlement, package, or billing meter. Read receipt.cacheMode on get({"kind":"rfp_turn"}):
  • miss_full_compose created or replaced the skeleton;
  • hit_hydrated reused a truth-gated narrative for the same request and deterministic decision basis;
  • hit_customized reused only source-authenticated catalog or Material claims whose composite references and decision basis still match; and
  • bypassed composed normally and names the reason, such as a non-Quick origin, any non-live purpose, a legacy or omitted preset, the 500-key Seller Account cache limit, or another request currently populating that Seller Account’s cache.
For cache hits, cachedSkeletonDigest identifies the loaded shared skeleton and currentSkeletonDigest identifies the request-specific overlay. The buyer’s requested product count is null when no cardinality was authored; seller-selected plan size remains separate. Decision claims carry evidence for the source that actually established each claim. A seller-authored pass reason cites its strategy.passReason request locator. Missing-input limitations cite the exact request path. Catalog, format, role, and measurement limitations cite the current catalog revision; the renderer never presents a generic policy or catalog citation for a reason that source did not produce. An explicit seller pass does not require a viable product, currency, or strategy.passReason. Every explicit pass records explicit_strategy_posture as immutable provenance. For an enrolled Seller Account, a supplied reason produces the v2 pass document and is recorded as its seller-authored policy basis; an explicit pass without a reason—or any explicit pass composed while the exposure flag is off or unavailable—retains the deployed v1 compatibility document. A planner-driven catalog-gap pass is a v2 decision without seller-posture provenance. Catalog or request gaps remain separate rendered limitations. presetId labels a preset as curated; normalized dimensions and the resolved response locale determine its cache key, so cached English narrative can never be selected for a Dutch response. Dutch platform narrative is deterministic and does not populate cachedNarrative. Unnamed combinations populate lazily from real demand. Interchange retains at most 500 normalized preset keys per Seller Account and does not prebuild their Cartesian product. The same turn receipt includes bounded composition evidence at receipt.composer:
  • schemaVersion is rfp-composer-execution-v1;
  • mode is model, cache, or fallback;
  • outcome is composed for a live model result, cache_hit for reuse, or a bounded fallback category. Dutch responses use localized_deterministic and do not invoke or reuse a narrative model;
  • provider and model identify the live composer only when mode is model; both are null for cache and fallback.
  • attempted says whether a model invocation was attempted for this turn; attemptProvider and attemptModel identify that invocation when the provider reported a bounded identity. A rejected or ungrounded paid result therefore remains mode: "fallback" without being mislabeled as a no-call fallback.
receipt.modelVersion continues to identify the content’s model lineage. On a cache hit it can name the model that produced the cached narrative without claiming that a model ran for the current turn; use receipt.composer.mode to distinguish those cases.

Create and poll a turn

Call save_rfp with action: "create". clientRequestId is the idempotency key: retrying the exact request returns the same RFP and turn, while reusing the key with a changed payload returns an idempotency conflict.
The save returns immediately with rfpId, turnId, responseState, and a safe get call. Poll the exact turn:
The observable lifecycle is: queued → processing → ready | passed | needs_clarification | failed
  • ready contains an immutable proposal-semantic-document-v1 response with products, prices, budget allocations, canonical formats, seller claims and their source citations, visual reuse decisions, and composition receipts.
  • passed is a deliberate, evaluated decision not to propose, not an absent response. For an enrolled Seller Account, a pass with strategy.passReason or a planner-driven catalog-gap pass contains an immutable seller-rfp-pass-v2 response. An explicit reasonless pass, or any unenrolled explicit pass even when it has a reason, can contain seller-rfp-pass-v1. Both remain readable. The response includes a product-free semantic document with what the seller heard, the grounded reason or posture-only compatibility basis for passing, and the condition for a useful new turn. Catalog and request gaps appear as separate, rendered decision limitations. An explicit strategy.posture: "pass" records explicit_strategy_posture as immutable decision provenance on both the response and turn; it never invents a catalog incompatibility or emits offer lines and terms disguised as a pass.
  • needs_clarification also contains an immutable semantic document. It contains no proposed products or budget allocations and lists the questions and catalog facts that must be resolved. Correct the product catalog or request, then append a new turn.
  • failed contains a stable reason and safe retry guidance. Do not treat it as a successful response or keep polling it as though it were processing.
Each composition attempt is lease- and generation-fenced. A reclaimed or late worker cannot overwrite the artifact committed by the current attempt.

Continue the lifecycle

save_rfp uses one closed action union. Fields from another action are rejected rather than ignored. record_outcome.result remains a nonempty free-form string so existing integrations can preserve their source vocabulary. Aggregate reporting uses the documented closed taxonomy in RFP performance; an unknown recorded value is retained but makes classified commercial metrics unavailable with unknown_outcome. The certified seller_proposal_v1 profile supports seller_response_json, buyer_proposal_json, semantic_document_json, html, pdf, and pptx. Each file is generated from the turn’s immutable document and preserves the same products, prices, budget allocations, seller claims, and source citations. Layout differs by format. HTML declares its language, title, heading order, table headers, image alternative text, and tested color contrast. PDF is paginated and embeds a deterministic subset of the certified Noto Sans font for its supported Latin, Greek, and Cyrillic glyphs. A PDF request containing a glyph outside that font, including CJK, right-to-left scripts, or emoji, fails explicitly instead of replacing the character. PPTX text remains editable; long text is continued across slides according to the fixed text-box height. Unsupported profiles, formats, glyphs, or layout sizes fail explicitly; they are never substituted or truncated. Set representation.audience to seller_preview only for a preview you review yourself. Seller previews include the seller-only Shaped by provenance line when the agent followed an endorsed pair. The default, buyer_delivery, is the required audience for any file you send to a buyer and never includes that provenance. buyer_proposal_json is always clamped to buyer_delivery. Representation states are queued, processing, ready, failed, and canceled. Temporary render failures retry automatically. A canceled or superseded attempt cannot replace a newer file. A ready representation returns its checksum, commercial digest, validation receipt, expiry, and a short-lived download URL authorized for the current Seller Account. The URL is limited to the exact stored file version, expires no later than the artifact’s retention expiry, and does not make the file public. An artifact at or past its retention expiry receives no download URL. Signing also requires more than three minutes of retention to remain, so private-storage validation cannot consume the URL’s usable lifetime. Newly rendered files return a proposal-representation-validation-v2 receipt. It binds rfpId, turnId, representationId, and the originating clientRequestId alongside the document, commercial, artifact, format, and render-option digests. Use those correlation fields to match a file to the exact request_representation call that created it. Historical proposal-representation-validation-v1 receipts remain readable as immutable legacy evidence, but they do not contain the four request-correlation fields and must not be treated as a v2 receipt with stripped fields. Both request_representation and record_feedback echo clientRequestId in their fresh and idempotent-replay responses. When an equivalent representation is reused, request_representation also returns originatingClientRequestId; it identifies the original request whose immutable validation receipt is returned, while clientRequestId identifies the current call. The receipt’s existing options record includes audience. This remains v2: render options are an extensible record, and a historical receipt without an audience is interpreted fail-closed as buyer_delivery. A visual is included only when its reuse rights, confidentiality, advertiser restrictions, expiry, file type, checksum, and allowed proposal use all permit it. The response lists every omitted or unavailable visual and the fallback used. Requests that exceed a certified limit fail explicitly; they are never returned as partial files.

Proposal Pass, the portable turn workbench

Proposal Pass is the portable MCP App a seller opens to work one RFP turn — in Murph, Claude, ChatGPT, or another conforming MCP Apps host. It is a thin client of the same save_rfp/get contract documented above: it introduces no calculation, no hidden lifecycle step, and no capability the API itself does not already offer. Open it from a v3 Seller Account with the exact immutable pair returned by save_rfp or get:
Call open_proposal_pass with that object. The launch result contains only those ids; after the fixed Proposal Pass resource opens, its app-only tools load the private turn. A missing turn, a turn belonging to another RFP or Seller Account, or an unauthorized account returns NOT_FOUND or WRONG_ACCOUNT without opening the Page. The legacy v2 Storefront form, open_proposal_pass({"runId": ...}), is unchanged and is not the v3 contract. Draft and evaluation turns can be opened deliberately by exact id for review. That does not promote them into the live Demand Inbox or include them in live RFP performance metrics; those populations remain live-only. From an open turn, Proposal Pass can:
  • record record_feedback on the turn;
  • append_turn a revision from that feedback, opening a new immutable turn — the sent turn is never edited in place;
  • request_representation for seller_response_json, html, pdf, or pptx as either a seller preview or buyer delivery, and watch it move through queued → processing → ready | failed; and
  • cancel_representation while it is still queued.
release_turn and record_outcome are deliberately not available from Proposal Pass. Both are consequential, human-facing commitments the widget does not yet have a reviewed confirmation flow for; use save_rfp directly for those two actions. Feedback and representations render in the same bounded pages the exact-turn read returns (see Search and audit below): the widget shows the default page and a “Show more” control follows feedback.next/ representations.next for the rest, so a turn with more than one page of either never hides feedback or representations past the default page. Preview and buyer-delivery download read the representation’s short-lived signed URL fresh at the moment of the request, hand it directly to the host’s own link-opening capability to open in a new browser tab, and hold it nowhere else: the URL is never written into the widget’s saved state, never rendered, never logged, and never visible to the model driving the conversation the seller is in. Every other format downloads the same way. An expired or not-yet-ready representation offers no open action. Because request_representation is idempotent per format-and-audience tuple for a given turn, a representation that has already been requested cannot be re-requested to force a fresh attempt — a failed, canceled, or expired representation stays in that terminal state.

Search and audit

Use search({"kind":"rfp"}) for opportunities and search({"kind":"rfp_turn"}) for immutable turns. RFP filters include state, origin, purpose, buyer, advertiser, category, market, channel, posture, Playbook version, cache mode, and outcome. Turn filters add RFP ID, response and evaluation state, recipe/model/judge version, and feedback status. Use get({"kind":"rfp","id":"RFP_ID"}) for the opportunity summary, latest and released turns, bounded turn history, outcome summary, and links. Use get({"kind":"rfp_turn","id":"TURN_ID"}) for the exact request, response, receipts, diagnostics, feedback, outcome, and representation status. Search results are summaries; they do not repeat the complete request or response. Feedback bodies and representation statuses remain on the authorized exact-turn read in bounded pages. Follow feedback.next and representations.next until they are null. Feedback continuations carry feedbackOffset; representation pages carry an opaque representationCursor. Pass the supplied argument back to get rather than synthesizing one. The representation cursor pins a snapshot, so rows appended while paging do not produce skips or duplicates.