/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
guaranteedornon_guaranteeddelivery 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_kindplusparams).
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 whenorigin.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.
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_composecreated or replaced the skeleton;hit_hydratedreused a truth-gated narrative for the same request and deterministic decision basis;hit_customizedreused only source-authenticated catalog or Material claims whose composite references and decision basis still match; andbypassedcomposed 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.
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:
schemaVersionisrfp-composer-execution-v1;modeismodel,cache, orfallback;outcomeiscomposedfor a live model result,cache_hitfor reuse, or a bounded fallback category. Dutch responses uselocalized_deterministicand do not invoke or reuse a narrative model;providerandmodelidentify the live composer only whenmodeismodel; both arenullfor cache and fallback.attemptedsays whether a model invocation was attempted for this turn;attemptProviderandattemptModelidentify that invocation when the provider reported a bounded identity. A rejected or ungrounded paid result therefore remainsmode: "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
Callsave_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.
rfpId, turnId, responseState, and a
safe get call. Poll the exact turn:
queued → processing → ready | passed | needs_clarification | failed
readycontains an immutableproposal-semantic-document-v1response with products, prices, budget allocations, canonical formats, seller claims and their source citations, visual reuse decisions, and composition receipts.passedis a deliberate, evaluated decision not to propose, not an absent response. For an enrolled Seller Account, a pass withstrategy.passReasonor a planner-driven catalog-gap pass contains an immutableseller-rfp-pass-v2response. An explicit reasonless pass, or any unenrolled explicit pass even when it has a reason, can containseller-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 explicitstrategy.posture: "pass"recordsexplicit_strategy_postureas 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_clarificationalso 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.failedcontains a stable reason and safe retry guidance. Do not treat it as a successful response or keep polling it as though it were processing.
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 samesave_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:
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_feedbackon the turn; append_turna revision from that feedback, opening a new immutable turn — the sent turn is never edited in place;request_representationforseller_response_json,html,pdf, orpptxas either a seller preview or buyer delivery, and watch it move throughqueued → processing → ready | failed; andcancel_representationwhile it is stillqueued.
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
Usesearch({"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.