<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harman Singh</title>
    <description>The latest articles on DEV Community by Harman Singh (@harrymakez).</description>
    <link>https://dev.to/harrymakez</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4138262%2F1be03496-b769-4f67-8921-d3f232964a27.png</url>
      <title>DEV Community: Harman Singh</title>
      <link>https://dev.to/harrymakez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harrymakez"/>
    <language>en</language>
    <item>
      <title>How we built a stateless MCP server for boutique stock</title>
      <dc:creator>Harman Singh</dc:creator>
      <pubDate>Tue, 29 Sep 2026 10:56:21 +0000</pubDate>
      <link>https://dev.to/harrymakez/how-we-built-a-stateless-mcp-server-for-boutique-stock-5hme</link>
      <guid>https://dev.to/harrymakez/how-we-built-a-stateless-mcp-server-for-boutique-stock-5hme</guid>
      <description>&lt;p&gt;Our MCP server at &lt;code&gt;https://shopwithin.co/mcp&lt;/code&gt; exposes 6 tools, and none of them can reserve, hold or charge anything. An agent asks what is in stock at independent boutiques. It gets back pieces, sizes, prices and the product page for each one.&lt;/p&gt;

&lt;p&gt;We read every number below from production on 27 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; Streamable HTTP, stateless, no account needed; the handshake echoes any protocol version the SDK knows and answers &lt;code&gt;2025-11-25&lt;/code&gt; to any other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools:&lt;/strong&gt; 6, every one annotated as read only, idempotent and closed world&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources:&lt;/strong&gt; 3 listed, plus 1 template that returns a journal guide as markdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shelves:&lt;/strong&gt; 2 boutiques live, holding 3,699 and 930 pieces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pages:&lt;/strong&gt; up to 24 pieces a call, with offsets up to 480&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photographs:&lt;/strong&gt; visual search takes images under 1.5 MB and never stores them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One server per request
&lt;/h2&gt;

&lt;p&gt;The endpoint is a route in our React Router app on Vercel. Each POST builds a fresh &lt;code&gt;McpServer&lt;/code&gt; and transport, answers one message, then throws both away.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports" rel="noopener noreferrer"&gt;Streamable HTTP transport&lt;/a&gt; makes sessions optional. The SDK has a stateless mode for exactly this shape, so no session store has to live between function invocations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// consumer-web/app/routes/mcp.tsx, trimmed&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createWithinMcpServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebStandardStreamableHTTPServerTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sessionIdGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// stateless: no session ids&lt;/span&gt;
    &lt;span class="na"&gt;enableJsonResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// one JSON body, no event stream&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the JSON body is complete here, so closing never cuts an answer&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plain GET returns a small JSON descriptor with the tool names, for a person with curl. DELETE answers 405, since a stateless server has no session to end. A token bucket per IP, with a global ceiling, limits the calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six tools, named for the shopper
&lt;/h2&gt;

&lt;p&gt;A model chooses a tool from its description, so ours say when to call it, in a shopper's words.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search_pieces&lt;/code&gt; finds pieces from a plain sentence, or from brands, categories, sizes, boutiques, a city and a price range.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_piece&lt;/code&gt; reads one piece in full, with every size and the units in stock.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;similar_pieces&lt;/code&gt; returns alternatives to an anchor piece across every boutique.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_boutiques&lt;/code&gt; names the shops by city, with pieces live and how soon a piece is ready to collect.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;boutique_shelf&lt;/code&gt; reads one shop's shelf, newest first, with an optional search inside it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;visual_search&lt;/code&gt; matches a photograph, a chat upload, or a TikTok or Pinterest link.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A piece is &lt;code&gt;boutique_domain&lt;/code&gt; plus &lt;code&gt;id&lt;/code&gt; across the tools, so a model chains a search into &lt;code&gt;get_piece&lt;/code&gt; without parsing a URL. One registration, trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// consumer-web/app/lib/mcp/server.server.ts, trimmed&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boutique_shelf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;One boutique's shelf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;One boutique's shelf right now, newest first, with sizes and prices; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a query searches inside that shop only. Use this when a shopper names a specific boutique...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;boutique_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;boutique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BoutiqueSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;pieces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PieceSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;next_offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;view_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;openWorldHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;idempotentHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// the ChatGPT widget keys, covered below&lt;/span&gt;
    &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;shelfMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reading the boutique's shelf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read the boutique's shelf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boutique_shelf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// read the feed, shape each piece, return { content, structuredContent }&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server instructions, sent back in the handshake, include this line: "never state availability, sizes, or prices that a tool did not return." The &lt;code&gt;search_pieces&lt;/code&gt; description ends: "Do not use for general style advice or for mass market retailers."&lt;/p&gt;

&lt;h2&gt;
  
  
  How a model asks what a boutique has
&lt;/h2&gt;

&lt;p&gt;This is a real call against production, run on 27 September 2026. It asks WDLT117's shelf for MM6 pieces under $300.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://shopwithin.co/mcp &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    -H 'content-type: application/json' \
    -H 'accept: application/json, text/event-stream' \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"boutique_shelf",
&lt;/span&gt;&lt;span class="gp"&gt;         "arguments":{"boutique_domain":"wdlt117.myshopify.com","query":"MM6 under $&lt;/span&gt;300&lt;span class="s2"&gt;","&lt;/span&gt;limit&lt;span class="s2"&gt;":2}}}' &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;  | jq -r '.result.content[0].text'
WDLT117 (Toronto), pieces matching "MM6":
1. MM6 Maison Margiela MM6 X Dr. Martens Edition 8-eye Burgundy, CAD 249, sizes in stock: UK 3 / EU 36, at WDLT117 (Toronto). https://shopwithin.co/product/wdlt117/mm6-maison-margiela-mm6-x-dr-martens-edition-8-eye-burgundy/10083805495606
Stock checked 17 days ago.
More: call again with offset 2.
The boutique on within: https://shopwithin.co/store/wdlt117
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server keeps nothing between requests, so this bare &lt;code&gt;tools/call&lt;/code&gt; works with no &lt;code&gt;initialize&lt;/code&gt; first. That makes curl a fair test client.&lt;/p&gt;

&lt;p&gt;The query is a plain sentence. It runs through the same parser as the site's search box, so "under $300" became a price ceiling and "MM6" stayed the search.&lt;/p&gt;

&lt;p&gt;Every answer has two halves. The &lt;code&gt;content&lt;/code&gt; text is one line per piece that a model can quote whole: designer, price with its currency, sizes in stock, the shop and the URL. The &lt;code&gt;structuredContent&lt;/code&gt; half is typed and checked against the tool's zod &lt;code&gt;outputSchema&lt;/code&gt;, and it is what the ChatGPT widget reads.&lt;/p&gt;

&lt;p&gt;For the boot, the typed half held &lt;code&gt;price: 249&lt;/code&gt;, &lt;code&gt;currency: "CAD"&lt;/code&gt; and &lt;code&gt;units_in_stock: 1&lt;/code&gt;. Its &lt;code&gt;condition&lt;/code&gt; came back null: the listing never stated one, and we do not guess.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;stock_checked_at&lt;/code&gt; read 10 September 2026, and the text half printed that as "Stock checked 17 days ago." Printing an age lets a model hedge instead of promising stock.&lt;/p&gt;

&lt;p&gt;Sizes are the hard part. One register labels a shoe &lt;code&gt;10M&lt;/code&gt;, another writes &lt;code&gt;UK 3 / EU 36&lt;/code&gt;, and the engine matches labels exactly. When a size finds nothing, &lt;code&gt;search_pieces&lt;/code&gt; reads a wider page and lets a bare number match that number in any system.&lt;/p&gt;

&lt;p&gt;Asked for a size 10 Jordan 4 under $500, &lt;code&gt;search_pieces&lt;/code&gt; returned a pair at CAD 500 with &lt;code&gt;10M&lt;/code&gt; on the shelf. The note said no conversion was implied.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we refuse
&lt;/h2&gt;

&lt;p&gt;A refusal should say what happened and what to try next. These three came back from production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A city with no shops: "within has no boutiques live in Paris yet. Boutiques are live in Toronto; the same search without a city reads every shelf."&lt;/li&gt;
&lt;li&gt;A piece that has gone: "It may have sold, or the link may be old. A fresh search shows what is in stock today."&lt;/li&gt;
&lt;li&gt;An Instagram post: "Instagram does not allow its photographs to be read; ask the shopper for a screenshot and pass it as image_base64."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pieces from demo shops, and pieces without a photograph, never leave the server. &lt;code&gt;visual_search&lt;/code&gt; reads a photograph in memory and never stores it.&lt;/p&gt;

&lt;p&gt;There is no write tool at all. The shopper opens the piece's page and buys there. If we ever add holds, they get separate tools with their own consent step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ChatGPT shelf
&lt;/h2&gt;

&lt;p&gt;Five of the six tools carry &lt;code&gt;openai/outputTemplate&lt;/code&gt;, pointing at &lt;code&gt;ui://widget/within-shelf.html&lt;/code&gt;. That &lt;code&gt;text/html+skybridge&lt;/code&gt; resource renders the pieces as a grid of photo tiles. A client that does not read the &lt;code&gt;openai/&lt;/code&gt; keys still gets both halves, as plain text and JSON.&lt;/p&gt;

&lt;p&gt;The widget's image policy allows a fixed list of 9 host patterns: the Shopify, Squarespace, Wix, Square and Lightspeed image hosts, plus our own. A WooCommerce boutique serving images from its own domain would get tiles with no photograph until we build that list from the live stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we still get wrong
&lt;/h2&gt;

&lt;p&gt;The size example above asked for "Jordan 4 under $500, cheapest first". The parser lifted "cheapest" into a price sort but left "first" in the query. The answer opened "No exact match; the closest by name", yet it still found the pair.&lt;/p&gt;

&lt;p&gt;The note gave the bug away: &lt;code&gt;Understood as: searched for "Jordan 4, first", priced up to 500, lowest price first.&lt;/code&gt; Echoing the parse back shows the model, and us, exactly how a sentence was read.&lt;/p&gt;

&lt;p&gt;Pages come back short. Our search engine filters price by coarse bands, and the band that holds $300 runs to $400. We drop the pieces between $300 and $400 after the engine has already cut the page.&lt;/p&gt;

&lt;p&gt;On WDLT117 the next two MM6 pieces in that band were a CAD 340 bucket hat and a CAD 345 beanie. So the call above asked for 2 and showed 1. Asked for 24, the same shelf shows 3.&lt;/p&gt;

&lt;p&gt;Asked for 1 at offset 1, it says it has nothing matching "MM6", then offers offset 3. The code reads one more window when a page comes back empty, and here that was not enough.&lt;/p&gt;

&lt;p&gt;A higher ceiling returned fewer pieces. With a limit of 3, "MM6 under $399" showed the boot, the hat and the beanie, and "MM6 under $401" showed only the boot.&lt;/p&gt;

&lt;p&gt;At $401 the ask covers every band, so the code sends no price filter at all. The rest of the window is pieces over $401, and our filter throws them away.&lt;/p&gt;

&lt;p&gt;Related searches read like catalog tags. For "black leather boots" they came back as cotton (80), relaxed (62) and Pants (45), which helps nobody shopping for boots. The Jordan 4 search got nearly the same list.&lt;/p&gt;

&lt;p&gt;The freshness line is wrong in both directions. It prints the newest check on the page, so the 24 piece answer said "Stock checked 3 days ago" over the boot's 17 day old stamp.&lt;/p&gt;

&lt;p&gt;The stamp itself is the last time our sync rewrote the product record. The nightly sync skips unchanged products, and a stock webhook updates the count without touching that record, so a count can be newer than its date.&lt;/p&gt;

&lt;p&gt;Analytics are thin. Each tool call logs the tool, the milliseconds and the client's name, and in stateless mode that name always reads &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A client names itself once, in the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle" rel="noopener noreferrer"&gt;&lt;code&gt;initialize&lt;/code&gt; request&lt;/a&gt;. Every &lt;code&gt;tools/call&lt;/code&gt; lands on a fresh server that never heard it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you build one
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build the server per request. Stateless mode means no session store on a serverless host.&lt;/li&gt;
&lt;li&gt;Write each tool description as a rule for when to call it.&lt;/li&gt;
&lt;li&gt;Return a line a model can quote and a typed object a widget can render.&lt;/li&gt;
&lt;li&gt;Make every refusal one plain sentence with the next step in it.&lt;/li&gt;
&lt;li&gt;Echo back how you read the question.&lt;/li&gt;
&lt;li&gt;Print how old the data is, show the oldest stamp on a page, and name each stamp after what writes it.&lt;/li&gt;
&lt;li&gt;Filter before you cut a page, or keep reading until the page is full.&lt;/li&gt;
&lt;li&gt;Expect nothing from &lt;code&gt;initialize&lt;/code&gt; on a stateless server, because each call starts from zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With no session and no account, a client needs only the URL. &lt;code&gt;claude mcp add --transport http within https://shopwithin.co/mcp&lt;/code&gt; connects Claude Code, and other Streamable HTTP clients take the same one.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>An open JSON index of Toronto resale prices, with curl and jq</title>
      <dc:creator>Harman Singh</dc:creator>
      <pubDate>Tue, 29 Sep 2026 10:54:30 +0000</pubDate>
      <link>https://dev.to/harrymakez/an-open-json-index-of-toronto-resale-prices-with-curl-and-jq-c15</link>
      <guid>https://dev.to/harrymakez/an-open-json-index-of-toronto-resale-prices-with-curl-and-jq-c15</guid>
      <description>&lt;p&gt;On 27 September 2026, 4,629 pieces were live at the two Toronto boutiques we list, with a median asking price of $400 in Canadian dollars. That reading is a public JSON file you can fetch with one curl.&lt;/p&gt;

&lt;p&gt;We build shopwithin.co, which lists what is in stock at independent luxury boutiques. These are our notes on the price index we publish over those shelves: what it counts, how the median works, when it is counted, and how to query it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Page:&lt;/strong&gt; &lt;a href="https://shopwithin.co/index/toronto-resale" rel="noopener noreferrer"&gt;Toronto Resale Price Index&lt;/a&gt;, with a schema.org &lt;code&gt;Dataset&lt;/code&gt; entity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File:&lt;/strong&gt; &lt;a href="https://shopwithin.co/index/toronto-resale.json" rel="noopener noreferrer"&gt;the JSON distribution&lt;/a&gt;, CC BY 4.0, CORS open to any origin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counted:&lt;/strong&gt; 4,629 pieces, 3,699 at Utopia and 930 at WDLT117&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices:&lt;/strong&gt; $10 to $21,000 CAD, median $400&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre owned:&lt;/strong&gt; 1,379 pieces carry a wear grade, 30 percent, median $400&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows:&lt;/strong&gt; 20 segments, 15 of them sneaker models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every number here was read with curl on 27 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it counts
&lt;/h2&gt;

&lt;p&gt;The index walks every live listing at the Toronto boutiques we list. Each price is the asking price the boutique set, unchanged.&lt;/p&gt;

&lt;p&gt;A piece priced in another currency is dropped from every number, and the file says how many. On 27 September, &lt;code&gt;counted.skipped&lt;/code&gt; was 0.&lt;/p&gt;

&lt;p&gt;Condition comes from the wear grade the boutique itself wrote: pre owned, lightly used or worn once. Since 26 September 2026, "vintage" in a title no longer counts as a grade, because boutiques use it for a wash or an era.&lt;/p&gt;

&lt;p&gt;Sold prices are not in it. We do not hold them, so we do not estimate them. These are asking prices, and the method text inside the JSON says so in plain words.&lt;/p&gt;

&lt;p&gt;One finding surprised us. Only 1 of the 4,629 pieces states outright that it is new, and 3,249 state no condition at all. So the file carries &lt;code&gt;statedNewMedian: null&lt;/code&gt; instead of a guess, and the page says why.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the median is computed
&lt;/h2&gt;

&lt;p&gt;The median is the lower of the two middle prices, which statisticians call the lower median. It is never an average. This is the whole function, shared by the page, the JSON route, the monthly snapshot and the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/lib/designer-prices.ts, with line breaks added&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * Low, median and high over a set of asking prices. The median is the lower
 * of the two middle prices on an even count, so every number a price page
 * prints is a price some piece is actually asking.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bandOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;PriceBand&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;median&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it &lt;code&gt;[300, 350, 400, 450]&lt;/code&gt; and it returns 350, not 375. We chose that on purpose.&lt;/p&gt;

&lt;p&gt;Every median in the file is a price some piece on a Toronto shelf is asking today. A journalist can quote it and a reader can go find that piece.&lt;/p&gt;

&lt;p&gt;Two floors keep thin rows out. A segment needs 10 live pieces before it gets a row at all. The pre owned and stated new medians each need 3 pieces on their side.&lt;/p&gt;

&lt;p&gt;You can see the second floor in the live file. Yeezy Slide had 12 pieces on 27 September, so it gets a row. Only 1 of them carried a wear grade, so its &lt;code&gt;resaleMedian&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it is counted
&lt;/h2&gt;

&lt;p&gt;Nothing is precomputed into a table. The route reads the live catalog, runs the arithmetic, and lets the CDN share each response for ten minutes.&lt;/p&gt;

&lt;p&gt;Each server instance holds its own count in memory for up to an hour, then reads the catalog again on the next request. So &lt;code&gt;countedAt&lt;/code&gt; matters.&lt;/p&gt;

&lt;p&gt;At 07:17 UTC on 27 September, the copy the CDN held said it was counted at 06:28 UTC, and its &lt;code&gt;age&lt;/code&gt; header said 523 seconds. A read that missed the cache said 07:07 UTC.&lt;/p&gt;

&lt;p&gt;The HTML page's &lt;code&gt;dateModified&lt;/code&gt; said 06:28 UTC. All three said 4,629 pieces.&lt;/p&gt;

&lt;p&gt;The series is separate. A script appends one row a month to a JSON file committed to the repo, and a page render never writes it.&lt;/p&gt;

&lt;p&gt;A second run in the same month replaces that month's row. On 27 September the &lt;code&gt;history&lt;/code&gt; array held one row: September 2026, 4,700 pieces, median $400.&lt;/p&gt;

&lt;p&gt;The route refuses to publish a partial count. If the latest read missed part of any boutique's catalog, it falls back to the last whole count this instance took in the past 24 hours.&lt;/p&gt;

&lt;p&gt;With no whole count on hand, it answers 503 with &lt;code&gt;no-store&lt;/code&gt;. It never answers 200 with a small number, and never 404.&lt;/p&gt;

&lt;p&gt;This rule came from a real bug. A sync blip once read only a small slice of the catalog. The JSON published that count under the shared cache header, and the CDN went on serving it after the catalog was whole again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/routes/index_.toronto-resale[.]json.tsx, lightly trimmed and reformatted&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;census&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wholeCensusOrNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// a whole reading, or null&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;census&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nf"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 503, retry after 10 s, cache-control: no-store&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveResaleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;census&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SEO_CITY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nf"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resaleIndexJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;resaleIndexHistory&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// "public, max-age=0, s-maxage=600, stale-while-revalidate=86400"&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SHARED_PRICE_CACHE_CONTROL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;https://creativecommons.org/licenses/by/4.0/&amp;gt;; rel="license"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access-control-allow-origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 404 rule is about search. A 404 tells Google to drop a URL, and a catalog that failed to answer is not an empty one. A 503 with &lt;code&gt;retry-after&lt;/code&gt; asks the crawler to come back.&lt;/p&gt;

&lt;p&gt;The licence travels in a &lt;code&gt;Link&lt;/code&gt; header, so a script that never sees the page still gets it.&lt;/p&gt;

&lt;p&gt;One trap if you check the headers yourself. The CDN keeps &lt;code&gt;s-maxage&lt;/code&gt; for itself, so a GET on 27 September showed only &lt;code&gt;public, max-age=0&lt;/code&gt;. The &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;x-vercel-cache: HIT&lt;/code&gt; headers are what tell you a shared copy answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it with curl and jq
&lt;/h2&gt;

&lt;p&gt;These ran against the live file on 27 September 2026. The output is what came back, with tabs shown as spaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The headline numbers, and when they were counted&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://shopwithin.co/index/toronto-resale.json &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'{countedAt, pieces: .counted.pieces, median: .counted.median, resaleShare: .counted.resaleShare}'&lt;/span&gt;
&lt;span class="c"&gt;# {"countedAt":"2026-09-27T06:28:15.861Z","pieces":4629,"median":400,"resaleShare":30}&lt;/span&gt;

&lt;span class="c"&gt;# Sneaker models, deepest first: label, pieces, median, pre owned median&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://shopwithin.co/index/toronto-resale.json &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.segments[] | select(.parent == "sneakers") | [.label, .pieces, .median, .resaleMedian] | @tsv'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-4&lt;/span&gt;
&lt;span class="c"&gt;# Jordan 1      241  350  300&lt;/span&gt;
&lt;span class="c"&gt;# Jordan 4      134  350  300&lt;/span&gt;
&lt;span class="c"&gt;# Travis Scott  132  450  1000&lt;/span&gt;
&lt;span class="c"&gt;# Kobe          107  400  350&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Travis Scott row is the one to read slowly. Its 57 pre owned pieces ask a median of $1,000, while all 132 ask a median of $450. A wear grade does not always mean a lower price.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Which rows link to a page on the site&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://shopwithin.co/index/toronto-resale.json &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'[.segments[] | select(.shelf != null) | .label]'&lt;/span&gt;
&lt;span class="c"&gt;# ["Sneakers","Jordan 1","Jordan 4","Chrome Hearts","Supreme"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five of the 20 rows carry a &lt;code&gt;shelf&lt;/code&gt; URL, such as &lt;code&gt;/shop/toronto/sneakers/jordan-1&lt;/code&gt;. The other 15 leave the &lt;code&gt;shelf&lt;/code&gt; key out entirely. jq reads a missing key as &lt;code&gt;null&lt;/code&gt;, so the filter works either way.&lt;/p&gt;

&lt;p&gt;A sneaker model links only where the site has a page for that model. So Travis Scott and Kobe get no &lt;code&gt;shelf&lt;/code&gt; key, not a link to the nearest shelf.&lt;/p&gt;

&lt;p&gt;Drift against the stored month is one more filter. &lt;code&gt;jq '.counted.pieces - .history[-1].pieces'&lt;/code&gt; returned -71 on 27 September, from 4,700 in the September row to 4,629 live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we still owe
&lt;/h2&gt;

&lt;p&gt;Nothing schedules the monthly script yet. We run it by hand.&lt;/p&gt;

&lt;p&gt;While checking this post on 27 September, we found that script still calls a catalog read the server module no longer exports. The October row needs that one line fixed first.&lt;/p&gt;

&lt;p&gt;The lesson is general. The script is plain &lt;code&gt;.mjs&lt;/code&gt; and loads the TypeScript modules through Vite's &lt;code&gt;ssrLoadModule&lt;/code&gt;. Those exports come back untyped, so the type checker never saw the stale name.&lt;/p&gt;

&lt;p&gt;Two boutiques is a small sample. The file names both and their counts, so you can weigh it yourself.&lt;/p&gt;

&lt;p&gt;If you use the numbers, cite them the way the file's own &lt;code&gt;citation&lt;/code&gt; field does: "Source: within, Toronto Resale Price Index, September 2026, shopwithin.co/index/toronto-resale". The licence is &lt;a href="https://creativecommons.org/licenses/by/4.0/" rel="noopener noreferrer"&gt;CC BY 4.0&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
