Specification
About the specDraft 0.5v0.3 bindingAmbiguitiesSpec changelogRe-Commerce Trust Profile (RCTP) — Draft 0.5
Editor's draft, 10 November 2026: the implementation baseline
| This version | spec/rctp-draft-0.5.md |
| Previous version | Draft 0.4, 27 October 2026 |
| Implementation | carousell/rctp: v0.3.0-draft.5 implements the Starter profile of this draft (wire binding); v0.2.0-draft.4 implemented Draft 0.4 |
| Editor | Kelvin Vuong |
| Feedback | GitHub issues with the label draft-0.5 |
| Changes | Appendix C: decisions and changes |
Abstract
RCTP is a profile of existing commerce standards that lets AI agents buy and sell second-hand goods for people. A person delegates the whole trade to their agent, with their consent. Every seller and every unique item has a profile both sides can check. The marketplace's gateway enforces the seller's limits on every call and keeps a signed record. RCTP reuses the Universal Commerce Protocol (UCP) and the Agent Payments Protocol (AP2) wherever they already define a verb or noun, and defines only five things of its own: offers, Listing Terms, the enforcement decision, signed proof of who did what, and portable credentials about people and items.
Status of this document
This is the editor's draft of 10 November 2026. It incorporates the
Draft 0.5 proposal (#91),
which the editor accepted as the implementation baseline on that day: new implementations build on this draft, not on
Draft 0.4. Lazy consensus under GOVERNANCE runs until 17 November 2026; anything it changes is
applied here before release v0.3.0-draft.5 is tagged. It is still a draft, not a standard: names, fields and numbers
may change in a later draft.
It supersedes Draft 0.4. Draft 0.5 changes no mechanism that protects a trade. It removes rules that were stricter than UCP (authentication, 4.1), moves tier policy to the marketplace (8.4), and lets a seller state Listing Terms in plain language (6). The open questions raised for this draft and their resolutions are in Appendix B. Every decision since Draft 0.3, and the history of earlier drafts, is in Appendix C.
Placeholder domains use .example. Capability and extension names use dev.rctp.*, backed by the domain rctp.dev,
which the project controls.
1. Introduction
1.1 Scope
RCTP covers person-to-person trade in used goods where one or both sides act through an AI agent. It defines how a seller authorises an agent, how the seller's limits are set and enforced, how agents negotiate, how each step of a deal is proven, and how facts about people and items travel between platforms.
Out of scope: booking, quoting and tracking shipments; moving money; negotiation strategy and pricing; identity-proofing methods; transports. RCTP composes with the standards that cover them (1.4).
1.2 What RCTP gives each party
| Delegated by consent | A person hands their agent the whole trade, with their consent: what it may do, under which limits and until when (5). They can revoke it at any time (5.4). |
| Transparent profiles | Every seller and every unique item has a profile both sides can check: the seller's track record in bands (Party Credential, 11.1), and the item's passport and history (Item Passport, 11.3). |
| Guardrails, enforced | The marketplace's gateway, and every party in the ecosystem, enforces the seller's limits on every call (8) and keeps a signed record of what happened (10). |
The profile defines verbs, scopes and invariants, and records facts. Marketplaces and platforms set policy: which authentication mechanisms they accept (4.1) and which tiers they allow (8.4).
1.3 Design principles
Simplicity is the ultimate complexity. RCTP defines the minimum a marketplace must implement and operate. Categories and logistics chains extend it through open values, not new protocol. Where UCP or AP2 defines a verb or noun, RCTP uses it.
Six rules are fixed. Everything else is an open value named by reverse domain, as in UCP.
| ID | Fixed rule |
|---|---|
| FR-1 | The platform enforces the seller's terms, not the agent. |
| FR-2 | No answer means no. |
| FR-3 | The marketplace never discloses private terms to any agent. |
| FR-4 | Money moves only on signed proof. |
| FR-5 | Facts about people, never identities. |
| FR-6 | No custom cryptography. |
1.4 Relationship to UCP and AP2
Pinned to UCP release v2026-08-25 and AP2 v0.2. UCP Ask is not yet in a UCP release; RCTP follows it at UCP commit
f2626c2 (20 October 2026) until it is.
| Need | Defined by | RCTP adds |
|---|---|---|
| Agent identity and authentication | UCP: UCP-Agent, profiles at /.well-known/ucp, identity binding; RFC 9421 signatures (recommended), OAuth 2.0, API keys or mTLS |
Accountability fields in the profile; how each call authenticated, in the record (4.1) |
| Seller's permission for an agent | UCP Identity Linking: OAuth 2.0 at the marketplace, scopes {capability}:{scope} |
RCTP scopes, tiers, key binding (5) |
| Buyer questions | UCP Ask | Answers from structured facts only (8.3) |
| Delivery method, carrier, windows, cost | UCP Fulfillment (open method types) | Nothing |
| When payment is due | UCP Payment Terms | Nothing |
| Paying into escrow | AP2 Payment Mandate: payment.allowed_payees |
Nothing |
| Checkout, order, shipping and money events | UCP Checkout and Order: fulfillment events and adjustments | Who attests each event (10) |
| Webhooks | UCP signed webhooks (Standard Webhooks headers, RFC 9421) | Nothing |
| Transports | UCP REST and MCP bindings | RCTP verbs as REST routes and MCP tools (12) |
| Price negotiation | Not defined; UCP discussion #502 is open | dev.rctp.deal (7) |
| Seller limits, enforcement, signed proof, people and item facts | Not defined | Sections 6, 8, 10, 11 |
1.5 Conventions
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY and
OPTIONAL are to be read as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.
Numbered rules are normative; tables define fields and values; examples and notes are informative. Values in code are
registered (16) unless marked as examples.
2. Terminology
| Term | Meaning |
|---|---|
| Agent Grant | The seller's permission for one agent to act for them on one marketplace (5) |
| Listing Terms | The seller's limits for one listing, confirmed by the seller and enforced by the platform (6) |
| Proposed terms | Terms values a seller agent carries from what the seller told it; nothing until the seller confirms them (6.1) |
| Floor | The lowest price the seller accepts; interfaces may call it the "lowest price" (6.2) |
| Term set | The terms a category takes, their types and limits (6.2) |
| Deal | A trade a buyer and a seller agree through offers, from the first offer to the release of funds (7, 10) |
| Deal Terms | What an offer proposes and an acceptance fixes: price, fulfillment option, payment term (7.2) |
| Negotiation | The offers and counters between one buyer agent and one listing, linked by negotiation_id (7.2) |
| Reservation | The hold an accepted offer places on its listing until its checkout is redeemed or expires (7.4) |
| Enforcement point | The marketplace gateway that decides every agent call (8) |
| Tier | auto (act), ask (act only after the seller approves) or forbid (never act) |
| Authentication mechanism | How a call authenticated: http_signature, oauth, api_key or mtls (4.1) |
| Holder of funds | The party that holds the buyer's payment: the AP2 payee, either the marketplace or a licensed payment partner |
| Carrier, Custodian | A party that moves or holds an item, and signs what it observed |
| Attester | A party that signs facts about one item, such as a registry, inspector or notary |
| Issuer | A party that signs portable credentials, usually a marketplace |
| Identity issuer | A public e-ID service or licensed eKYC provider that signs identity facts (11.2) |
| First-hand, relayed | Whether the signer observed the event, or passes on a named partner's report |
| Deal record | The hash-chained, signed entries for one deal (10) |
| Trusted Surface | A screen the person controls where consent and approvals are given, as in AP2 |
| Band | A coarse range that replaces a raw number, such as 50-200 trades |
| Pairwise pseudonym | An identifier unique to one relationship or one deal |
| Verified custody lane | A deal in which a Custodian checks the item and payment is released when the buyer accepts it (11.4) |
3. Roles
RCTP names roles, never companies. One organisation MAY play several roles; each role's duties apply in full.
| Role | MUST | MUST NOT |
|---|---|---|
| Marketplace | Issue Agent Grants; hold Listing Terms; run the enforcement point; write the deal record | Disclose private terms to any agent; store a grant or terms the seller did not confirm |
| Seller agent | Act within its grant; answer from structured facts; propose only terms the seller stated | Act outside its granted verbs |
| Buyer agent | Act within the buyer's AP2 mandates; default to protected payment (8.6) | Pay off-platform or pass payment links without an explicit ask |
| Holder of funds | Sign funds_held, released and refunds; release only on the deal's release condition |
Release before the condition holds |
| Carrier, Custodian | Sign only what it observed; publish liability terms | Attest outside its observation |
| Issuer, Identity issuer, Attester | Sign only what it verified; publish methodology and status lists | Learn which verifier checked which credential |
| Approval surface | Show the exact action and amount; return the seller's own signature (9) | Approve on timeout; sign with an operator key |
4. Common rules
4.1 Agent identity and authentication
- Every agent request to an enforcement point MUST authenticate by a mechanism UCP allows: HTTP Message Signatures per
UCP Message Signatures (RFC 9421, ES256 baseline), which are RECOMMENDED; an OAuth 2.0 access token; an API key; or
mTLS. The marketplace chooses which mechanisms it accepts. The request carries
UCP-Agentnaming the operator's UCP profile; a signed request MUST. When a call authenticated by OAuth carries noUCP-Agent, the enforcement point MAY use the profile it registered for the OAuth client; when it carries one, the two MUST match. Either way, the enforcement point MUST confirm that the authenticated principal may act for that profile (UCP identity binding). - The operator's profile MUST publish its signing keys in
keys[]when its agents sign, and MUST add adev.rctp.operatorentry withlegal_entity,jurisdiction,incident_contactandincident_sla_hours. It MAY addcertifications,data_residencyandkey_custody. - An enforcement point MUST refuse a call whose profile cannot be resolved, whose operator it has not onboarded, or
whose
UCP-Agentdoes not match the profile registered for its authenticated client, withoperator_unknown. - An operator whose agents act for sellers MUST NOT export buyer data, contact buyers outside the marketplace, or use RCTP data for advertising.
- An agent without a UCP profile of its own, such as a plugin on an agent host, uses the profile its developer publishes as operator. A marketplace MAY instead host a profile for each OAuth client it registers. Either way, the profile names an operator accountable under rule 2. An operator without a profile of its own accepts the duties of rules 2 and 4 in its OAuth client registration with the marketplace. The agent host that runs a plugin, such as its model provider or IDE, sees the conversation, buyer data included, and is not bound by rule 4. That is the real difference from an operator whose agents sign; a marketplace weighs it when it sets tiers for OAuth clients (8.4).
- Every deal record entry the enforcement point writes for an agent call, decisions and refusals included, MUST carry
authenticated_by, the mechanism the call used, andagent_profile, the profile the call acted for (10.1). An entry for an OAuth or API-key call MUST also carryclient_id, the client that authenticated. Whether a token was key-bound follows from the mechanism (5.3). - What each mechanism may do is the marketplace's policy (8.4). Without a signature, the record proves which client acted for which profile, not the content of each request; a marketplace weighs that when it sets tiers. The controls for bearer tokens are short-lived access tokens, refresh-token rotation and the live grant check on every call (5.3(2)). DPoP (RFC 9449) is a possible middle step for clients that can hold a key but cannot sign every request; it waits for a pinned library (INV-8).
4.2 Portable credentials
- Party Credentials, identity facts, Item Passport attestations and deal records are SD-JWT VCs (RFC 9901,
draft-ietf-oauth-sd-jwt-vc) with
typdc+sd-jwt, carryingvct,iss,iat,exp,cnfandstatus. - Every credential MUST be bound to a holder key through
cnf. A presentation MUST include a key-binding JWT whoseaudis the verifier and whosenoncethe verifier supplied. - Holder keys SHOULD be hardware-backed and not exportable.
4.3 Status lists
- Credential revocation MUST use the Token Status List. Each list MUST be shared by many credentials; a list per holder or per credential is forbidden.
- Verifiers SHOULD fetch whole lists and cache them for their stated lifetime.
- Agent Grants do not use status lists; they are checked live (5.4).
4.4 Bands
Numeric facts about people MUST be expressed as registered bands, never raw numbers.
4.5 Names, versions and open values
- Capability names use
dev.rctp.<name>. Credential types end in a version number; a breaking change increments it. - Method types, event types, adjustment types, attestation types and term names are open values. A party MAY add a value under its own reverse domain; parties that do not recognise it MUST treat it as informative and MUST NOT let it change a decision.
- The capability version of this draft is
2026-11-draft5. A deployment MAY keep serving2026-11-draft4beside it for at most 90 days after it starts serving2026-11-draft5.
5. Agent Grant
An Agent Grant is a UCP Identity Linking grant, issued by the marketplace, with RCTP scopes. It is bound to one agent key when the agent signs (5.3). It carries no prices and no listings.
5.1 Grant record
| Field | Content |
|---|---|
grant_id |
grt_ followed by a unique identifier |
seller |
Pairwise pseudonym of the seller for this marketplace and operator |
operator, agent_id |
The operator's UCP profile URL and agent identifier |
cnf.jkt |
RFC 7638 thumbprint of the bound agent key, when the grant is key-bound (5.3) |
scopes |
Granted RCTP scopes (8.4) |
tiers |
The seller's per-scope tier, within the tiers the marketplace allows for each scope (8.4) |
consent |
Consent screen version, time and account assurance; kept by the marketplace |
iat, exp, status |
Issue and expiry times; active, revoked or expired |
5.2 Issuance
The marketplace MUST issue a grant through UCP Identity Linking (OAuth 2.0 authorization code with PKCE) only after the seller confirms, on a Trusted Surface, the agent, its operator, each verb with its tier, and the expiry.
The marketplace's profile declares each RCTP scope (8.4), as UCP declares every scope, in the
config.scopesof itsdev.ucp.common.identity_linkingentry, withdefault_tierandallowed_tiersin the scope's policy. The seller's chosen tiers travel as RFC 9396 authorization details of typedev.rctp.agent_grant.The consent screen MUST NOT ask for prices. Prices belong to Listing Terms.
A grant MUST expire at most 90 days after it is issued; the agent renews it by refresh-token rotation. A marketplace MAY expire a grant earlier when it is unused.
5.3 Use on every call
- The agent sends the grant's access token in
Authorization. When the request is signed, the token MUST be sender-constrained withcnf.jkt(RFC 7800), and the request signature MUST coverauthorization. When it is not signed, the token is the request's OAuth credential. - The enforcement point MUST look the grant up on every call, whatever the authentication mechanism and bearer tokens
included, and check that the grant is active and unexpired and that
the verb's scope is granted. When the token is bound, it MUST also check that the signing key's thumbprint equals
cnf.jkt.
5.4 Revocation
- The seller can revoke a grant at any time; operators can revoke their own tokens (RFC 7009).
- A revoked or expired grant MUST fail the next call, whatever its authentication mechanism.
6. Listing Terms
Listing Terms are the seller's negotiating envelope for one listing. The seller states them to their agent in plain language, such as "list it at 500, don't go below 400", and confirms them once on a Trusted Surface. The platform stores what the seller confirmed and enforces it on every call. Everything between the floor and the list price is the agents' to negotiate; the marketplace never sells below the floor, whatever anyone says to an agent.
6.1 Setting terms
- The seller agent calls
listing.createwith the item and its public list price. It MAY include proposed terms: the values the seller told it, the floor included. - Terms are confirmed on a Trusted Surface (the marketplace's hosted page or app). It shows the listing and a form built from the category's term set, pre-filled with the proposed terms and the marketplace's category defaults (6.2), all editable. The seller confirms with their passkey. This is the expected flow. A marketplace MAY ignore proposed terms and ask the seller for the floor directly; that is its policy.
- The approval challenge MUST bind the listing hash and the
terms_commitment(6.4). The platform MUST store exactly the terms the seller confirmed. Proposed terms are not terms: the enforcement point MUST NOT store or enforce them until the seller confirms them, and MUST discard them if the seller declines. - The seller agent learns the outcome, the term set ID and
terms_commitment. The marketplace MUST NOT return the private values to any agent, including the seller agent that proposed them. - When the marketplace allows
listing.createatauto(8.4), the listing is created without terms, and stays closed to agent negotiation until the seller confirms terms throughlisting.set_terms(6.5). - A listing a seller creates by hand in the marketplace app takes the same form.
6.2 Term sets
A term set, such as goods.small/1, defines the terms a category takes. A listing records the term set version it was
approved under. Platforms MUST advertise the term sets they enforce in the config of dev.rctp.listing in their UCP
profile.
- The minimum Listing Terms are two prices: the public list price, already on the listing, and the private
floor. Every term set MUST includefloorandavailable_until; every other term is optional. requiredin a term set means the term has a value when the seller confirms, not that the seller types it. The marketplace fills each public term from its category defaults,available_untilincluded, and the seller changes them only if they want to.
| Term | Type | Visibility | Enforced at |
|---|---|---|---|
floor |
money | private | offer.create, offer.counter, offer.accept, listing.reprice |
available_until |
date-time | public | Every offer; after it the listing is closed |
fulfillment_options |
UCP fulfillment method types and options | public | offer.create (7.2) |
payment_terms |
UCP payment terms | public | offer.create (7.2) |
release_condition |
buyer_accepted, handover_inspection_passed |
public | The holder of funds (10.3) |
deliver_within_days |
integer | public | Refunds (10.3) |
counterparty_requirements |
minimum bands and identity assurance | public | offer.create |
deposit_pct, inspection_before_deposit |
percent, boolean | public | deal.accept_deposit |
required_observation |
first_hand or relayed, per checkpoint |
public | Release and refund (10.3) |
Constraints come from a closed set, so every platform checks them the same way: min, max, gt_term, lt_term,
lte_list_price, min_ratio_of_list_price. Terms that break a constraint MUST be refused with terms_invalid before
the seller can approve them. Which terms a term set includes is the marketplace's choice for that category.
6.3 Changing terms
listing.set_termschanges terms. It has no tier choice: it always opens the seller's confirmation and is answeredask(8.4). It MAY carry proposed terms, which are handled as in 6.1.listing.repricechanges the public price; a price below the floor MUST be refused withbelow_floor.- Open offers are re-evaluated against new terms the next time a party acts on them. Accepted offers are not (7.4).
6.4 Keeping private terms private
- Private terms, proposed or confirmed, MUST NOT appear in listing data, offers, decisions, approval request bodies, webhooks, partner-readable logs, deal records or any other response to an agent. They appear only on the seller's Trusted Surface.
- Deal records carry
terms_commitment: SHA-256 over the RFC 8785 canonical JSON of{salt, terms}, with a 128-bit salt kept by the platform. - The platform discloses private terms only to the seller, and to an arbiter in a dispute the seller is party to. No setting lets the marketplace send them to an agent.
- A seller may tell their own agent the floor; that is the seller's choice, and it is how proposed terms work (6.1). An agent that knows the floor can still be talked into revealing it. The guarantee does not depend on the agent: no sale happens below the floor (8). A marketplace that wants the floor kept from every agent asks the seller for it directly (6.1(2)).
6.5 Listings without terms
A listing with no approved terms is not open to agent negotiation. Offers on it reach the seller as ordinary marketplace
offers; a seller agent acting on it is refused not_in_scope.
7. Offers and Deal Terms
7.1 Offer capability
RCTP defines two UCP capabilities. Both are standalone, not extensions of dev.ucp.shopping.checkout.
| Capability | Covers | Profile config |
|---|---|---|
dev.rctp.listing |
Listings and their Listing Terms (6) | Term sets enforced (6.2) |
dev.rctp.deal |
A negotiated trade between a buyer and a seller: offers, acceptance and the checkout it produces, and the deal record (7, 10) | Offer expiry default and maximum (7.3); offer budget (8.5) |
A deal is a trade both parties agree to, not a promotion or a discount. An offer is its own resource, formed before any
checkout exists; accepting one produces a UCP checkout (7.4). Payment, shipping and order events stay with UCP Checkout
and Order; dev.rctp.deal adds who attests them (10). Offer operations: create, counter, accept, decline, get.
An RCTP offer is not UCP's Offer extension, which is a merchant-proposed change to a cart.
7.2 Deal Terms
- An offer carries Deal Terms: a price, one UCP fulfillment option and one UCP payment term, each chosen from those the listing's terms allow.
- A choice the listing does not allow MUST be refused with
terms_invalid. - Booking, quotes, labels and tracking stay with UCP Fulfillment and the carrier.
- Every offer and counter carries
offer_id,negotiation_idandexpires_at(7.3). The enforcement point assignsnegotiation_idto a buyer agent's first offer on a listing; every later offer and counter between that buyer agent and that listing carries the same value. It is opaque and MUST NOT be derived from any identifier of the buyer. - An offer that answers a counter, or replaces the buyer agent's own open offer, MUST name it in
supersedes. The enforcement point sets the named offer tosuperseded. Asupersedesthat names an offer outside the negotiation MUST be refused withterms_invalid.
7.3 Offer states and who accepts
States: open → countered or awaiting_approval → accepted, declined, expired, superseded or refused.
An offer is awaiting_approval while an ask on it waits for the seller.
- Only the seller side accepts, under
offer.accept. A counter is an invitation, not a commitment; a buyer agrees to it byoffer.createat the counter's Deal Terms. - The party that creates an offer or counter sets its
expires_at; if it is omitted, the enforcement point sets the default it publishes in theconfigofdev.rctp.deal. The enforcement point MUST capexpires_atat the listing'savailable_untiland at the maximum it publishes. - At
expires_atan offer that is not accepted becomesexpired; acting on it MUST be refused withoffer_expired. - An
askraised on an offer expires no later than the offer. When it times out, the offer becomesexpired. - Seller counter-offers are the marketplace's option. A marketplace that does not support them leaves
dev.rctp.deal:offer_counterout of itsconfig.scopes, and agents learn that from discovery. Floor defence alone is complete: an offer below the floor is refused; at or above it, the offer goes to the seller or their agent to accept or decline.
7.4 Acceptance, reservation and checkout binding
- An accepted offer MUST become a business-signed checkout bound to the listing hash, offer ID, Deal Terms, buyer agent
key thumbprint, expiry and a single-use nonce. A second use MUST fail with
offer_replayed. - Accepting an offer reserves the listing for that checkout. While the reservation holds, the enforcement point MUST
refuse every other
offer.accepton the listing withlisting_reserved, and MUST NOT create a second checkout for it. Buyer agents MAY still make offers; they stayopenuntil they expire. - The reservation ends when the checkout is redeemed, which closes the listing, or when the checkout expires, which reopens the listing to offers that have not expired.
- Once an offer is accepted, its Deal Terms are fixed. A later
listing.set_termsorlisting.repriceMUST NOT change them or end the checkout. - Only the seller, on a Trusted Surface, can withdraw a reserved listing. Redeeming its checkout then fails with
not_in_scope, and the withdrawal is written to the deal record.
7.5 Buyer authority and payment
- An enforcement point MUST accept a valid AP2 Checkout Mandate as proof that a buyer agent may offer, and MUST refuse
an offer outside its constraints with
buyer_authority_exceeded. - The buyer's AP2 Payment Mandate SHOULD constrain the payee with
payment.allowed_payeesto the deal's holder of funds. - Agents MUST NOT pass payment links or payment instructions between the parties.
8. Enforcement decisions
The enforcement point, not the agent, decides every action.
8.1 Check order
| Step | Check | Refusal |
|---|---|---|
| 1 | Authentication and operator | 401 signature_invalid, grant_invalid, operator_unknown |
| 2 | Grant: known, active, scope granted; key-bound when signed (seller side) | 401 grant_invalid; 403 grant_revoked, grant_expired, action_forbidden |
| 3 | Listing: the grant's seller's, has terms, open; for offer.accept, not reserved; offer not expired |
403 not_in_scope; 409 listing_reserved, offer_expired |
| 4 | Buyer authority and counterparty_requirements (buyer side) |
403 buyer_authority_exceeded, counterparty_requirements_unmet |
| 5 | Terms: floor, Deal Terms, offer budget | 403 below_floor, 422 terms_invalid; 429 offer_budget_exhausted |
| 6 | Tier: the grant's tier or the marketplace's default | 200 auto; 202 ask |
An unauthenticated request is refused at step 1: signature_invalid where the marketplace requires signatures,
otherwise grant_invalid for a seller verb and operator_unknown for a buyer verb. Every decision, including each
refusal, appends a deal record entry (10) carrying authenticated_by and agent_profile (4.1(6)).
8.2 Decision header
The decision travels in an RFC 9651 Structured Field: a Token (auto, ask, forbid) with parameters.
| Decision | HTTP | Parameters |
|---|---|---|
auto |
200, 201 | none |
ask |
202 | approval_id, expires_in, on_timeout=deny; the body carries the approval request (9) |
forbid |
401, 403, 409, 422 | action, reason |
forbid |
429 | reason=offer_budget_exhausted, retry_after |
A refusal for a low offer MUST use
below_floorand carries no private term: it MUST NOT state the floor or any value derived from it. An agent that wants a reference price reads the public list price on the listing.No agent input, prompt or listing text can change a tier, a term or a grant.
The header is named
RCTP-Decision.
8.3 Decisions by verb
| Verb | By | Below the floor | Otherwise |
|---|---|---|---|
offer.create |
Buyer agent | 403 below_floor; counts against the budget |
201, delivered to the seller agent |
offer.counter |
Seller agent, where offered (7.3(5)) | 403 below_floor |
Tier |
offer.counter |
Buyer agent | Treated as offer.create at the counter amount |
|
offer.accept |
Seller agent | 403 below_floor |
Tier |
offer.accept |
Buyer agent | 403 action_forbidden |
|
listing.reprice |
Seller agent | 403 below_floor |
Tier |
listing.answer_buyer |
Seller agent | Tier; answered through UCP Ask from structured listing and passport facts only |
8.4 Verbs, tiers and scopes
Every seller verb belongs to one of RCTP's two capabilities (7.1), and its scope is {capability}:{scope} as in UCP
Identity Linking. A verb's prefix names its capability: listing.x is dev.rctp.listing:x, deal.x is
dev.rctp.deal:x, and offer.x is dev.rctp.deal:offer_x. Fixed forbid verbs have no scope, so no grant can contain
them.
- The Default and Allowed columns are reference defaults, like the reference offer budget (8.5). A marketplace
declares its own
default_tierandallowed_tiersfor each scope it offers, in theconfig.scopesof itsdev.ucp.common.identity_linkingentry (5.2). It MAY vary them by authentication mechanism (4.1). It leaves out a scope it does not offer. - The seller chooses a tier within what the marketplace allows, at consent and later.
- Three things are fixed, because they are mechanics, not policy:
listing.set_termsalways opens the seller's confirmation (ask); only the seller confirms terms (INV-5), so a listing created atautohas no terms until the seller confirms them (6.1(5)); anddeal.sign_contractandaccount.updateare alwaysforbid(INV-3).
| Verb | Scope | Default | Allowed | Notes |
|---|---|---|---|---|
listing.create |
dev.rctp.listing:create |
ask |
ask, auto |
At auto, created without terms until the seller confirms them |
listing.set_terms |
dev.rctp.listing:set_terms |
ask |
ask |
Fixed: always opens the seller's confirmation |
listing.reprice |
dev.rctp.listing:reprice |
auto |
auto, ask |
Never below the floor |
listing.answer_buyer |
dev.rctp.listing:answer_buyer |
auto |
auto, ask |
|
offer.counter |
dev.rctp.deal:offer_counter |
auto |
auto, ask |
Never below the floor; optional for a marketplace (7.3(5)) |
offer.accept |
dev.rctp.deal:offer_accept |
ask |
ask, auto |
Never below the floor |
deal.commit_to_ship |
dev.rctp.deal:commit_to_ship |
ask |
ask, auto |
|
deal.disclose_passport |
dev.rctp.deal:disclose_passport |
ask |
ask, auto |
Discloses the full Item Passport report (11.3) |
deal.accept_deposit |
dev.rctp.deal:accept_deposit |
ask |
ask |
|
deal.sign_contract |
none | forbid |
forbid |
Fixed |
account.update |
none | forbid |
forbid |
Fixed |
offer.create is a buyer verb: it is checked against the terms and has no tier. Buyer verbs need nothing
RCTP-specific beyond authentication (4.1): checkout and payment authority come from UCP and AP2, or another payment
protocol, which carry their own proof.
8.5 Offer budget
- The enforcement point MUST count below-floor offers per buyer agent per listing, with a ceiling per buyer operator
per listing. It publishes both numbers in the
configofdev.rctp.deal. - Past either limit, every offer from that agent or operator on that listing gets 429 until the window frees a slot, so 201-versus-429 cannot reveal the floor.
8.6 Buyer agent defaults
- A buyer agent MUST, by default, pay for second-hand goods only into protected payment: the verified custody lane, or a holder of funds with a release condition.
- A buyer agent MUST NOT pay a counterparty directly, or move a conversation to an outside channel, when a protected
path exists, unless its principal approves an explicit
askstating the purchase is unprotected. - Before payment, a buyer agent MUST tell its principal which item facts are attested and by whom.
- An enforcement point MUST NOT refuse a buyer only for
insufficient-historyor zero trades when the deal uses the verified custody lane. - A principal MAY change these defaults only on a Trusted Surface.
9. Approvals
A partner's app can show an approval, but only a key the seller controls can sign it.
9.1 Levels
| Level | Shown in | Signed with | Allowed when |
|---|---|---|---|
marketplace_app |
The marketplace's app | The seller's marketplace passkey | Any amount |
hosted_embedded |
A marketplace-hosted panel opened in the partner's app | The seller's marketplace passkey | Any amount; recommended for partners |
certified_operator |
The partner's own interface | A hardware-backed approval key on the seller's device | Certified operators only, below the grant's cap |
9.2 Request and response
- The request travels in the 202 body:
approval_id, action, listing hash, amount, counterparty bands,display,expires_at, a one-timechallengeand the allowed levels. The challenge is SHA-256 over the RFC 8785 canonical JSON of RFC 9396 authorization details of typedev.rctp.approval. displayMUST NOT contain private terms. A listing approval shows the terms form, pre-filled as in 6.1, on the seller's Trusted Surface only, and its challenge bindsterms_commitment.- The response carries
approval_id,decision, the signature over the challenge (a WebAuthn assertion at the first two levels), the level, and the SHA-256 of the text shown.
9.3 Rules
- Before acting, the enforcement point MUST verify that the signature is the seller's registered key, answers this
challenge, is within
expires_at, is first used, and uses an allowed level; otherwiseapproval_invalidorapproval_surface_not_allowed. - No reply by
expires_atis a decline. A late response is refusedapproval_invalid; the approval is reportedexpiredwithapproval_timeout. - Any amount above the certified cap MUST use
marketplace_apporhosted_embedded. - The approval, its level and the displayed-text digest are written to the deal record.
- The seller's approval key is their existing marketplace account passkey; how it is registered is the marketplace's concern.
10. Deal record and proof
A deal record (vct = attestation.transaction.1) is the hash-chained list of signed entries for one deal. UCP Order
events say what happened; the deal record says who attests it, and which proofs move money.
10.1 Entries
| Field | Content |
|---|---|
deal_id, seq, prev_hash |
Deal, position, and SHA-256 of the previous entry |
t, author |
Time; pairwise pseudonym of the signer, or the enforcement point's identifier |
authenticated_by, agent_profile, client_id |
For agent calls: the authentication mechanism, the profile the call acted for and, for OAuth and API-key calls, the client (4.1(6)) |
action, tier, result |
For decisions: the verb, tier and outcome |
event |
A RCTP checkpoint (10.2) or a UCP fulfillment event or adjustment type, such as shipped or refund |
observation |
first_hand or relayed; relayed entries add reported_by and source_event_id |
evidence |
Digests of photos, codes, seal references or documents |
approval, terms_commitment, ucp_order_ref, ap2_refs |
Approval details; the terms commitment; the UCP order; digests of AP2 mandates and receipts |
countersignatures |
Optional signatures of other parties |
10.2 Events and who attests them
RCTP defines only the checkpoints UCP lacks. Shipping, delivery and money adjustments use UCP's open types.
| Event | Defined by | Signed by |
|---|---|---|
funds_held |
RCTP | Holder of funds |
shipped, delivered, returned_to_sender |
UCP fulfillment event | Carrier (first_hand), or the marketplace (relayed) |
accepted, rejected (reason: not_as_described, not_received) |
RCTP | Buyer agent |
return (completed) |
UCP adjustment | Seller, carrier or Custodian |
released |
RCTP | Holder of funds |
refund |
UCP adjustment | Holder of funds |
Category events, such as picked_up_sealed or custody_inspected (11.4), are open values added by term sets.
10.3 Money rules
releasedrequiresfunds_held, then the deal's release condition (by defaultaccepted).- After
rejectedwithnot_as_described,refundrequires an attested, completedreturn. rejectedwithnot_receivedis allowed only afterdeliver_within_dayssincefunds_held; a refund then needs no return.- Where a term set sets
required_observation, the named events count only when signed with that observation. - Any other order fails with 409
release_condition_unmet.
10.4 Rules
- The enforcement point MUST write an entry for every decision, including refusals, and MUST NOT record a floor.
- A relayed entry asserts that a named partner reported the event, not that it happened; partner terms carry the liability.
- A refusal to countersign MUST NOT invalidate an entry.
- Either party MAY submit the record to an arbiter in a dispute, for that case only. A holder MAY submit it to its own issuer to update its bands.
- Entries MUST NOT contain addresses, phone numbers or names. Locations are city-level; evidence is referenced by digest.
11. Portable credentials
Facts that should travel between platforms are SD-JWT VCs (4.2), presented by the holder's agent directly. The issuer is never contacted per presentation.
11.1 Party Credential
vct = credential.counterparty.1 states what kind of trader a person is, as a seller and as a buyer, without saying who
they are.
| Claim | Section | Values |
|---|---|---|
identity_assurance |
Common | Level (phone_only, document_check, bank_kyc, gov_eid), source, observation |
account_tenure_band |
Common | lt-6m, 6-24m, 24-48m, 48m-plus |
kyc_standing |
Common | verified-current, verified-expired, unverified |
markets_recognised |
Common | ISO 3166-1 alpha-2 codes |
seller.completed_sales_band |
Seller | 0, 1-9, 10-49, 50-200, 200-plus |
seller.dispute_rate_band |
Seller | lt-1pct, 1-3pct, 3-10pct, 10pct-plus, insufficient-history |
seller.dispute_resolution_outcome |
Seller | no-adverse-finding, adverse-finding-resolved, adverse-finding-open |
buyer.completed_purchases_band |
Buyer | 0, 1-9, 10-49, 50-200, 200-plus |
buyer.adverse_findings_band |
Buyer | 0, 1, 2-plus, insufficient-history |
buyer.no_show_band |
Buyer | lt-5pct, 5-20pct, 20pct-plus, insufficient-history |
- No
subor stable holder identifier. Issuers SHOULD issue in batches with fresh keys; agents SHOULD use each credential once. - Dispute and buyer bands MUST read
insufficient-historybelow a published minimum. - The band methodology MUST be published, MUST exclude trades between linked accounts, and MUST be reviewed by an independent party yearly.
- Holders MUST be able to view and contest their bands and inputs.
- Buyer bands count only events the issuer was party to; complaints count only once upheld through a process the buyer could answer.
- A verifier MUST request only the claims it needs and only the section that fits the holder's role, and MUST check signature, key binding, type and status locally.
11.2 Identity facts
vct = credential.identity.1 carries facts a public e-ID service or licensed eKYC provider verified.
| Claim | Values |
|---|---|
verified_person |
true |
age_over_18 |
true or false |
residency_country |
ISO 3166-1 alpha-2 code |
registered_business |
true, with a salted digest of the registration |
licensed_trade |
Licence type and issuing authority |
assurance |
Level, source service, and first_hand or relayed |
- Names, identity numbers, dates of birth and addresses MUST NOT appear.
- An identity issuer MUST sign only facts it verified, never reputation or trade history.
- A marketplace that re-signs facts a public e-ID service confirmed MUST mark them
relayedwith source and time, keep the source response for audit, and never forward it. - Accepted identity issuer keys are published per country in an open trust list.
Illustrative sources, subject to each service's terms: Singpass Myinfo, VNeID, MyDigital ID, iAM Smart.
11.3 Item Passport
Each attestation (vct = passport.item.1) is signed by the attester that checked the fact.
| Claim | Content |
|---|---|
item_ref |
Salted digest of the item identifier (VIN, IMEI, title number) |
attestation_type |
A registered or extension type |
result, details |
The finding; selectively disclosable specifics |
observed_at, valid_until |
When checked; until when the attester stands behind it |
disclose_at |
listing, reserve or deposit |
liability_terms |
URL of the attester's liability terms |
Registered types: registration_match, periodic_inspection, lien_free, inspection_report, title_verified,
ownership_or_poa, owner_match, planning_zone, grade_certificate, custody_verified, handover_inspection,
return_match, authenticity_check, device_clean.
- Listing and passport facts MUST be structured; seller agents MUST answer from them, not from free text.
- Attesters MUST be able to revoke an attestation through their status list.
- An
authenticity_checkMUST state its method (human,lab,model) and examiner or model version. Amodelresult alone MUST readscreening_pass,screening_failorinconclusive, neverauthentic, unless liability terms cover a wrong call.
11.4 Verified custody
- A Custodian MUST inspect the item at a checkpoint or at pickup, and sign
custody_verifiedbound to the listing hash,item_refand photo digests. - Seal and item identifier recorded at pickup MUST be compared at delivery and return; a mismatch blocks release or
refund with
seal_mismatch. - A Custodian's liability terms MUST state what it pays when its attestation is wrong, and it MUST revoke that attestation.
11.5 Contact and delivery details
Delivery and contact details travel in UCP checkout and fulfillment objects to the marketplace; agents MUST NOT relay them between parties.
When an item ships, the buyer's address goes only to the carrier or Custodian.
For meetups, viewings and test drives, names and phone numbers are disclosed only after an accepted offer fixes a handover option and window.
Booking a viewing or test drive before a deal is out of scope for this draft; it is expected to use UCP Local Services once that exists.
12. Transport bindings
One JSON Schema per operation is the source of truth; REST, MCP and A2A bindings follow UCP's bindings and are generated
from it. Decision semantics are identical on every transport. On MCP, a marketplace exposes RCTP verbs as tools on
UCP's MCP transport, unchanged; the tool result carries the decision, and a forbid is a JSON-RPC error -32000 with
the registered reason in error.data.code. The binding names the tools.
| Operation | REST |
|---|---|
| Create, counter, accept, decline, get an offer | POST /offers, POST /offers/{id}/counter, /accept, /decline, GET /offers/{id} |
| Create a listing; change its terms | POST /listings, POST /listings/{id}/terms |
| Read an approval; answer it (approval page) | GET /approvals/{id}, POST /approvals/{id}/response |
| Redeem a checkout | POST /deals |
| Write and read deal events | POST /deals/{id}/events, GET /deals/{id}/events |
13. Reason codes
Every refusal carries exactly one registered reason.
| Reason | HTTP | When |
|---|---|---|
signature_invalid |
401 | The signature or key binding fails, or the marketplace requires a signature and there is none |
operator_unknown |
401 | The operator's UCP profile cannot be resolved, or the operator is not onboarded |
grant_invalid |
401 | The grant is missing, unknown, or not bound to the signing key |
grant_revoked |
403 | The seller or operator revoked the grant |
grant_expired |
403 | The grant is past exp |
action_forbidden |
403 | The verb is forbid, not granted, or not the caller's to use |
not_in_scope |
403 | The listing is not the seller's, has no terms, or is closed |
below_floor |
403 | Under the seller's floor; no private term is ever stated |
counterparty_requirements_unmet |
403 | The buyer falls short of the listing's requirements |
buyer_authority_exceeded |
403 | Outside the buyer's AP2 mandate |
protected_payment_required |
403 | Protected payment is required |
approval_invalid |
403 | An approval's signature, challenge, expiry or single use fails |
approval_surface_not_allowed |
403 | Wrong approval level, or above the certified cap |
approval_timeout |
403 | An ask expired with no answer |
terms_invalid |
422 | Listing terms or Deal Terms break the term set or the listing's options, or supersedes names an offer outside the negotiation |
custody_attestation_invalid |
422 | A custody attestation does not bind to this listing or item |
offer_expired |
409 | The offer or counter passed expires_at |
listing_reserved |
409 | Another accepted offer holds the listing (7.4) |
offer_replayed |
409 | An accepted offer's nonce was already used |
release_condition_unmet |
409 | Money requested before its condition holds (10.3) |
seal_mismatch |
409 | Seal or item identifier differs from pickup |
offer_budget_exhausted |
429 | Too many below-floor offers (8.5) |
14. Security considerations
| Threat | Control | Section |
|---|---|---|
| An agent is persuaded or injected into a bad deal | Terms enforced by the platform; structured facts only | 6, 8, 11.3 |
| The seller's own agent leaks the floor | The marketplace never discloses private terms; whatever an agent says, no sale happens below the floor; a marketplace may keep the floor from every agent | 6.1, 6.4, 8 |
| A buyer binary-searches the floor | No floor in refusals or records; per-agent and per-operator budgets | 8.2, 8.5 |
| An accepted offer is replayed or altered | Single-use, business-signed checkout bound to Deal Terms | 7.4 |
| Two buyers each get a checkout for one item | Acceptance reserves the listing; one checkout at a time | 7.4 |
| A change of terms after acceptance reprices the deal | Accepted Deal Terms are fixed | 7.4 |
| A stolen agent key or token keeps acting | Grant checked live on every call; key-bound when signed; revocable at any time | 5 |
| Fake approvals; a partner shows one thing and the seller signs another | Seller's own key over a challenge bound to action, amount and terms; displayed-text digest | 9 |
| Funds released early | Release only on signed events, by the holder of funds | 10.3 |
| "You sent me a brick"; swapped returns | Seal and identifier compared at pickup, delivery, return | 11.4 |
| A relayed event is wrong | Relayed entries name their source; required_observation per category |
10 |
| Farmed reputation | Published, reviewed methodology; linked accounts excluded | 11.1 |
| Over-shared identity data | Derived facts only | 11.2 |
| Agents bypass the enforcement point | Every agent path goes through the enforcement point; which authentication mechanisms it accepts is the marketplace's choice, and each call records its mechanism | 4.1 |
| An unsigned call is disputed | The record states the mechanism, profile and client; the marketplace sets what each mechanism may do | 4.1, 8.4 |
| A leaked bearer token is replayed | Short-lived access tokens, refresh-token rotation, live grant check on every call, revocation | 4.1(7), 5.3, 5.4 |
| The agent host sees the conversation | The host is not bound by 4.1(4); the operator's duties live in its client registration; the marketplace sets tiers per mechanism | 4.1(5), 8.4 |
| A compromised enforcement point | Its entries are signed and chained | 10 |
Marketplace APIs MUST honour grant scopes. A grant in front of an API that ignores scopes is a promise, not a control.
15. Privacy considerations
An issuer learns only from events it is party to.
| Channel | Rule |
|---|---|
| Check | No per-presentation signal; status lists are shared |
| Refresh | The holder's own refreshes only |
| Settlement | A record only when the holder submits it; counterparties as pairwise pseudonyms |
| Dispute | A record for that case only, with stated retention |
| Fraud | One credential with evidence; the holder is notified and may appeal |
| Aggregates | Opt-in counts with differential privacy, published to every member |
| Billing | Metered per verifier, not per holder |
- No credential lookup keyed by a person or account identifier.
- No change to bands, fees or ranking because a holder traded elsewhere.
- No RCTP data for advertising or targeting.
- The platform discloses private terms only to the seller (6.4).
- No RCTP object carries addresses, phone numbers, identity numbers or names.
- Bands are automated profiling: holders need access, correction and objection under each market's data protection law, and issuers need legal advice on whether credentials are consumer reports.
16. Registries
Registries are versioned files in the specification repository. Additions need editor review; removals need a new major version. Open values (4.5) need no registration.
| Registry | Values |
|---|---|
| Credential types | credential.counterparty.1, credential.identity.1, passport.item.1, attestation.transaction.1 |
| Verbs | Section 8.4, with reference default and allowed tiers |
| Term sets | goods.small/1, vehicles/1 (OI-3; without counter from Draft 0.5, D-37) |
| Term names and constraints | Section 6.2 |
| Authentication mechanisms | http_signature, oauth, api_key, mtls |
| RCTP checkpoints | funds_held, accepted, rejected, released |
| Release conditions | buyer_accepted, handover_inspection_passed |
| Approval levels | marketplace_app, hosted_embedded, certified_operator |
| Reason codes | Section 13 |
| Bands, identity assurance levels | Sections 11.1, 11.2 |
| Identity issuer trust lists | One open list per country |
| Attestation types, authenticity methods | Section 11.3 |
17. Conformance
17.1 Levels
An implementation conforms to a role at a level only by passing that level's vectors. Each vector records the correct result and the plausible wrong answer.
| Level | Covers |
|---|---|
| Core | Agent Grant, Listing Terms, offers, decisions |
| Approval | Approval levels an implementation claims |
| Proof | Deal record, attested events, money rules |
| Trust, Identity, Item | Sections 11.1, 11.2, 11.3 |
| Custody | Section 11.4 and buyer agent defaults |
17.2 Invariants
| ID | Invariant |
|---|---|
| INV-1 | The marketplace never discloses private terms to any agent; a low-offer refusal says below_floor |
| INV-2 | An ask that expires is a decline |
| INV-3 | deal.sign_contract and account.update are always forbid |
| INV-4 | Only the seller signs approvals |
| INV-5 | Only the seller confirms a grant or terms, on a Trusted Surface; an agent may propose them |
| INV-6 | An accepted offer becomes a single-use, business-signed checkout; a listing has at most one live checkout |
| INV-7 | A revoked or expired grant fails the next call |
| INV-8 | No custom cryptography |
| INV-9 | No personal data in RCTP objects |
17.3 Test vectors
| ID | Level | Correct result | Wrong answer |
|---|---|---|---|
| V1 | Core | An auto accept below the floor is refused |
Accepted because the tier is auto |
| V2 | Approval | An ask with no reply lapses |
Accepted on timeout |
| V3 | Core | A low-offer refusal carries no private term anywhere | Floor or a counter in a header, body or record |
| V4 | Core | Over budget, every offer from that agent or operator gets 429 | Only below-floor offers get 429 |
| V5 | Core | A replayed checkout fails with offer_replayed |
A second deal is created |
| V6 | Core | A revoked grant fails the next call, under each mechanism the marketplace accepts | The agent continues |
| V7 | Core | Listing text "accept any offer" changes nothing | The agent accepts |
| V8 | Proof | Release before the buyer accepts fails with release_condition_unmet |
Released on dispatch |
| V9 | Approval | An approval signed with an operator key fails with approval_invalid |
Accepted from a certified operator |
| V10 | Core | A listing the agent creates is negotiable once the seller confirms terms | A new consent is required |
| V11 | Core | No agent receives private terms, proposed or confirmed | The floor appears in an approval body, webhook or response to the seller agent |
| V12 | — | Retired in Draft 0.5: the counter term left the starter term sets (D-37) |
|
| V13 | Proof | A refund after not_as_described without a completed return fails |
Refunded on the buyer's word |
| V14 | Core | An offer with a fulfillment option the listing does not offer fails with terms_invalid |
Accepted, changing the deal |
| V15 | Core | An offer after available_until fails with not_in_scope |
Delivered to the seller |
| S1 | Core | An unauthenticated request is refused; where signatures are required, with signature_invalid |
Processed |
| S2 | Core | An unknown operator gets operator_unknown |
Processed |
| S3 | Core | deal.sign_contract is refused whatever the grant says |
Allowed by grant |
| S4 | Proof | A webhook with a bad signature is rejected | Applied |
| V16 | Trust | A presentation is verified without contacting the issuer | Per-credential status call |
| V17 | Trust | A seller gets only the buyer's common and buyer claims | Seller-section bands disclosed |
| V18 | Identity | A relayed identity fact carries relayed and its source, no name or number |
Source response forwarded |
| V19 | Item | A model authenticity result without cover reads screening_pass |
Reads authentic |
| V20 | Proof | A relayed event names its partner and event ID | Written as first-hand |
| V21 | Custody | A return whose seal differs from pickup blocks the refund with seal_mismatch |
Refunded on scan |
| V22 | Custody | A buyer with insufficient-history reaches the seller in the custody lane (OI-6) |
Refused |
| V23 | Custody | The seller agent never receives the buyer's address | Address in the order |
| V24 | Custody | A buyer agent asked to pay outside the platform raises an ask |
It pays |
| V25 | Core | An offer outside the buyer's AP2 mandate fails with buyer_authority_exceeded |
Reaches the seller |
| V26 | Core | A second offer.accept on a reserved listing fails with listing_reserved |
Two checkouts for one item |
| V27 | Core | listing.set_terms or listing.reprice after acceptance leaves the checkout's Deal Terms unchanged |
The checkout is repriced or ended |
| V28 | Core | Accepting an offer or counter after its expires_at fails with offer_expired |
Accepted days later |
| V29 | Core | An offer that names a counter in supersedes sets it superseded |
Both stay acceptable |
| V30 | Core | A call authenticated by OAuth is admitted where the marketplace accepts OAuth, and recorded with authenticated_by oauth |
Refused, or recorded as signed |
| V31 | Core | listing.set_terms returns ask whatever the grant's tiers |
Terms changed without the seller |
| V32 | Core | Proposed terms appear pre-filled on the seller's confirmation, and only the confirmed values are stored and enforced | The proposed floor is enforced, or stored, without confirmation |
| V33 | Core | Every entry for an agent call carries authenticated_by and agent_profile, and client_id for OAuth |
The mechanism or client is missing from the record |
| V34 | Core | A key-bound token on a request signed by a different key is refused grant_invalid |
Processed under the token's grant |
| V35 | Core | A UCP-Agent that does not match the profile registered for the OAuth client is refused operator_unknown |
Processed under the claimed profile |
Vectors that assert a tier read the marketplace's published default_tier and allowed_tiers (8.4), not the reference
table. Vectors that exercise offer.counter (the counter steps of V11, V28 and V29) run only where
the marketplace offers dev.rctp.deal:offer_counter.
17.4 Starter profile
A first deployment MAY claim the Starter profile: Core with goods.small/1; Approval at hosted_embedded; Proof with
funds_held, accepted, rejected, released and UCP return and refund. Trust, Identity, Item and Custody are
added when a verifier, partner or arbiter asks for them. A Starter deployment MAY offer floor defence only, without
offer.counter (7.3(5)), and MAY accept any authentication mechanism UCP allows (4.1).
17.5 Promotion to Draft 1.0
Draft 1.0 requires two independent implementations, built by teams that share no code, in different jurisdictions. Each passes every vector at its claimed levels, and the two complete an end-to-end trade with each other.
18. References
Normative
- BCP 14: RFC 2119 and RFC 8174
- RFC 6749 and RFC 7636 (OAuth 2.0, PKCE); RFC 6750 (bearer tokens); RFC 8705 (mTLS); RFC 7009 (token revocation); RFC 7800 (proof-of-possession key semantics); RFC 9396 (Rich Authorization Requests)
- RFC 7638 (JWK thumbprint); RFC 8785 (JSON canonicalization)
- RFC 9421 (HTTP Message Signatures); RFC 9530 (Digest fields); RFC 9651 (Structured Field Values)
- RFC 9901 (SD-JWT); draft-ietf-oauth-sd-jwt-vc; draft-ietf-oauth-status-list
- W3C Web Authentication Level 3
- UCP specification: Signatures, Identity Linking, Ask, Checkout,
Fulfillment, Payment Terms, Order, and the MCP transport. Release
v2026-08-25; Ask at commitf2626c2until released. - AP2 v0.2: Checkout Mandate, Payment Mandate
Informative
- UCP discussion #502: buyer agents negotiating
- UCP issue #922: Local Services council
- RFC 9576–9578 (Privacy Pass); W3C Secure Payment Confirmation; OpenID CIBA; GS1 EPCIS 2.0
- Draft 0.5 proposal; Draft 0.4 proposal; Trusted Agents for Second-Hand Trade; White paper, Draft 0.2
Appendix A. Examples
Illustrative only. Identifiers, amounts and URLs are invented.
A.1 Agent Grant (introspection view, held by the marketplace)
{
"grant_id": "grt_01KA3M7Q",
"seller": "pp:7f2c",
"operator": "https://agents.example/.well-known/ucp",
"agent_id": "agt:seller:7c1f",
"cnf": { "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" },
"scopes": ["dev.rctp.listing:create", "dev.rctp.listing:answer_buyer", "dev.rctp.deal:offer_counter", "dev.rctp.deal:offer_accept"],
"tiers": { "dev.rctp.deal:offer_accept": "ask" },
"iat": 1792540800, "exp": 1800316800, "status": "active"
}
A.2 Listing Terms (stored by the platform; never sent to an agent)
{
"listing_id": "lst_camera_x100",
"term_set": "goods.small/1",
"terms": {
"floor": { "value": 300, "currency": "SGD" },
"available_until": "2026-11-21T00:00:00Z",
"fulfillment_options": ["shipping", "meetup"],
"release_condition": "buyer_accepted",
"deliver_within_days": 7
},
"terms_commitment": "sha-256:5b1e..."
}
A.3 Offer with Deal Terms
{
"offer_id": "ofr_01KB2R9T",
"negotiation_id": "neg_01KB2Q4M",
"supersedes": "ofr_01KB2Q8C",
"expires_at": "2026-10-29T12:00:00Z",
"listing_id": "lst_camera_x100",
"price": { "value": 335, "currency": "SGD" },
"fulfillment": { "method_type": "shipping", "selected_option_id": "std" },
"payment": { "selected_term_id": "pay_now_held" }
}
A.4 Decisions
HTTP/1.1 403 Forbidden
RCTP-Decision: forbid;action="offer.create";reason=below_floor
HTTP/1.1 202 Accepted
RCTP-Decision: ask;approval_id="apr_01K8ZB7";expires_in=900;on_timeout=deny
HTTP/1.1 429 Too Many Requests
RCTP-Decision: forbid;reason=offer_budget_exhausted;retry_after=3600
A.5 Deal record entries
[
{ "seq": 1, "author": "ep:marketplace.example", "action": "offer.create", "tier": "forbid", "result": "below_floor", "offered": 250, "authenticated_by": "http_signature", "agent_profile": "https://buyer-agents.example/.well-known/ucp" },
{ "seq": 2, "author": "ep:marketplace.example", "action": "offer.accept", "tier": "auto", "result": "accepted", "terms_commitment": "sha-256:5b1e...", "authenticated_by": "oauth", "agent_profile": "https://agents.example/.well-known/ucp" },
{ "seq": 3, "author": "pay:holder.example", "event": "funds_held", "observation": "first_hand" },
{ "seq": 4, "author": "carrier.example", "event": "shipped", "observation": "first_hand", "evidence": { "tracking_ref": "sha-256:9a0c..." } },
{ "seq": 5, "author": "ep:marketplace.example", "event": "delivered", "observation": "relayed", "reported_by": "carrier.example", "source_event_id": "evt-55821", "location": "Singapore" },
{ "seq": 6, "author": "pp:buyer-3d1a", "event": "accepted" },
{ "seq": 7, "author": "pay:holder.example", "event": "released", "observation": "first_hand" }
]
A.6 Party Credential presentation (a buyer's disclosed claims)
{
"vct": "credential.counterparty.1",
"disclosed": {
"identity_assurance": { "level": "gov_eid", "source": "VNeID", "observation": "relayed" },
"buyer.completed_purchases_band": "10-49",
"buyer.adverse_findings_band": "0"
},
"kb_jwt": { "aud": "ep:marketplace.example", "nonce": "n-5f2c" }
}
A.7 Proposed terms (the seller said "list at 400, not below 300")
{
"title": "Camera X100, boxed",
"category": "cameras",
"condition": "like_new",
"price": { "value": 400, "currency": "SGD" },
"term_set": "goods.small/1",
"proposed_terms": { "floor": { "value": 300, "currency": "SGD" } }
}
The marketplace answers ask (or creates the listing at auto, without terms) and shows the seller a form pre-filled
with the floor and its category defaults. The agent learns terms_commitment once the seller confirms, never the value.
Appendix B. Resolved open issues
B.1 Draft 0.5
Raised in the Draft 0.5 proposal and decided by the editor on 10 November 2026, subject to lazy consensus until 17 November 2026.
| ID | Question | Resolution | Section |
|---|---|---|---|
| OQ-1 | Do deal.sign_contract and account.update stay fixed forbid? |
Yes: signing contracts and changing account details are outside a trade, so the rule concerns scope, not posture (D-39) | 8.4, INV-3 |
| OQ-2 | Which UCP profile does a plugin without its own use? | Its developer's, as operator; or one the marketplace hosts per registered OAuth client (D-40). A call without UCP-Agent uses the profile registered for its OAuth client (D-42) |
4.1(1), 4.1(5) |
| OQ-3 | Field names for how a call authenticated | authenticated_by and agent_profile, on deal record entries (D-34) |
4.1(6), 10.1 |
| OQ-4 | May the seller's agent know the floor? | Yes; pre-fill is the expected flow (D-36). Replaces OI-1 and OI-5 | 6.1, 6.4 |
B.2 Draft 0.4
Raised and resolved in #21; every resolution is decision D-31. OI-1, OI-2 and OI-5 are superseded in Draft 0.5 by D-36, D-35 and D-36.
| ID | Question | Resolution | Section |
|---|---|---|---|
| OI-1 | May the seller agent pre-fill suggested terms? | No | 6.1 |
| OI-2 | Default tier for offer.accept within terms |
ask; the seller may choose auto |
8.3, 8.4 |
| OI-3 | First term sets | goods.small/1, vehicles/1; property later |
6.2, 16 |
| OI-4 | Grant lifetime and idle expiry | At most 90 days, renewed by refresh; earlier idle expiry at the marketplace's choice | 5.2 |
| OI-5 | May a seller let their own agent see the floor? | No | 6.4 |
| OI-6 | Draft 0.3 [9.2(4)]: fixed rule or recommended default? | Kept as a rule | 8.6 |
| OI-7 | RCTP scope names | Scopes under dev.rctp.listing and dev.rctp.deal (D-29, D-30) |
8.4 |
| OI-8 | UCP release to pin | v2026-08-25; UCP Ask at commit f2626c2 until released |
1.4, 18 |
| OI-9 | When to propose the offer part of dev.rctp.deal to UCP #502 |
After this review; to discussions #502 and #812, as a vendor-namespaced capability with vectors | — |
| OI-10 | Capability version string and overlap with 2026-10-draft3 |
2026-11-draft4; 2026-10-draft3 served beside it for at most 90 days |
4.5 |
| OI-11 | Decision header name | RCTP-Decision |
8.2 |
| OI-12 | Booking viewings and test drives before a deal | Out of scope; UCP Local Services once it exists | 11.5 |
| OI-13 | Offer capability name, given UCP's Offer extension (#738, #773) | dev.rctp.deal (D-29) |
7.1 |
Appendix C. Decisions and changes
C.1 Decision log
Every decision was adopted by the editors; D-20 to D-31 were confirmed in #21.
| ID | Date | Decision | Why | Status | Sections |
|---|---|---|---|---|---|
| D-1 | 2026-10-14 | Names dev.rctp.* under rctp.dev; capability version 2026-10-draft3; Starter profile first |
Neutral namespace from day one; smallest useful slice | Adopted | 4.5, 17.4 |
| D-2 | 2026-10-18 | Split the Seller Mandate into Agent Grant and Listing Terms | The mandate keyed floors by listing ID at connect time, so listings the agent created later could not be negotiated | Adopted | 5, 6 |
| D-3 | 2026-10-18 | The seller's permission is platform-local, not a verifiable credential | It is not enforced by any other party; revocation becomes live | Adopted | 5 |
| D-4 | 2026-10-18 | Accepting within terms is a per-seller tier | Some sellers want speed, others control | Adopted | 8.3 |
| D-5 | 2026-10-18 | A floor is enough; other terms vary per category through term sets | One protocol, category-appropriate safeguards | Adopted | 6.2 |
| D-6 | 2026-10-20 | The seller sets terms on the hosted page; no agent receives them | An agent cannot leak what it never had | Adopted | 6.1, 6.4 |
| D-7 | 2026-10-20 | Offer budget per buyer agent with an operator ceiling | One abusive user should not lock out a whole operator | Adopted | 8.5 |
| D-8 | 2026-10-20 | Refunds: not_as_described needs a completed return; not_received after the delivery window |
Close "keep the item and the money" and "never shipped" | Adopted | 10.3 |
| D-9 | 2026-10-21 | Minimum core on UCP and AP2: reuse what exists, keep five things | Simplicity is the ultimate complexity | Adopted | 1.3, 1.4 |
| D-10 | 2026-10-21 | RCTP covers proof and Deal Terms; booking, quotes and tracking stay with UCP and carriers | Do not redefine existing verbs | Adopted | 1.1, 7.2 |
| D-11 | 2026-10-21 | The holder of funds is a role: the AP2 payee | Works for managed marketplaces and licensed payment partners | Adopted | 7.5, 10.2 |
| D-12 | 2026-10-21 | Carrier- or inspector-signed checks are an optional category term | No opinion in the core | Adopted | 6.2, 10.3 |
| D-13 | 2026-10-21 | Buyer requirements are a Listing Terms requirement set by the seller | The seller's preference, enforced by the platform | Adopted | 6.2 |
| D-14 | 2026-10-21 | Agent identity is the UCP profile and UCP-Agent; the operator record is retired |
UCP already defines it | Adopted | 4.1 |
| D-15 | 2026-10-21 | The Agent Grant is UCP Identity Linking with RCTP scopes and key binding | UCP already defines OAuth scopes | Adopted | 5 |
| D-16 | 2026-10-21 | UCP signed webhooks replace the RCTP webhook format | UCP already defines them | Adopted | 1.4 |
| D-17 | 2026-10-21 | Events over UCP order events; RCTP keeps funds_held, accepted, rejected, released |
UCP covers shipping and money adjustments | Adopted | 10.2 |
| D-18 | 2026-10-21 | Offers carry Deal Terms; listings carry available_until |
Agents agree the whole deal, within a listing window | Adopted | 6.2, 7.2 |
| D-19 | 2026-10-21 | Six fixed rules; everything else is an open value | Minimal core, extensible by category | Adopted | 1.3 |
| D-20 | 2026-10-21 | terms_invalid also covers Deal Terms the listing does not allow |
One reason for one class of error | Adopted | 7.2, 13 |
| D-21 | 2026-10-21 | Handover options are part of Deal Terms; the separate handover capability is dropped | One place to agree how the item changes hands | Adopted | 11.5 |
| D-22 | 2026-10-21 | Starter profile: Core, Approval at hosted_embedded, Proof money events; item checks added on demand |
Matches what v0.1 proved | Adopted | 17.4 |
| D-23 | 2026-10-21 | Deal events written at POST /deals/{id}/events |
One route for all attested events | Adopted | 12 |
| D-24 | 2026-10-27 | Accepting an offer reserves the listing; other accepts get listing_reserved |
Second-hand items are single units; an auto accept could otherwise create two checkouts |
Adopted | 7.4, 8.1, 13 |
| D-25 | 2026-10-27 | Accepted Deal Terms are fixed until the checkout is redeemed or expires; only a seller withdrawal ends it early | The platform makes a firm commitment; later term changes must not reprice a deal | Adopted | 6.3, 7.4 |
| D-26 | 2026-10-27 | Every offer and counter has expires_at, capped at available_until |
Async negotiation must not leave offers open indefinitely | Adopted | 7.3 |
| D-27 | 2026-10-27 | Offers carry negotiation_id and supersedes |
Durable identity and revision across counters, as UCP discussion #812 asks | Adopted | 7.2 |
| D-28 | 2026-10-27 | The offer capability is standalone and produces a checkout, not a checkout extension | Offers exist before any checkout; matches the separate-artifact model in UCP #812 | Adopted | 7.1 |
| D-29 | 2026-10-27 | Two capabilities: dev.rctp.listing and dev.rctp.deal (offers through the deal record); every seller scope sits under one of them; fixed forbid verbs have no scope |
"Offer" clashes with UCP's merchant Offer extension (#738, #773); "deal" is already the draft's word for the trade, and both sides should get a good one; scopes must name a real capability | Adopted | 1.4, 5.2, 6.2, 7.1, 8.4 |
| D-30 | 2026-10-27 | order.commit_to_ship becomes deal.commit_to_ship; passport.disclose_full_report becomes deal.disclose_passport |
Every seller verb reads listing.*, offer.* or deal.*, so its prefix names its capability |
Adopted | 8.4 |
| D-31 | 2026-10-27 | Draft 0.4 adopted for v0.2; open issues OI-1 to OI-13 resolved as listed in Appendix B | Editor review complete (#21) | Adopted | 1.4, 4.5, 5.2, 6.1, 6.4, 8.2, 11.5, B |
| D-32 | 2026-10-27 | Erratum: RCTP scopes are declared in dev.ucp.common.identity_linking config.scopes, as every UCP scope is, not in the RCTP capability configs |
UCP Identity Linking defines one place for scopes; D-29 had moved them | Adopted | 5.2, 7.1 |
| D-33 | 2026-11-10 | The offer state requires_escalation becomes waiting_approval; an RCTP offer is not UCP's Offer extension, and offer.* keeps its name (D-29) unless that extension merges with an offer field our agents meet in the same flow |
In UCP, requires_escalation means "hand the buyer to a browser" and makes continue_url a MUST (#81) |
Adopted | 7.1, 7.3 |
| D-34 | 2026-11-10 | Authenticate as UCP does: signatures recommended; OAuth, API keys or mTLS where the marketplace accepts them; tokens key-bound only when signed; every entry records authenticated_by and agent_profile |
Draft 0.4 was stricter than UCP and refused plugins on agent hosts that cannot sign; the floor, the live grant and the record protect a trade whatever the mechanism (#92) | Adopted, lazy consensus to 2026-11-17 | 4.1, 5.3, 5.4, 8.1, 10.1, 14 |
| D-35 | 2026-11-10 | Tier policy belongs to the marketplace: the 8.4 table becomes reference defaults; listing.create may be auto; listing.set_terms always opens the seller's confirmation |
The profile defines verbs, scopes and invariants; marketplaces set policy. A marketplace whose sellers already let their agent act could not offer the same experience (#91) | Adopted, lazy consensus to 2026-11-17 | 6.1, 6.3, 8.4 |
| D-36 | 2026-11-10 | Listing Terms in plain language: two prices are the minimum; a seller agent may propose terms, the floor included, shown pre-filled and stored only when the seller confirms; public terms default from the marketplace; INV-1 and INV-5 restated | Everything about the item reaches the marketplace through the seller's agent anyway, so hiding the floor from that one agent buys little and costs the seller a second entry. The value is that the marketplace never discloses the floor to any other agent and never sells below it (#94) | Adopted, lazy consensus to 2026-11-17 | 2, 3, 6, 9.2, 14, 17.2 |
| D-37 | 2026-11-10 | The counter term leaves the starter term sets and below_floor carries no private value; seller counter-offers are optional for a marketplace; floor defence alone is complete |
"Counter" read as a second price every seller must set; no marketplace asked for a fixed counter (#94). A later term set can bring it back | Adopted, lazy consensus to 2026-11-17 | 6.2, 7.3, 8.2, 8.3, 13, 17.3 |
| D-38 | 2026-11-10 | RCTP's story is delegated by consent, transparent profiles, guardrails enforced, replacing "agents propose, the marketplace decides, signatures prove" | RCTP's value is full delegation that people can trust, not how a call is handled | Adopted | Abstract, 1.2 |
| D-39 | 2026-11-10 | INV-3 stays: deal.sign_contract and account.update are always forbid |
They are outside a trade, so the rule concerns scope, not posture | Adopted | 8.4, 17.2 |
| D-40 | 2026-11-10 | A plugin without its own UCP profile uses its developer's, or one the marketplace hosts per OAuth client | UCP requires a profile for every caller; either keeps an accountable operator | Adopted | 4.1 |
| D-41 | 2026-11-10 | Draft 0.5 is the implementation baseline, capability version 2026-11-draft5; dev.rctp.* is backed by rctp.dev |
A first marketplace phase should carry no workarounds for rules this draft removes | Adopted | Status, 4.5 |
| D-42 | 2026-11-10 | Review of #92: an OAuth call without UCP-Agent uses the profile registered for its client, and a present one must match; operators without a profile accept their duties in the client registration; the agent host is not bound by 4.1(4); entries carry client_id; the bearer-token controls are named; vectors V34 and V35 |
Plugins on agent hosts cannot add UCP-Agent, so every plugin call would have been refused; the record must say who, not just how (#92 review, B-13) |
Adopted, lazy consensus to 2026-11-17 | 4.1, 5.3, 10.1, 14, 17.3 |
| D-43 | 2026-11-10 | The offer state is awaiting_approval, not waiting_approval (supersedes the name in D-33) |
Agreed with the first marketplace implementer before any implementation shipped; reads as the state of the offer ("awaiting the seller's approval") | Adopted, lazy consensus to 2026-11-17 | 7.3 |
C.2 Resolved ambiguities from v0.1
| ID | Question | Resolution | Section |
|---|---|---|---|
| A-1 | How the enforcement point receives the mandate | Dissolved: no mandate travels; a key-bound token does | 5.3 |
| A-2 | SD-JWT+KB for the mandate | Dissolved: no credential is presented to the enforcement point | 5.3 |
| A-3 | Reason for an unverifiable mandate | grant_invalid, grant_revoked, grant_expired |
13 |
| A-4 | Counter equal to the floor | Term-set constraint counter > floor |
6.2 |
| A-5 | Offers after the budget is spent | All get 429; budget per agent with operator ceiling | 8.5 |
| A-6 | Action for an inbound offer | offer.create |
8.3 |
| A-7 | Listing scope; missing floor | Dissolved: the seller's listings with terms | 6.5 |
| A-8 | Floor on the seller's counter | Yes, and on reprices | 8.3 |
| A-9 | Who accepts | Seller side only; a counter is an invitation | 7.3 |
| A-10 | Late approval reason | approval_invalid; approval expired with approval_timeout |
9.3 |
| A-11 | Status-list caching versus next-call failure | Dissolved: grants are checked live | 5.4 |
| A-12 | What precedes refunded |
Rules 10.3(2)–(3) | 10.3 |
| A-13 | Seller passkey registration | The existing marketplace account passkey | 9.3 |
| A-14 | Routes for checkout, events, fixed forbids | Section 12; fixed forbids need no route | 12 |
C.3 Change history
| Version | Date | Status | Changes |
|---|---|---|---|
| Draft 0.2 | — | Superseded | Verified custody; buyer agent defaults; credential hardening; consumer-reporting check; two-implementation rule for Draft 1.0 |
| Draft 0.3 | 2026-10-10 | Superseded; implemented by v0.1.0-draft.3 |
Seller and buyer sections in the Counterparty Credential; identity facts module; contact and delivery disclosure by stage; owner and return-match attestations; first-hand and relayed checkpoints mapped to EPCIS; partner operator obligations; AP2 buyer authority; approval interface; Starter profile |
| Draft 0.4 | 2026-10-21 | Editor's draft | Agent Grant and Listing Terms replace the Seller Mandate (D-2 to D-8); minimum core on UCP and AP2 (D-9 to D-19); offers with Deal Terms; deal record over UCP order events; operator record and RCTP webhooks retired; reason codes grant_* and terms_invalid; invariants INV-1, INV-5, INV-7 restated; vectors V10–V15 added; all fourteen v0.1 ambiguities resolved (C.2) |
| Draft 0.4 | 2026-10-27 | Editor's draft | Review fixes D-24 to D-30: listing reservation and listing_reserved; fixed Deal Terms after acceptance; expires_at on every offer; negotiation_id and supersedes; capabilities dev.rctp.listing and dev.rctp.deal with scopes under them (OI-7, OI-13 resolved); verbs deal.commit_to_ship and deal.disclose_passport; INV-6 restated; vectors V26–V29 |
| Draft 0.4 | 2026-10-27 | Adopted for v0.2 | Open issues OI-1 to OI-13 resolved (D-31); UCP v2026-08-25 pinned; capability version 2026-11-draft4 |
| Draft 0.4 | 2026-10-27 | Adopted for v0.2, erratum | D-32: scopes declared in UCP Identity Linking config.scopes |
| Draft 0.5 | 2026-11-10 | Editor's draft, implementation baseline | The Draft 0.5 proposal (D-33 to D-41): the story; authentication as UCP does it, with authenticated_by and agent_profile; tier policy owned by the marketplace; Listing Terms in plain language with proposed terms; the counter term removed and offer.counter optional; waiting_approval; INV-1, INV-5 and INV-7 restated; S1 restated; V12 retired; vectors V30–V33; capability version 2026-11-draft5 |
| Draft 0.5 | 2026-11-10 | Editor's draft, review fixes | D-42 from the #92 review: OAuth calls without UCP-Agent, operator duties in the client registration, the agent host, client_id on entries, bearer-token controls, V34 and V35 |
| Draft 0.5 | 2026-11-10 | Editor's draft, naming fix | D-43: the offer state is awaiting_approval |
Source: spec/rctp-draft-0.5.md