{"id":6180,"date":"2026-10-20T04:24:16","date_gmt":"2026-10-20T07:24:16","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/10\/20\/two-tools-built-on-our-data-and-what-our-docs-left-out\/"},"modified":"2026-10-20T04:24:16","modified_gmt":"2026-10-20T07:24:16","slug":"two-tools-built-on-our-data-and-what-our-docs-left-out","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/10\/20\/two-tools-built-on-our-data-and-what-our-docs-left-out\/","title":{"rendered":"Two tools built on our data, and what our docs left out"},"content":{"rendered":"<div>\n<div><\/div>\n<p><strong>Products with no monthly price.<\/strong> <code>billing_cycles<\/code> lists only the terms a product is sold on. Seven products, all of them special offers, have no <code>monthly<\/code> key at all. PulseWatch showed a dash for them and recorded no price history, so their price moves were invisible.<\/p>\n<p><strong>Two prices for one product.<\/strong> PulseWatch also compares the feed with the storefront once a day, and for some products the store&#8217;s &#8220;Starting from&#8221; price was higher than the feed&#8217;s price. Its README put that down to &#8220;a stale monthly price in the feed&#8221;. The real reason is that the store adds the cheapest of a product&#8217;s required options to its &#8220;Starting from&#8221; price, while the feed publishes the base price. Both are correct. They answer different questions.<\/p>\n<p>None of these is a bug on either side. Each one is a field whose meaning lived in our code and nowhere else. The product-feed items on this list are now written down on the <a href=\"https:\/\/wiki.pulsedmedia.com\/wiki\/Pulsed_Media_Public_Data_Feeds\" target=\"_blank\" rel=\"noopener noreferrer\">feeds page<\/a>.<\/p>\n<h2> <a name=\"how-to-poll-a-hosts-feed-politely\" href=\"#how-to-poll-a-hosts-feed-politely\"> <\/a> How to poll a host&#8217;s feed politely <\/h2>\n<p>PulseWatch fetches the catalog every five minutes. Our product feed is rebuilt every 15 minutes, so two of every three fetches download a file that has not changed. That costs the client and the server bandwidth for nothing, and it is fixable with headers that are already there.<\/p>\n<p>Pulsed Media&#8217;s feed responses carry <code>ETag<\/code> and <code>Last-Modified<\/code>. Send them back and you get an empty <code>304 Not Modified<\/code> when nothing changed: <\/p>\n<div>\n<pre><code><span>url<\/span><span>=<\/span>https:\/\/pulsedmedia.com\/data\/v1\/products.json <span>etag<\/span><span>=<\/span><span>$(<\/span>curl <span>-sI<\/span> <span>\"<\/span><span>$url<\/span><span>\"<\/span> | <span>awk<\/span> <span>-F<\/span><span>': '<\/span> <span>'tolower($1)==\"etag\"{print $2}'<\/span> | <span>tr<\/span> <span>-d<\/span> <span>'\\r'<\/span><span>)<\/span> curl <span>-s<\/span> <span>-o<\/span> \/dev\/null <span>-w<\/span> <span>'%{http_code}\\n'<\/span> <span>-H<\/span> <span>\"If-None-Match: <\/span><span>$etag<\/span><span>\"<\/span> <span>\"<\/span><span>$url<\/span><span>\"<\/span> <span># 304<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>In a Python client the same thing takes a few lines with the standard library: <\/p>\n<div>\n<pre><code><span>import<\/span> <span>json<\/span><span>,<\/span> <span>urllib<\/span><span>.<\/span><span>request<\/span><span>,<\/span> <span>urllib<\/span><span>.<\/span><span>error<\/span> <span>def<\/span> <span>fetch<\/span><span>(<\/span><span>url<\/span><span>,<\/span> <span>cache<\/span><span>):<\/span> <span>req<\/span> <span>=<\/span> <span>urllib<\/span><span>.<\/span><span>request<\/span><span>.<\/span><span>Request<\/span><span>(<\/span><span>url<\/span><span>)<\/span> <span>if<\/span> <span>cache<\/span><span>.<\/span><span>get<\/span><span>(<\/span><span>\"<\/span><span>etag<\/span><span>\"<\/span><span>):<\/span> <span>req<\/span><span>.<\/span><span>add_header<\/span><span>(<\/span><span>\"<\/span><span>If-None-Match<\/span><span>\"<\/span><span>,<\/span> <span>cache<\/span><span>[<\/span><span>\"<\/span><span>etag<\/span><span>\"<\/span><span>])<\/span> <span>try<\/span><span>:<\/span> <span>with<\/span> <span>urllib<\/span><span>.<\/span><span>request<\/span><span>.<\/span><span>urlopen<\/span><span>(<\/span><span>req<\/span><span>,<\/span> <span>timeout<\/span><span>=<\/span><span>30<\/span><span>)<\/span> <span>as<\/span> <span>r<\/span><span>:<\/span> <span>cache<\/span><span>.<\/span><span>update<\/span><span>(<\/span><span>etag<\/span><span>=<\/span><span>r<\/span><span>.<\/span><span>headers<\/span><span>.<\/span><span>get<\/span><span>(<\/span><span>\"<\/span><span>ETag<\/span><span>\"<\/span><span>),<\/span> <span>body<\/span><span>=<\/span><span>r<\/span><span>.<\/span><span>read<\/span><span>())<\/span> <span>except<\/span> <span>urllib<\/span><span>.<\/span><span>error<\/span><span>.<\/span><span>HTTPError<\/span> <span>as<\/span> <span>e<\/span><span>:<\/span> <span>if<\/span> <span>e<\/span><span>.<\/span><span>code<\/span> <span>!=<\/span> <span>304<\/span><span>:<\/span> <span>raise<\/span> <span>return<\/span> <span>json<\/span><span>.<\/span><span>loads<\/span><span>(<\/span><span>cache<\/span><span>[<\/span><span>\"<\/span><span>body<\/span><span>\"<\/span><span>])<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Three more habits that hold for any host&#8217;s feed, not just ours:<\/p>\n<ol>\n<li> <strong>Check the timestamp, not just the status code.<\/strong> Our feeds carry <code>generated_at<\/code>. A 200 with a feed that is three hours old means the generator stopped, and a monitor should say &#8220;stale&#8221; instead of reporting old stock as current. The same goes for a cached copy you fall back to when a fetch fails.<\/li>\n<li> <strong>Poll at the rebuild rate, not faster.<\/strong> If the docs say the data refreshes every 15 minutes, a five-minute poll with conditional requests is fine and a one-minute poll without them is noise.<\/li>\n<li> <strong>Treat unknown fields as unknown.<\/strong> When a value looks odd, like a negative stock, ask or read the docs before you give it a meaning. A guess shown on a public page becomes a fact for whoever reads it.<\/li>\n<\/ol>\n<p>One store-specific tip, because PulseWatch sends visitors to our store with a referral id: our <code>aff.php?aff=ID&amp;pid=N<\/code> link sets the referral and then lands on that product&#8217;s order form, so a referral link can go straight to the product. A plain <code>?aff=<\/code> added to a store page does nothing.<\/p>\n<h2> <a name=\"local-tools-public-tunnels\" href=\"#local-tools-public-tunnels\"> <\/a> Local tools, public tunnels <\/h2>\n<p>PulseWatch can publish itself on a Cloudflare quick tunnel, so the dashboard you run on a home server gets a public HTTPS address in one command. That is convenient, and it changes what the code has to defend against. A page that only you open on 127.0.0.1 now faces anyone who finds the address, and even on 127.0.0.1, a web page open in your browser may be able to send it requests.<\/p>\n<p>Two defects came from that shift, and both are common in small self-hosted tools:<\/p>\n<ul>\n<li> <strong>Query values echoed into links without escaping.<\/strong> Most of PulseWatch escapes its HTML carefully, and the pages showed it. One helper built pager links from the raw query string, though, so a crafted catalog URL could run script in the page.<\/li>\n<li> <strong>An expensive action behind a plain GET.<\/strong> The daily feed-versus-storefront check could be started by anyone with <code>?run=1<\/code>, with no token and no cooldown. Every call made the instance fetch the feeds and eight store pages from its owner&#8217;s IP.<\/li>\n<\/ul>\n<p>The fixes are short: URL-encode and escape every value that goes into an attribute, and put anything that costs requests behind the same token and cooldown as the manual scan. Both are in <a href=\"https:\/\/github.com\/HopperHopper\/PulseWatch\/pull\/1\" target=\"_blank\" rel=\"noopener noreferrer\">the pull request we sent<\/a>, alongside the feed-meaning fixes above. As I write this, on 20 October 2026, it is open. Until those fixes are in the version you run, skip the tunnel and don&#8217;t open PulseWatch links that other people send you.<\/p>\n<p>We didn&#8217;t run any of the code to find these. A repository from outside is untrusted input for us, so the review was reading only, and the fixes were tested by the project&#8217;s own CI on our fork.<\/p>\n<h2> <a name=\"why-a-host-fixes-other-peoples-tools\" href=\"#why-a-host-fixes-other-peoples-tools\"> <\/a> Why a host fixes other people&#8217;s tools <\/h2>\n<p>At Pulsed Media we run our own hardware and our own open-source platform, <a href=\"https:\/\/github.com\/MagnaCapax\/PMSS\" target=\"_blank\" rel=\"noopener noreferrer\">PMSS<\/a>. Publishing feeds and an API is part of the same idea: what you rent from us is yours to script, watch and build on. When someone does, the cheapest help we can give is a careful read and a pull request, plus the line in our docs that would have saved them the guess.<\/p>\n<p>For anyone who publishes data that other people build on:<\/p>\n<ol>\n<li> <strong>Write down what each field means, not only its name and type.<\/strong> Units, rounding, null, and what a strange value such as a negative number stands for.<\/li>\n<li> <strong>Say how fresh the data is and send validators.<\/strong> A refresh interval, a <code>generated_at<\/code>, and <code>ETag<\/code>\/<code>Last-Modified<\/code> let clients be polite for free.<\/li>\n<li> <strong>Read the tools people build on you.<\/strong> Their models of your data show you exactly where your docs are silent.<\/li>\n<\/ol>\n<p>Both tools have pages on our wiki: <a href=\"https:\/\/wiki.pulsedmedia.com\/wiki\/PMSS_Fleet_Manager\" target=\"_blank\" rel=\"noopener noreferrer\">PMSS Fleet Manager<\/a> and <a href=\"https:\/\/wiki.pulsedmedia.com\/wiki\/PulseWatch\" target=\"_blank\" rel=\"noopener noreferrer\">PulseWatch<\/a>, with setup notes and what to watch out for.<\/p>\n<hr>\n<p><em>If you build on a host&#8217;s API or feeds, or you&#8217;d like a host that reads the code people build on it, I run support and infrastructure at <a href=\"https:\/\/pulsedmedia.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Pulsed Media<\/a>. Seedboxes and storage boxes on our own hardware in our own datacenters in Finland, an open-source platform, 1Gbps or 10Gbps, EU jurisdiction, 14-day money-back. Customers earn <a href=\"https:\/\/pulsedmedia.com\/virsilipas.php\" target=\"_blank\" rel=\"noopener noreferrer\">AI credits<\/a> from the services they pay for.<\/em><\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/vainamoinen\/two-tools-built-on-our-data-and-what-our-docs-left-out-4nfb\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Products with no monthly price. billing_cycles lists only the terms a product is sold on. Seven products, all of them special offers, have no monthly key at all. PulseWatch showed a dash for them and recorded no price history, so their price moves were invisible. Two prices for one product. PulseWatch also compares the feed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":6179,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"webixso_pending_account_ids":""},"categories":[41],"tags":[],"class_list":["post-6180","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/6180","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=6180"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/6180\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/6179"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=6180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=6180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=6180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}