v1.1 is an additive, backward-compatible robustness pass over v1.0: signed webhook delivery for Standing Wants (§16), partial-result transparency on the Match response (§6.1), and a secret-rotation endpoint. A v1.0 client keeps working unchanged. Rationale and the full change set (including the P1/P2 items still in design) are in
docs/demand-protocol/v1.1-draft.md.
Abstract
The iwant.fyi demand-side protocol defines a standard way for AI agents to express structured purchase intent on behalf of a user, receive ranked matches from supply across multiple sources, and report outcomes back to the demand origin.
Existing agentic commerce protocols are supply-side: ACP and UCP define how an agent completes a purchase at a merchant, AP2 defines how payment is authorized, MCP defines how tools and data are accessed. None of them define how an agent expresses what its user wants in a structured, machine-matchable form, or how that demand persists across sessions and gets fulfilled across multiple sources.
This protocol fills that gap. It is small, MCP-native, transport-flexible, and intentionally agnostic about who matches the demand, who fulfills it, and how money moves.
Status of this document
This is a draft published for public comment. The protocol is implemented today by iwant.fyi as the reference implementation. Feedback, issues, and proposed extensions can be opened against this document.
The protocol is published under the Apache License 2.0. Anyone may implement, fork, extend, or commercialize the protocol or compatible implementations.
1. Introduction
1.1 The problem
Today, when a user expresses purchase intent inside an AI chat — "I need a torque wrench under $150, calibrated within the last two years" — the agent has poor options:
- Run an unstructured web search and parse merchant pages (brittle, slow, often illegal).
- Call a single retailer's API directly (limited inventory, no cross-merchant comparison).
- Use a supply-side protocol like ACP/UCP, which only covers partner merchants and only at the checkout end of the funnel.
None of these treat the user's articulated demand as a first-class object. Each chat session re-derives the user's intent from scratch. Demand doesn't persist. Constraints can't be reliably communicated. Outcomes don't flow back to the demand origin.
1.2 What the iwant.fyi demand-side protocol does
The iwant.fyi demand-side protocol defines:
- A canonical Want object — structured purchase intent, durable beyond a single chat session.
- A vocabulary of Constraints — machine-evaluable filters that go beyond keyword search (price ranges, condition floors, locations, deadlines, specs).
- A Match response shape — how supply across multiple sources is presented back to the agent.
- Outcome events — how clicks, purchases, and abandonments attribute back to the originating agent and accumulate into demand signal.
- An MCP tool surface — the canonical way to call a iwant.fyi demand-side protocol implementation.
- An HTTP/REST fallback — for agents that don't speak MCP.
1.3 What it does NOT do
The iwant.fyi demand-side protocol does not:
- Process payments — that's ACP, AP2, or direct merchant flows.
- Define a checkout experience — once a Match is selected, the agent hands off to the merchant or to a checkout protocol.
- Mandate a specific matching algorithm — implementations can compete on match quality.
- Mandate a specific business model — implementations can be free, affiliate-funded, ad-supported, or paid.
- Replace MCP — it layers on top of MCP as the transport.
1.4 Design principles
- Small. v1 is intentionally minimal. Negotiation, escrow, multi-party payment, identity verification — all deferred.
- Composable. iwant.fyi demand-side protocol complements ACP, UCP, AP2, and MCP rather than competing with them. A typical flow: user → agent → iwant.fyi demand-side protocol (find the right thing) → ACP/UCP (checkout) → AP2 (payment authorization).
- Source-agnostic. A compliant implementation can pull supply from anywhere — affiliate feeds, direct APIs, scraped catalogs (with proper licensing), user-generated listings, anywhere.
- Identity-light. Users remain anonymous to the protocol by default. The originating agent is identified for attribution and outcome routing.
- Open and forkable. Anyone can run a iwant.fyi demand-side protocol server. Anyone can extend the constraint vocabulary. Implementations are free to compete.
2. Relationship to other protocols
| Protocol | Role | iwant.fyi demand-side protocol relationship |
|---|---|---|
| MCP (Anthropic) | Tool/data transport for AI agents | iwant.fyi demand-side protocol uses MCP as its primary transport. A iwant.fyi demand-side protocol implementation IS an MCP server exposing a defined tool surface. |
| ACP (OpenAI/Stripe/PayPal) | Agentic Checkout — completing a purchase at a merchant | A iwant.fyi demand-side protocol Match can point to an ACP-compatible merchant. Agent hands off post-match. |
| UCP (Google/Shopify) | Universal Commerce — discovery + checkout at partner merchants | A iwant.fyi demand-side protocol implementation can include UCP merchants as a supply source. UCP results normalize into Match shape. |
| AP2 (Google → FIDO) | Agent Payments — cryptographic intent/cart/payment mandates | iwant.fyi demand-side protocol Want objects can produce AP2 Intent Mandates as a side effect when a user pre-authorizes a purchase. |
| Visa TAP / Mastercard Agent Pay | Card-network agent authentication | Orthogonal. iwant.fyi demand-side protocol does not handle payment credentials. |
The mental model: iwant.fyi demand-side protocol is upstream of all the supply/checkout protocols. Agents express demand here, then hand off to whichever supply/payment protocol the user-selected match requires.
3. Terminology
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.
- Agent — Software acting on behalf of a user. Typically an AI assistant.
- Want — A structured purchase intent expressed by an agent on behalf of a user.
- Constraint — A machine-evaluable filter attached to a Want.
- Match — A specific piece of supply (a product or service listing) that satisfies a Want.
- Supply Source — A merchant, marketplace, or other origin of matchable inventory.
- Implementation — A server that speaks the iwant.fyi demand-side protocol. The iwant.fyi MCP server is the reference Implementation.
- Origin Agent — The agent that first created a Want. Outcomes attribute back to this agent.
4. The Want object
A Want is the central data structure. It represents one piece of structured purchase intent.
4.1 Required fields
{
"id": "string (UUID v4)",
"protocol_version": "1.0",
"title": "string (5–200 chars)",
"price_cents": "integer (>= 0)",
"price_currency": "string (ISO 4217, e.g. \"USD\")"
}
id— Implementation-assigned UUID. Stable across the Want's lifetime.protocol_version— The iwant.fyi demand-side protocol version this Want adheres to. v1 implementations MUST accept"1.0".title— Short, user-facing description. Length 5–200 characters. MUST be a single line.price_cents— The buyer's stated budget or willingness-to-pay, in the smallest unit ofprice_currency. For a $150 budget in USD,price_cents = 15000.price_currency— ISO 4217 currency code.
4.2 Optional fields
{
"description": "string (max 2000)",
"category": "goods | services | other",
"vertical": "string (free-form, recommended values below)",
"mode": "new | used | any",
"location": { "text": "string", "lat": number, "lng": number, "radius_km": number },
"constraints": { "rules": {...}, "negotiable": [...], "auto_accept": {...} },
"origin": { "agent_id": "string", "agent_name": "string", "session_id": "string" },
"expires_at": "string (ISO 8601)",
"created_at": "string (ISO 8601)",
"updated_at": "string (ISO 8601)"
}
category
One of goods, services, other. Implementations SHOULD use this for top-level routing. The protocol does not define richer top-level categories in v1.
vertical
A free-form hint identifying a more specific vertical within category. Recommended values for v1 (extensible by implementations):
tools— hand tools, power tools, hardware, fastenersauto_parts— vehicle parts and componentsoutdoor— camping, hiking, hunting, fishingkitchen— cooking equipment, knives, small applianceselectronics— consumer electronicshome_services— plumbing, HVAC, cleaning, movingprofessional_services— legal, accounting, design
Implementations MAY define additional vertical values. Wants without a vertical SHOULD still match within category.
mode
One of new, used, any. This is the buyer's preference for supply mode (catalog-driven vs reverse-auction / peer-to-peer). Default if omitted: any.
new— Buyer prefers catalog supply from merchants.used— Buyer prefers used / peer-to-peer supply.any— Buyer accepts either; Implementation MAY rank but MUST return both.
location
{
"text": "Brooklyn, NY",
"lat": 40.6782,
"lng": -73.9442,
"radius_km": 25
}
All sub-fields OPTIONAL. text is the buyer's stated location (city, neighborhood, postal code, etc.). lat/lng are coordinates if known. radius_km is the maximum acceptable distance for location-relevant supply (mainly used goods, services). Default radius if omitted: implementation-defined (50km is recommended for mode: used; ignored for mode: new unless explicitly set).
constraints
The machine-evaluable filter object. See Section 5.
origin
{
"agent_id": "string",
"agent_name": "string",
"session_id": "string"
}
The agent that originated this Want. All sub-fields OPTIONAL but RECOMMENDED.
agent_id— Stable identifier of the originating agent (e.g., an MCP agent API key prefix, an OAuth client ID).agent_name— Human-readable agent name for display.session_id— Opaque identifier for the originating session, useful for grouping related Wants.
Outcomes (clicks, purchases) MUST attribute back to origin.agent_id when present.
expires_at
ISO 8601 timestamp. If present, Implementations MUST NOT surface the Want as "open" past this time. If absent, Implementation default expiration applies (RECOMMENDED: 30 days).
created_at, updated_at
ISO 8601 timestamps set by the Implementation.
4.3 Example Want
{
"id": "8f3c2e10-3b4a-4d2e-9f8c-1a2b3c4d5e6f",
"protocol_version": "1.0",
"title": "Torque wrench, 1/4\" drive, 25–100 ft-lb",
"description": "Calibrated within last 2 years. Open to new or used in good condition. Need by next weekend.",
"category": "goods",
"vertical": "tools",
"mode": "any",
"price_cents": 15000,
"price_currency": "USD",
"location": {
"text": "Brooklyn, NY",
"lat": 40.6782,
"lng": -73.9442,
"radius_km": 50
},
"constraints": {
"rules": {
"price_max": 15000,
"condition_min": "good",
"deadline": "2026-05-31T00:00:00Z",
"specs": {
"drive_size": "1/4\"",
"torque_range_ftlb": [25, 100],
"calibration_within_days": 730
}
},
"negotiable": ["price_max", "deadline"],
"auto_accept": null
},
"origin": {
"agent_id": "claude_anthropic_001",
"agent_name": "Claude (anthropic)",
"session_id": "sess_abc123"
},
"expires_at": "2026-06-19T00:00:00Z",
"created_at": "2026-05-19T15:00:00Z"
}
5. Constraint vocabulary
The constraints.rules object holds typed filters. v1 defines the following keys; implementations MAY extend with namespaced custom keys (e.g., "x_iwantfyi_priority": "high").
5.1 Pricing constraints
| Key | Type | Description |
|---|---|---|
price_max | integer (cents) | Maximum acceptable price. SHOULD equal or be less than Want's price_cents. |
price_min | integer (cents) | Minimum (useful when filtering out obviously fake or stripped items). |
shipping_max_cents | integer | Max acceptable shipping cost. |
total_price_max | integer | Max price + shipping + taxes. |
5.2 Condition constraints
| Key | Type | Description |
|---|---|---|
condition_min | enum | Minimum acceptable condition. Order: new > like-new > good > fair > used. |
condition_exact | enum | Must be exactly this condition. Cannot coexist with condition_min. |
must_be_unopened | boolean | For new items, must be sealed/unopened. |
manufacturer_warranty_required | boolean | Must include manufacturer warranty. |
5.3 Time constraints
| Key | Type | Description |
|---|---|---|
deadline | ISO 8601 | Latest acceptable delivery/availability. |
available_within_days | integer | Alternative to deadline. |
return_window_days_min | integer | Minimum return window. |
5.4 Brand and provenance
| Key | Type | Description |
|---|---|---|
brand_allowlist | string[] | Only accept matches in these brands. |
brand_blocklist | string[] | Reject matches in these brands. |
manufacturer_required | string | Must be made by this manufacturer. |
country_of_origin_allowlist | string[] (ISO 3166) | Acceptable countries of origin. |
5.5 Location
Location is also in the Want's top-level location object, but constraints can sharpen:
| Key | Type | Description |
|---|---|---|
pickup_required | boolean | Must be available for in-person pickup. |
shipping_required | boolean | Must ship; no pickup-only. |
local_only | boolean | Restrict to location.radius_km. |
5.6 Vertical-specific specs
Vertical-specific constraints live under constraints.rules.specs as a free-form object. Implementations SHOULD support common patterns:
For vertical: auto_parts:
"specs": {
"year_make_model": { "year": 2018, "make": "Honda", "model": "Civic", "trim": "EX" },
"vin": "1HGFA16526L000000",
"part_number": "string",
"oem_required": true
}
For vertical: tools:
"specs": {
"drive_size": "1/4\"",
"voltage": 18,
"torque_range_ftlb": [25, 100],
"calibration_within_days": 730,
"cordless_required": false
}
For vertical: outdoor:
"specs": {
"capacity_persons": 2,
"weight_max_grams": 1500,
"temperature_rating_f": 20,
"season": "3-season"
}
Implementations SHOULD document their supported spec keys per vertical. Unknown spec keys SHOULD be treated as "informational, not filtering" rather than rejecting the Want.
5.7 Negotiable fields and auto-accept
"constraints": {
"rules": { ... },
"negotiable": ["price_max", "deadline", "shipping_max_cents"],
"auto_accept": {
"price_lte": 12000,
"condition_min": "like-new",
"seller_trust_gte": 0.85
}
}
negotiable— Array of constraint keys the buyer is willing to flex on. Implementations MAY surface these to seller agents for counter-offers.auto_accept— Rules under which the Implementation MAY auto-accept a Match without further agent interaction. All conditions inauto_acceptmust be satisfied. v1 implementations MAY ignore this object; v2 makes it normative for negotiation.
6. The Match response
When an agent queries for matches against a Want, the Implementation returns a Match response.
6.1 Response shape
{
"want_id": "string",
"matches": [Match, ...],
"match_count": integer,
"sources_consulted": [string, ...],
"degraded": boolean, // v1.1 — true iff one+ attempted sources did not contribute
"incomplete_source_count": integer, // v1.1 — count of failed sources (no names, no reasons)
"generated_at": "ISO 8601"
}
Partial-result transparency (v1.1). Supply is fanned out across multiple sources; a source MAY
time out or error on any given request. An Implementation MUST NOT silently present a partial result
as complete. When one or more attempted sources fail, it MUST set degraded: true and report how many
failed in incomplete_source_count. The signal is deliberately opaque — it carries no source
identifiers and no failure reasons, only enough for the agent to know the result may be incomplete and
MAY re-query. When every attempted source responds, degraded is false and
incomplete_source_count is 0. v1.0 clients ignore both fields.
6.2 Match object
{
"id": "string",
"source": "string",
"source_id": "string",
"title": "string",
"description": "string (optional)",
"price_cents": integer,
"price_currency": "string",
"shipping_cents": integer,
"total_price_cents": integer,
"condition": "new | like-new | good | fair | used | unknown",
"mode": "new | used",
"location": "string (optional)",
"images": [string, ...],
"url": "string (REQUIRED — link to fulfill)",
"direct_url": "string (optional — unwrapped merchant URL for verification)",
"score": number,
"reasons": [string, ...],
"attributes": object,
"supply_metadata": object
}
id— Implementation-assigned identifier for this match instance.source— Origin of the supply (e.g.,"cj","impact","shopify","ebay","native").source_id— Source-specific product identifier.url— The URL the agent SHOULD surface to the user. This MAY include affiliate tracking by the Implementation; agents MUST NOT strip tracking parameters.direct_url— Optional, the unwrapped merchant URL. Useful for verification or for agents whose users prefer direct merchant relationships.score— Match quality from 0 to 1. Implementation-defined scoring. v1 does not mandate an algorithm.reasons— Human-readable strings explaining why this match scored. Helps agents explain to users.attributes— Source-specific fields (brand, manufacturer, GTIN, part_number, etc.). Free-form; agents MAY display.supply_metadata— Implementation- or source-specific metadata. v1.1 reserves well-known (still optional) keys so agents can trust-weight a match:retrieved_at(ISO 8601, when the supply was last fetched),freshness_ttl_s(how long to treat it as fresh),in_stock(true | false | "unknown"),enriched(boolean, whether specs were normalized),seller_trust(0..1). Agents SHOULD down-weight or flag a match whoseretrieved_at + freshness_ttl_sis in the past.
6.3 Ranking and pagination
Matches MUST be returned in descending score order. v1.1 mandates a deterministic TOTAL order so
pages are stable and ties don't reshuffle between calls: score desc, then source asc, then
source_id asc. Implementations SHOULD return at most 50 matches per response by default.
Implementations MAY support cursor pagination: the Match response MAY include next_cursor (an opaque
string, null when no more pages), and demand.search / POST /v1/search accept a cursor echoed
back verbatim to fetch the next page. The cursor encodes the last item's ordering key; agents MUST
treat it as opaque and MUST NOT construct or parse it.
6.4 Example response
{
"want_id": "8f3c2e10-3b4a-4d2e-9f8c-1a2b3c4d5e6f",
"matches": [
{
"id": "match_001",
"source": "cj",
"source_id": "home-depot-sku-12345",
"title": "Husky 1/4\" Drive Torque Wrench 25-100 ft-lb",
"price_cents": 8999,
"price_currency": "USD",
"shipping_cents": 0,
"total_price_cents": 8999,
"condition": "new",
"mode": "new",
"url": "https://www.anrdoezrs.net/links/PUB/.../homedepot.com/p/12345",
"direct_url": "https://www.homedepot.com/p/12345",
"score": 0.87,
"reasons": ["price under budget", "exact drive size", "torque range match"],
"attributes": {
"brand": "Husky",
"manufacturer_warranty_months": 12
},
"supply_metadata": {
"feed_refresh": "2026-05-19T08:00:00Z",
"affiliate_network": "cj"
}
},
{
"id": "match_002",
"source": "ebay",
"source_id": "ebay-listing-789",
"title": "Snap-on TQR100 Torque Wrench (calibrated 2025)",
"price_cents": 12500,
"condition": "like-new",
"mode": "used",
"url": "https://rover.ebay.com/rover/1/.../itm/789",
"score": 0.81,
"reasons": ["calibrated within 1 year", "premium brand", "local pickup available"]
}
],
"match_count": 2,
"sources_consulted": ["cj", "ebay", "shopify"],
"generated_at": "2026-05-19T15:01:00Z"
}
7. Outcome events
After a Match is surfaced to the user, the agent SHOULD report outcome events back to the Implementation. This closes the demand-signal loop.
7.1 Event shape
{
"want_id": "string",
"match_id": "string",
"event": "viewed | clicked | started_checkout | purchased | abandoned | not_purchased",
"agent_id": "string (REQUIRED if Want had origin.agent_id)",
"timestamp": "ISO 8601",
"value_cents": integer,
"metadata": object
}
7.2 Event types
| Event | When | Required fields |
|---|---|---|
viewed | Match was displayed to the user | want_id, match_id, agent_id, timestamp |
clicked | User clicked through to merchant | want_id, match_id, agent_id, timestamp |
started_checkout | User reached merchant checkout | want_id, match_id, agent_id, timestamp |
purchased | Purchase completed | want_id, match_id, agent_id, timestamp, value_cents |
abandoned | User abandoned without buying | want_id, match_id, agent_id, timestamp |
not_purchased | User explicitly rejected match (negative signal) | want_id, match_id, agent_id, timestamp, metadata.reason (RECOMMENDED) |
7.3 Purpose
Outcome events drive:
- Match quality improvement (training data for the matching engine).
- Demand-signal aggregation (what was wanted but unmet at scale).
- Attribution for any revenue-share or commission-distribution arrangements between Implementations and originating agents.
Implementations MUST treat outcome events as informational and idempotent. Re-sending the same event SHOULD be a no-op.
8. MCP tool surface
A iwant.fyi demand-side protocol Implementation IS an MCP server. It MUST expose the following tools.
8.1 Required tools
demand.create_want
Create a new Want and return matches.
Input:
{
"title": "string (required)",
"price_cents": "integer (required)",
"price_currency": "string (required)",
"description": "string (optional)",
"category": "goods | services | other (optional)",
"vertical": "string (optional)",
"mode": "new | used | any (optional)",
"location": "object (optional)",
"constraints": "object (optional)",
"origin": "object (optional)"
}
Output:
{
"want": Want,
"matches": MatchResponse
}
demand.search
Run matching against existing supply without persisting a Want. For ephemeral queries.
Input: Same as create_want.
Output: Just the MatchResponse, no persisted Want.
demand.get_want
Retrieve a Want by ID, including any persisted matches.
Input: { "want_id": "string" }
Output: { "want": Want, "matches": MatchResponse | null }
demand.record_outcome
Report an outcome event.
Input: Event object (see §7.1).
Output: { "received": true }
8.2 Recommended tools
demand.list_verticals
Return the set of verticals this Implementation supports, with descriptive metadata. Useful for agent discovery.
Output:
{
"verticals": [
{
"id": "tools",
"display_name": "Tools and Hardware",
"description": "Hand and power tools, fasteners, garage equipment",
"supported_spec_keys": ["drive_size", "voltage", "torque_range_ftlb", ...],
"active_supply_sources": ["cj", "shopify", "ebay"]
},
...
]
}
demand.list_constraints
Return the constraint vocabulary supported by this Implementation, including custom (x_*) keys.
demand.health
Liveness + readiness check.
demand.capabilities (v1.1)
Discover which protocol features this Implementation supports and its operational limits, so agents adapt at runtime instead of hard-coding assumptions:
{
"protocol_version": "1.1",
"supported_versions": ["1.0", "1.1"],
"features": ["webhooks", "idempotency", "failure_transparency", "error_taxonomy", "capabilities", "http_fallback"],
"limits": { "max_matches_per_response": 50, "min_check_interval_seconds": 300,
"rate_limit_requests_per_minute": 60, "max_active_watches_per_agent": 100,
"max_client_token_length": 128 },
"tools": ["demand.create_want", "..."]
}
demand.create_watch / demand.list_watches / demand.cancel_watch (v1.1)
Create, list, and cancel Standing Wants — persisted Wants the Implementation re-matches on a
schedule, pushing only new qualifying matches to a webhook. See §16. demand.create_watch returns the
webhook signing secret exactly once.
8.3 Tool naming convention
Tools SHOULD use the demand. prefix to disambiguate from other MCP tools in the agent's environment. Implementations MAY also expose ungated aliases (e.g., create_want without the prefix) for backwards compatibility with simpler MCP clients.
8.4 Idempotency (v1.1)
demand.record_outcome is already idempotent (§7.3). v1.1 extends this to the other write tools:
demand.create_want and demand.create_watch accept an optional client_token (string, <=128
chars; HTTP transports MAY use an Idempotency-Key header instead). For a given (authenticated agent,
tool, client_token), an Implementation MUST return the result of the first call and MUST NOT create
a second resource, for a retention window of at least 24 hours. Absent a client_token, behavior is
unchanged. This makes the buyer-side write path safe to retry after a network timeout.
9. HTTP/REST fallback
Implementations SHOULD provide an HTTP fallback for agents that cannot speak MCP.
9.1 Required endpoints
| Method | Path | Description |
|---|---|---|
| POST | /v1/wants | Equivalent to demand.create_want |
| GET | /v1/wants/{id} | Equivalent to demand.get_want |
| POST | /v1/search | Equivalent to demand.search |
| POST | /v1/outcomes | Equivalent to demand.record_outcome |
| GET | /v1/verticals | Equivalent to demand.list_verticals |
| GET | /v1/constraints | Equivalent to demand.list_constraints |
| GET | /v1/health | Liveness |
| GET | /v1/capabilities | Feature + limit discovery (v1.1, §8.2) |
| POST | /v1/watches | Create a Standing Want (v1.1, §16) |
| GET | /v1/watches | List the caller's Standing Wants (v1.1) |
| DELETE | /v1/watches/{id} | Cancel a Standing Want (v1.1) |
| POST | /v1/watches/{id}/rotate-secret | Roll the webhook signing secret (v1.1, §16.3) |
Request and response bodies are JSON and follow the same schemas as the MCP tool surface.
9.2 Authentication
Agents authenticate via:
Authorization: Bearer <api_key>header (RECOMMENDED), OR?api_key=<key>query parameter (acceptable forGETendpoints), OR- MCP-native auth when called via MCP transport.
API key issuance is implementation-specific. iwant.fyi's reference implementation issues keys prefixed iwant_ak_.
10. Identity and attribution
10.1 Agent identity
Agents identify themselves via:
- The API key used for authentication (RECOMMENDED).
- The
origin.agent_idfield on the Want (REQUIRED when reporting outcomes for that Want).
Implementations MUST NOT require users to disclose personal identity. The user is anonymous to the protocol by default.
10.2 User identity (optional)
Implementations MAY support user accounts (so users can persist their own Wants and receive alerts across agents). When supported:
- User identity is opaque to the agent (the agent only sees what the user has shared).
- An agent MAY pass an opaque user reference via
origin.session_idor implementation-specific custom fields underorigin.
10.3 Attribution
Outcomes MUST attribute back to the origin.agent_id of the Want, when present. This enables:
- Performance reporting for the originating agent.
- Revenue-share or commission distribution between Implementation and originating agent (if any such arrangement exists).
- Demand-signal aggregation by agent surface.
11. Errors
Error responses follow JSON-RPC 2.0 convention when transported over MCP:
{
"jsonrpc": "2.0",
"id": "...",
"error": {
"code": -32000,
"message": "Unauthorized",
"data": { "reason": "API key invalid" }
}
}
11.1 Standard error codes
| Code | Meaning |
|---|---|
-32000 | Generic unauthorized / forbidden |
-32001 | Rate-limited |
-32600 | Invalid request shape |
-32601 | Method not found |
-32602 | Invalid parameters (validation failure) |
-32603 | Internal error |
-32700 | Parse error |
HTTP transport uses standard HTTP status codes (400/401/403/429/500) plus a JSON body of the same error shape under error.
11.2 Retryable error taxonomy (v1.1)
Integer codes are opaque to an agent and say nothing about whether a retry is meaningful. v1.1
REQUIRES every error's data to carry a stable machine error_type, a retryable boolean, and --
when throttled/backpressured -- a retry_after_ms:
{ "error": { "code": -32001, "message": "Rate limit exceeded",
"data": { "error_type": "rate_limited", "retryable": true, "retry_after_ms": 4200 } } }
error_type | code | HTTP | retryable |
|---|---|---|---|
unauthorized | -32000 | 401/403 | false |
rate_limited | -32001 | 429 | true (honor retry_after_ms) |
invalid_request | -32600 | 400 | false |
method_not_found | -32601 | 404 | false |
invalid_params | -32602 | 422 | false |
internal | -32603 | 500 | true (with backoff) |
upstream_unavailable | -32010 | 503 | true |
parse_error | -32700 | 400 | false |
Agents SHOULD branch on error_type. On rate_limited, HTTP transports MUST also send the standard
Retry-After header. The numeric code is retained for JSON-RPC conformance. error_type is
extensible with an x_ prefix.
12. Versioning and extensibility
12.1 Semantic versioning
The protocol follows semver. v1.x changes are backwards-compatible. Breaking changes ship as v2.
12.2 Adding fields
Implementations MAY add fields to any object. New fields:
- MUST NOT use top-level names defined by the protocol.
- SHOULD use the
x_<implementation_or_vendor>_<field>namespace (e.g.,x_iwantfyi_priority,x_anthropic_session_token).
12.3 Extending verticals and constraints
Implementations MAY define new verticals and new constraint keys. Implementations SHOULD expose these via demand.list_verticals and demand.list_constraints so agents can discover capabilities at runtime.
12.4 Deprecation
When the protocol deprecates a field or method:
- v1.x continues to support it.
- It is removed in v2 or later.
- A
Deprecationheader or_deprecated: truemarker SHOULD accompany responses that include deprecated fields.
13. Privacy considerations
- Users SHOULD be anonymous to the protocol by default.
- Implementations MUST NOT log Want titles, descriptions, or constraint values for purposes other than serving the Want, improving match quality, and aggregate demand-signal analytics — and MUST disclose any such use in their privacy policy.
- Implementations MUST allow users to delete their persisted Wants if user accounts are supported.
- Implementations MUST NOT share individual Want data with third parties without explicit user consent. Aggregate, anonymized demand signal MAY be shared.
- Implementations SHOULD NOT include direct user identifiers in outcome events unless the user has opted in.
14. Security considerations
- Implementations MUST authenticate every request (no anonymous access for
create_wantorrecord_outcome). - API keys MUST be transmitted over TLS only.
- Implementations SHOULD support rate limiting per API key.
- Implementations MUST validate all incoming Wants against the schema and reject malformed inputs with appropriate errors.
- Implementations SHOULD treat outcome events as untrusted input and validate that the
agent_idmatches the authenticated API key. - Implementations SHOULD support API key revocation and rotation.
15. Conformance
An Implementation is iwant.fyi demand-side protocol v1.0 compliant if it:
- Exposes all required MCP tools defined in §8.1.
- Accepts Wants conforming to §4 and §5.
- Returns Match responses conforming to §6.
- Accepts outcome events conforming to §7.
- Authenticates requests per §10.1.
- Supports the error shape in §11.
Implementations are iwant.fyi demand-side protocol v1.0 compliant with HTTP fallback if they additionally expose the endpoints in §9.
A conformance test kit accompanying this spec exercises these behaviors (see tools/conformance-kit/).
v1.1 makes conformance verifiable rather than asserted:
- Machine-readable JSON Schema for the core objects is published at
/.well-known/iwantfyi/schemas/1.1/{want,match,match-response,outcome}.json. - A live self-report at
GET /v1/conformanceevaluates the §15 checklist against the running tool surface + feature set (required/recommended tools present, transparency/error-taxonomy/webhooks/ idempotency supported) so registries and partners can check the claim directly.
16. Notifications and Webhooks — Standing Wants (v1.1)
v1.0 is request/response only. v1.1 adds the Standing Want: a persisted Want that the Implementation re-matches on a schedule, pushing only matches not previously delivered for that Standing Want — scoring at or above a threshold — to the caller's webhook. This turns one-shot search into durable, cross-session demand. Webhook delivery is authenticated so a receiver can verify it and reject forged or replayed payloads.
16.1 The Standing Want object
{
"id": "string (UUID)",
"want": Want, // a full §4 Want object
"notify": {
"transport": "webhook", // v1.1 defines "webhook"; others reserved
"url": "string (REQUIRED, https only)",
"min_score": "number (0..1, default 0.5)",
"check_interval_seconds": "integer (>= 300, default 3600)"
},
"active": "boolean",
"created_at": "ISO 8601",
"last_checked_at": "ISO 8601 | null"
}
notify.urlMUST behttps. Implementations MUST rejecthttp.min_score— only matches withscore >= min_scoreare delivered.check_interval_seconds— Implementations MUST NOT check more often than this (minimum 300s) and MAY check less often.- An Implementation MUST deliver each
(standing_want, source, source_id)at most once unless the match materially changes (§16.5).
16.2 Delivery payload
{
"type": "want.matches",
"event_id": "string (UUID v4, unique per logical delivery)",
"standing_want_id": "string",
"want_id": "string",
"query": "string",
"new_match_count": "integer",
"matches": [Match, ...],
"generated_at": "ISO 8601"
}
event_id is unique per logical delivery; retries (§16.4) reuse the same event_id. Receivers MUST be
idempotent on event_id.
16.3 Signature (REQUIRED)
Every webhook request MUST carry a signature so the receiver can verify authenticity. The scheme is symmetric HMAC (the widely-deployed Stripe model).
Headers:
X-IWantFyi-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
X-IWantFyi-Event: want.matches
User-Agent: iwant.fyi-webhook/1.1
Signing:
signed_payload = "<t>" + "." + <raw_request_body>
v1 = hex( HMAC_SHA256(key = standing_want.webhook_secret, message = signed_payload) )
webhook_secret(whsec_...) is generated by the Implementation at Standing Want creation and returned to the caller exactly once. It is never returned again. The caller stores it and uses it to verify each delivery with a constant-time compare of the recomputedv1.- Replay protection: receivers SHOULD reject if
|now - t| > 300s.tis inside the signed payload, so it cannot be altered without invalidating the signature. - Implementations MUST support secret rotation (
POST /v1/watches/{id}/rotate-secret): a newwhsec_...is returned once and the previous secret stops validating immediately.
16.4 Delivery guarantees and retries
- Delivery is at-least-once; receivers MUST dedupe on
event_id. - A delivery succeeds on a
2xxwithin the Implementation's timeout (RECOMMENDED 5s). - On non-2xx / timeout / connection error, Implementations SHOULD retry with exponential backoff
(RECOMMENDED
+30s, +5m, +30m, +2h, +6h), reusing the sameevent_id. After exhausting retries an Implementation MAY auto-pause the Standing Want and surface that status.
16.5 Re-notification on material change
The default is notify-once per (standing_want, source, source_id). An Implementation MAY re-notify a
previously-seen match if a material field changed (RECOMMENDED triggers: price_cents dropped, or
in_stock flipped false→true). Such a re-notification MUST use a distinct event_id.
16.6 Backward compatibility
Standing Wants are entirely additive. A v1.0 client that never calls the watch tools/endpoints is unaffected. A v1.1 implementation MAY deliver unsigned to a watch created before a secret existed, until that watch's secret is set via rotation.
17. License
This specification is licensed under the Apache License 2.0.
Implementations of the iwant.fyi demand-side protocol are not required to be open source. Any party may implement, fork, extend, or commercialize the protocol or compatible implementations without restriction beyond the Apache License terms applicable to the spec text itself.
18. Appendix: Complete flow example
End-to-end agent interaction with a iwant.fyi demand-side protocol Implementation:
Step 1 — Agent receives user prompt:
User: "I need a torque wrench for my Honda Civic — something good but not pro-shop expensive. Under $150."
Step 2 — Agent calls demand.create_want via MCP:
{
"method": "demand.create_want",
"params": {
"title": "Torque wrench, 1/4\" drive, mid-range for Honda Civic maintenance",
"description": "User wants quality but not pro-shop pricing. Good condition acceptable.",
"category": "goods",
"vertical": "auto_parts",
"mode": "any",
"price_cents": 15000,
"price_currency": "USD",
"constraints": {
"rules": {
"price_max": 15000,
"condition_min": "good",
"specs": {
"drive_size": "1/4\"",
"torque_range_ftlb": [20, 100]
}
},
"negotiable": ["price_max"]
},
"origin": {
"agent_id": "claude_anthropic_001",
"agent_name": "Claude",
"session_id": "sess_abc123"
}
}
}
Step 3 — Implementation returns matches:
3 matches: Husky ($89, new, Home Depot), Snap-on used ($125, like-new, eBay), Tekton ($65, new, Amazon).
Step 4 — Agent surfaces matches to user, who picks the Snap-on:
{
"method": "demand.record_outcome",
"params": {
"want_id": "8f3c2e10-...",
"match_id": "match_002",
"event": "clicked",
"agent_id": "claude_anthropic_001",
"timestamp": "2026-05-19T15:05:00Z"
}
}
Step 5 — User completes purchase on eBay. Agent reports:
{
"method": "demand.record_outcome",
"params": {
"want_id": "8f3c2e10-...",
"match_id": "match_002",
"event": "purchased",
"agent_id": "claude_anthropic_001",
"timestamp": "2026-05-19T15:18:00Z",
"value_cents": 12500
}
}
Step 6 — Implementation:
- Marks the Want fulfilled.
- Attributes the conversion to
claude_anthropic_001. - Routes any affiliate commission according to its business model.
- Feeds the outcome into match-quality training data.
19. Appendix: Changelog
- 1.1 P2 (2026-06-07) — Credibility/ecosystem additions: deterministic total order + cursor
pagination (§6.3); well-known provenance/freshness keys in
supply_metadata(§6.2); published JSON Schema artifacts + a liveGET /v1/conformanceself-report (§15). All additive/backward-compatible. - 1.1 P1 (2026-06-07) — Agent-resilience additions: retryable error taxonomy (§11.2,
error_type+retryable+retry_after_ms, 429/Retry-After); idempotency keys (§8.4,client_tokenon create_want/create_watch);demand.capabilitiesdiscovery tool +/v1/capabilities(§8.2). All additive/backward-compatible. - 1.1 (2026-06-07) — Additive robustness pass. Standing Wants + signed webhook delivery (§16,
symmetric HMAC-SHA256, one-time secret, replay window,
event_iddedupe, at-least-once retry, secret rotation); opaque partial-result transparency on the Match response (degraded+incomplete_source_count, §6.1);demand.create_watch/list_watches/cancel_watchtools +/v1/watches*endpoints. Fully backward-compatible with 1.0. Remaining v1.1 design (retryable error taxonomy,demand.capabilities, idempotency keys, deterministic pagination, provenance keys, published JSON Schema + conformance kit) tracked inv1.1-draft.md. - 1.0 (2026-05-19) — Initial draft. Want schema, constraint vocabulary, match response, outcome events, MCP tool surface, HTTP fallback, identity, errors, versioning, privacy, security, conformance.
20. Open questions
These are intentionally unresolved and may be addressed in a later v1.1.x or v2:
- Negotiation — How seller agents counter-offer against a Want. v2.
- Multi-step demand — How a Want with sub-Wants (a kit, a project, a procurement bundle) is expressed.
- Escrow / trust mechanics — How buyer protection is signaled in a Match.
- Cross-Implementation federation — Can Implementations forward Wants to each other for broader supply coverage?
- Agent revenue-share standardization — Whether commission attribution between Implementation and originating agent should be part of the protocol or strictly an implementation concern.
- Demand signal data licensing — Whether aggregated demand signal can be shared between Implementations, and under what terms.
Feedback on any of these is welcome.