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 matching 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;
  • 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 facts are never replaced with an empty offer. The turn finishes as needs_clarification and identifies the missing catalog facts instead. Composition scans at most 1,000 products and applies declared field, collection, and artifact-size limits. A larger catalog or response also ends in explicit clarification; Interchange never returns a partial proposal as ready. 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. 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.

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 a non-empty semantic response with bound products, prices, allocations, canonical formats, narrative sections, and composition receipts.
  • passed is a deliberate, evaluated decision not to propose, not an absent response. An explicit strategy.posture: "pass" records the canonical truth gate and decision provenance on the turn.
  • needs_clarification contains questions and the facts that could not 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. HTML, PDF, and PPTX renderers are not certified in this lifecycle slice. Requests for those formats return representation_format_not_certified; they do not fabricate a file or mutate the response artifact.

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.