iwant.fyi / specification

iwant.fyi demand-side protocol

Open specification for how AI agents express structured purchase intent, receive matched supply across multiple sources, and report outcomes back. MCP-native with HTTP fallback.

Version:
1.1 (Draft)
Status:
Draft for public comment
Date:
2026-05-19
License:
Apache License 2.0
Reference implementation:
iwant.fyi/api/mcp

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:

  1. Run an unstructured web search and parse merchant pages (brittle, slow, often illegal).
  2. Call a single retailer's API directly (limited inventory, no cross-merchant comparison).
  3. 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

  1. Small. v1 is intentionally minimal. Negotiation, escrow, multi-party payment, identity verification — all deferred.
  2. 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).
  3. Source-agnostic. A compliant implementation can pull supply from anywhere — affiliate feeds, direct APIs, scraped catalogs (with proper licensing), user-generated listings, anywhere.
  4. Identity-light. Users remain anonymous to the protocol by default. The originating agent is identified for attribution and outcome routing.
  5. 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

ProtocolRoleiwant.fyi demand-side protocol relationship
MCP (Anthropic)Tool/data transport for AI agentsiwant.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 merchantA 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 merchantsA 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 mandatesiwant.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 PayCard-network agent authenticationOrthogonal. 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 of price_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, fasteners
  • auto_parts — vehicle parts and components
  • outdoor — camping, hiking, hunting, fishing
  • kitchen — cooking equipment, knives, small appliances
  • electronics — consumer electronics
  • home_services — plumbing, HVAC, cleaning, moving
  • professional_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

KeyTypeDescription
price_maxinteger (cents)Maximum acceptable price. SHOULD equal or be less than Want's price_cents.
price_mininteger (cents)Minimum (useful when filtering out obviously fake or stripped items).
shipping_max_centsintegerMax acceptable shipping cost.
total_price_maxintegerMax price + shipping + taxes.

5.2 Condition constraints

KeyTypeDescription
condition_minenumMinimum acceptable condition. Order: new > like-new > good > fair > used.
condition_exactenumMust be exactly this condition. Cannot coexist with condition_min.
must_be_unopenedbooleanFor new items, must be sealed/unopened.
manufacturer_warranty_requiredbooleanMust include manufacturer warranty.

5.3 Time constraints

KeyTypeDescription
deadlineISO 8601Latest acceptable delivery/availability.
available_within_daysintegerAlternative to deadline.
return_window_days_minintegerMinimum return window.

5.4 Brand and provenance

KeyTypeDescription
brand_allowliststring[]Only accept matches in these brands.
brand_blockliststring[]Reject matches in these brands.
manufacturer_requiredstringMust be made by this manufacturer.
country_of_origin_allowliststring[] (ISO 3166)Acceptable countries of origin.

5.5 Location

Location is also in the Want's top-level location object, but constraints can sharpen:

KeyTypeDescription
pickup_requiredbooleanMust be available for in-person pickup.
shipping_requiredbooleanMust ship; no pickup-only.
local_onlybooleanRestrict 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 in auto_accept must 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 whose retrieved_at + freshness_ttl_s is 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

EventWhenRequired fields
viewedMatch was displayed to the userwant_id, match_id, agent_id, timestamp
clickedUser clicked through to merchantwant_id, match_id, agent_id, timestamp
started_checkoutUser reached merchant checkoutwant_id, match_id, agent_id, timestamp
purchasedPurchase completedwant_id, match_id, agent_id, timestamp, value_cents
abandonedUser abandoned without buyingwant_id, match_id, agent_id, timestamp
not_purchasedUser 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 }

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

MethodPathDescription
POST/v1/wantsEquivalent to demand.create_want
GET/v1/wants/{id}Equivalent to demand.get_want
POST/v1/searchEquivalent to demand.search
POST/v1/outcomesEquivalent to demand.record_outcome
GET/v1/verticalsEquivalent to demand.list_verticals
GET/v1/constraintsEquivalent to demand.list_constraints
GET/v1/healthLiveness
GET/v1/capabilitiesFeature + limit discovery (v1.1, §8.2)
POST/v1/watchesCreate a Standing Want (v1.1, §16)
GET/v1/watchesList the caller's Standing Wants (v1.1)
DELETE/v1/watches/{id}Cancel a Standing Want (v1.1)
POST/v1/watches/{id}/rotate-secretRoll 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 for GET endpoints), 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_id field 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_id or implementation-specific custom fields under origin.

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

CodeMeaning
-32000Generic unauthorized / forbidden
-32001Rate-limited
-32600Invalid request shape
-32601Method not found
-32602Invalid parameters (validation failure)
-32603Internal error
-32700Parse 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_typecodeHTTPretryable
unauthorized-32000401/403false
rate_limited-32001429true (honor retry_after_ms)
invalid_request-32600400false
method_not_found-32601404false
invalid_params-32602422false
internal-32603500true (with backoff)
upstream_unavailable-32010503true
parse_error-32700400false

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 Deprecation header or _deprecated: true marker 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_want or record_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_id matches 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:

  1. Exposes all required MCP tools defined in §8.1.
  2. Accepts Wants conforming to §4 and §5.
  3. Returns Match responses conforming to §6.
  4. Accepts outcome events conforming to §7.
  5. Authenticates requests per §10.1.
  6. 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/conformance evaluates 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.url MUST be https. Implementations MUST reject http.
  • min_score — only matches with score >= min_score are 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 recomputed v1.
  • Replay protection: receivers SHOULD reject if |now - t| > 300s. t is 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 new whsec_... 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 2xx within 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 same event_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 live GET /v1/conformance self-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_token on create_want/create_watch); demand.capabilities discovery 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_id dedupe, 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_watch tools + /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 in v1.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.