<?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: tutorial</title>
    <description>The latest articles tagged 'tutorial' on DEV Community.</description>
    <link>https://dev.to/t/tutorial</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/tutorial"/>
    <language>en</language>
    <item>
      <title>Meta Conversions API in Node.js: Deduplication, Hashing and Event Match Quality Done Right</title>
      <dc:creator>Mikail (Adnoxx)</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:47:40 +0000</pubDate>
      <link>https://dev.to/adnoxx/meta-conversions-api-in-nodejs-deduplication-hashing-and-event-match-quality-done-right-2had</link>
      <guid>https://dev.to/adnoxx/meta-conversions-api-in-nodejs-deduplication-hashing-and-event-match-quality-done-right-2had</guid>
      <description>&lt;p&gt;If you run an ecommerce store and send traffic from Facebook or Instagram, the browser Pixel alone no longer sees every purchase. Safari's tracking protection, ad blockers, consent banners and short-lived cookies all chip away at it. The answer Meta gives is the &lt;strong&gt;Conversions API (CAPI)&lt;/strong&gt;: send the same events from your server.&lt;/p&gt;

&lt;p&gt;Most CAPI tutorials stop at "POST some JSON". In production, three details decide whether it helps or hurts: &lt;strong&gt;deduplication&lt;/strong&gt;, &lt;strong&gt;hashing/normalisation&lt;/strong&gt; and &lt;strong&gt;match quality&lt;/strong&gt;. This post covers all three with a small Node.js implementation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Context: our team works on &lt;a href="https://adnoxx.com/blog/peptide-meta-ads-country-campaign-structure" rel="noopener noreferrer"&gt;peptide Meta ads&lt;/a&gt;, a regulated category where every purchase signal matters because audiences are narrow. Nothing below is niche-specific, though. It applies to any store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  The architecture in one paragraph
&lt;/h4&gt;

&lt;p&gt;The browser fires &lt;code&gt;fbq('track', 'Purchase', ...)&lt;/code&gt; on the thank-you page. Your backend, on the order-paid webhook, sends a &lt;code&gt;Purchase&lt;/code&gt; event to &lt;code&gt;POST https://graph.facebook.com/{API_VERSION}/{PIXEL_ID}/events&lt;/code&gt;. Both carry the &lt;strong&gt;same &lt;code&gt;event_id&lt;/code&gt;&lt;/strong&gt;. Meta receives two copies, sees the matching &lt;code&gt;event_name&lt;/code&gt; + &lt;code&gt;event_id&lt;/code&gt;, and keeps one. You get the coverage of the server and the context of the browser without double counting.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Generate one event ID per order
&lt;/h4&gt;

&lt;p&gt;Use something stable that both sides know. The order ID is ideal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared helper&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;purchaseEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`purchase_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the thank-you page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;fbq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;track&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="s1"&gt;Purchase&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;129.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content_ids&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="s1"&gt;SKU-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&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;eventID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purchase_100045&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// same string the server will send&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the casing: the browser option is &lt;code&gt;eventID&lt;/code&gt;; the server field is &lt;code&gt;event_id&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Normalise, then hash
&lt;/h4&gt;

&lt;p&gt;CAPI expects personal data hashed with SHA-256, after normalisation. Hashing an un-normalised value is the most common reason match quality stays low, because &lt;code&gt;" John@Mail.com "&lt;/code&gt; and &lt;code&gt;"john@mail.com"&lt;/code&gt; produce different hashes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&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;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&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;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\D&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;            &lt;span class="c1"&gt;// digits only, include country code&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;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;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;        &lt;span class="c1"&gt;// ISO-3166 alpha-2, e.g. "us"&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;hashed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&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="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))]&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; hash &lt;code&gt;client_ip_address&lt;/code&gt;, &lt;code&gt;client_user_agent&lt;/code&gt;, &lt;code&gt;fbc&lt;/code&gt; or &lt;code&gt;fbp&lt;/code&gt;. They are sent as plain values.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Capture fbp and fbc at checkout
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;_fbp&lt;/code&gt; is the browser ID cookie set by the Pixel. &lt;code&gt;_fbc&lt;/code&gt; holds the click ID when someone arrives from an ad (&lt;code&gt;fbclid&lt;/code&gt; in the URL). Save both on the order when checkout starts, because your webhook runs later, without the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// at checkout creation (server side, request available)&lt;/span&gt;
&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fbp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_fbp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fbc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_fbc&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fbclid&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`fb.1.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fbclid&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="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;referer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Send the server event
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PIXEL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;META_PIXEL_ID&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;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;META_CAPI_TOKEN&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;API_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;META_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// use the current Graph API version&lt;/span&gt;

&lt;span class="k"&gt;export&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;sendPurchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Purchase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;event_time&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;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paidAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;purchaseEventId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;action_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;event_source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;em&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;zp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
        &lt;span class="na"&gt;client_ip_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;client_user_agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fbp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fbp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fbc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fbc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;custom_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&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;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;num_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&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;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://graph.facebook.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_VERSION&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;PIXEL_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/events?access_token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TOKEN&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&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="s1"&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="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&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="nx"&gt;body&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="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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CAPI &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// { events_received: 1, fbtrace_id: ... }&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from the payment webhook, not from the thank-you page, so a closed tab never loses a sale. Put it on a queue with retries: Meta accepts events up to 7 days old, but delivery is most useful within an hour.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5: Check it in Events Manager
&lt;/h4&gt;

&lt;p&gt;In Events Manager, open the Pixel's &lt;strong&gt;Overview&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purchase should show &lt;strong&gt;Browser&lt;/strong&gt; and &lt;strong&gt;Server&lt;/strong&gt; as connection methods.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;deduplication&lt;/strong&gt; column should show most server events matched to browser events. If server and browser counts are both high and nothing is deduplicated, your &lt;code&gt;event_id&lt;/code&gt; values do not match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Match Quality (EMQ)&lt;/strong&gt; scores how well your &lt;code&gt;user_data&lt;/code&gt; identifies people. Email, phone, &lt;code&gt;fbp&lt;/code&gt;, &lt;code&gt;fbc&lt;/code&gt;, IP and user agent together usually lift Purchase to a strong score.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Common bugs we see
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Purchases doubled in Ads Manager&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eventID&lt;/code&gt; missing on the browser call, or different formats on each side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low match quality&lt;/td&gt;
&lt;td&gt;Hashing before normalising; phone without country code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server events "too old"&lt;/td&gt;
&lt;td&gt;Sending &lt;code&gt;event_time&lt;/code&gt; in milliseconds instead of seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No &lt;code&gt;fbc&lt;/code&gt; on most orders&lt;/td&gt;
&lt;td&gt;Not persisting &lt;code&gt;fbclid&lt;/code&gt; from the landing URL through checkout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values in the wrong currency&lt;/td&gt;
&lt;td&gt;Sending store currency while the Pixel reports customer currency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Why it matters for the ad side
&lt;/h4&gt;

&lt;p&gt;Meta's delivery system optimises toward the conversions it can see. When CAPI recovers the purchases the Pixel misses, the algorithm learns from the full set of real buyers, finds more people like them, and reports ROAS you can trust. For stores in competitive or regulated niches, that difference is often what separates a profitable campaign from one that gets switched off.&lt;/p&gt;

&lt;p&gt;If you want the marketing-side view of this setup, we wrote about &lt;a href="https://adnoxx.com/blog/pixel-capi-for-peptide-stores" rel="noopener noreferrer"&gt;Pixel and Conversions API for peptide stores&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Mikail builds tracking and Meta ads infrastructure at Adnoxx, a peptide ad agency with years in digital marketing.&lt;/em&gt;&lt;br&gt;
adnoxx.com · &lt;a href="mailto:peptideads@adnoxx.com"&gt;peptideads@adnoxx.com&lt;/a&gt; · Telegram @adnoxxads · WhatsApp +1 681 379 0505 · Instagram @adnoxxofficial&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Arbitrage Alert: Building a Cross-Exchange Prediction Market Monitor with Python</title>
      <dc:creator>heavy shield</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:47:02 +0000</pubDate>
      <link>https://dev.to/gu_cci_f94bedb90083e6aab4/arbitrage-alert-building-a-cross-exchange-prediction-market-monitor-with-python-31c7</link>
      <guid>https://dev.to/gu_cci_f94bedb90083e6aab4/arbitrage-alert-building-a-cross-exchange-prediction-market-monitor-with-python-31c7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Prediction markets like Polymarket and Kalshi provide real-time probabilities for future events. On October 18, 2024, they priced the Fed's October rate decision four percentage points apart: Polymarket implied a 92% chance of a 25bp cut, while Kalshi showed 88%. Such discrepancies create arbitrage opportunities, but manually monitoring multiple platforms is impractical. In this post, we'll build a Python agent that polls both exchanges, normalizes contract prices, and computes the spread. The agent runs in a loop with explicit termination conditions and uses only public APIs. We'll also discuss security and precision issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Prediction markets often disagree due to differing liquidity, fees, or user bases. For developers building AI agents, detecting these spreads programmatically is a first step toward automated trading or alerting. Challenges include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API heterogeneity&lt;/strong&gt;: Polymarket uses a GraphQL endpoint; Kalshi uses REST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract specifications&lt;/strong&gt;: Outcomes may be phrased differently (e.g., "Fed cuts rates by 25bp" vs. "FOMC decision: cut 25bp").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time updates&lt;/strong&gt;: Prices change frequently; polling must be efficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: Network failures, rate limits, and malformed responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll focus on a minimal viable monitor that fetches current prices for a specific market and computes the spread. We'll use Python 3.9+ with &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;asyncio&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Our agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the current price for a given market on Polymarket and Kalshi.&lt;/li&gt;
&lt;li&gt;Normalize prices to probabilities (0-1).&lt;/li&gt;
&lt;li&gt;Compute the absolute difference.&lt;/li&gt;
&lt;li&gt;Log the spread if it exceeds a threshold.&lt;/li&gt;
&lt;li&gt;Terminate after a specified number of iterations or when a stop condition is met.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll define an agent loop with explicit termination: it stops after &lt;code&gt;max_iterations&lt;/code&gt; or if a &lt;code&gt;KeyboardInterrupt&lt;/code&gt; occurs. We'll use &lt;code&gt;asyncio&lt;/code&gt; for concurrent polling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security note&lt;/strong&gt;: This code only reads public data. Do not use &lt;code&gt;eval&lt;/code&gt; or &lt;code&gt;exec&lt;/code&gt; on API responses. Always validate JSON schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests aiohttp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration
&lt;/span&gt;&lt;span class="n"&gt;POLYMARKET_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://clob.polymarket.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;KALSHI_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.elections.kalshi.com/trade-api/v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Example market IDs (replace with actual IDs from the platforms)
&lt;/span&gt;&lt;span class="n"&gt;POLYMARKET_MARKET_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x123...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Polymarket condition ID
&lt;/span&gt;&lt;span class="n"&gt;KALSHI_TICKER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FED-24OCT-T25&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;    &lt;span class="c1"&gt;# Kalshi market ticker
&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_polymarket_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch the yes price (probability) from Polymarket.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Polymarket's CLOB API requires a token ID; this is a simplified example.
&lt;/span&gt;    &lt;span class="c1"&gt;# In practice, you'd query the market's token IDs first.
&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;POLYMARKET_API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/price?token_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;side=BUY&lt;/span&gt;&lt;span class="sh"&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;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# Response format: {"price": "0.92"}
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&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;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_kalshi_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch the yes price from Kalshi.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KALSHI_API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/markets/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# Kalshi returns prices in cents (0-100)
&lt;/span&gt;            &lt;span class="n"&gt;yes_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;yes_price&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;yes_price&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monitor_spread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Poll both exchanges and log spreads above threshold.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;poly_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kalshi_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;fetch_polymarket_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;POLYMARKET_MARKET_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;fetch_kalshi_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KALSHI_TICKER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;poly_price&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;kalshi_price&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;spread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poly_price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;kalshi_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Iteration &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Polymarket=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;poly_price&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Kalshi=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kalshi_price&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Spread=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spread&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spread&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  ALERT: Spread exceeds &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Iteration &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Failed to fetch prices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;monitor_spread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polymarket&lt;/strong&gt;: The CLOB API endpoint &lt;code&gt;/price&lt;/code&gt; requires a token ID and side. In a real implementation, you'd first query &lt;code&gt;/markets&lt;/code&gt; to get token IDs. We use a placeholder &lt;code&gt;POLYMARKET_MARKET_ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kalshi&lt;/strong&gt;: The &lt;code&gt;/markets/{ticker}&lt;/code&gt; endpoint returns a &lt;code&gt;yes_price&lt;/code&gt; in cents. We convert to probability by dividing by 100.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent loop&lt;/strong&gt;: The &lt;code&gt;monitor_spread&lt;/code&gt; function runs for &lt;code&gt;max_iterations&lt;/code&gt; (explicit termination). It uses &lt;code&gt;asyncio.gather&lt;/code&gt; for concurrent fetching. Errors are caught and logged; the loop continues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold&lt;/strong&gt;: Alerts when spread &amp;gt;= 3% (default). Adjust based on fees and slippage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This is a simplified example. Actual market IDs and endpoints may change. Always refer to official API docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-exchange monitoring&lt;/strong&gt; is feasible with public APIs, but requires careful normalization of contract specifications and price formats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent loops need termination conditions&lt;/strong&gt;: we used &lt;code&gt;max_iterations&lt;/code&gt; and &lt;code&gt;asyncio.sleep&lt;/code&gt; to avoid infinite loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt; is crucial: network issues and API changes can break your agent. Always validate responses and use timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Never use &lt;code&gt;eval&lt;/code&gt; or &lt;code&gt;exec&lt;/code&gt; on API data. Use &lt;code&gt;json.loads&lt;/code&gt; and type checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precision&lt;/strong&gt;: Prices may be strings; convert carefully. Use &lt;code&gt;float&lt;/code&gt; only after validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arbitrage opportunities&lt;/strong&gt; may be fleeting; consider building a more robust system with WebSockets for real-time updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This agent is a starting point. Extend it with alerting (e.g., email, Slack) and persistent storage for historical spreads.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Bookmark It! STOP Shipping Broken Vibe Code: 10 Checks That Save Your App at 2 AM</title>
      <dc:creator>heavy shield</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:46:48 +0000</pubDate>
      <link>https://dev.to/gu_cci_f94bedb90083e6aab4/bookmark-it-stop-shipping-broken-vibe-code-10-checks-that-save-your-app-at-2-am-l71</link>
      <guid>https://dev.to/gu_cci_f94bedb90083e6aab4/bookmark-it-stop-shipping-broken-vibe-code-10-checks-that-save-your-app-at-2-am-l71</guid>
      <description>&lt;h1&gt;
  
  
  Bookmark It! STOP Shipping Broken Vibe Code: 10 Checks That Save Your App at 2 AM
&lt;/h1&gt;

&lt;p&gt;You know the scene: it's 2 AM, your AI agent just deployed a "minor fix," and now the entire app is returning 500s. The code &lt;em&gt;looked&lt;/em&gt; right when the model generated it. The tests passed locally. But in production, it's a disaster. This is the cost of shipping &lt;strong&gt;vibe code&lt;/strong&gt;—code that feels right but hasn't been verified.&lt;/p&gt;

&lt;p&gt;AI coding agents are powerful, but they are also confidently wrong. They hallucinate APIs, ignore edge cases, and happily introduce security holes. If you're building agents that write or modify code, you need a guardrail layer. Below are 10 checks you can implement in Python to catch broken vibe code before it hits production. Each check is runnable and designed to be integrated into your agent's output pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Agents Ship Vibes, Not Verified Code
&lt;/h2&gt;

&lt;p&gt;An AI agent loop typically does: plan → generate code → execute → observe → repeat. Without explicit termination conditions, the loop can run forever. Without verification, it can "succeed" by producing code that runs but is semantically wrong. Worse, if your agent executes arbitrary code (e.g., via &lt;code&gt;exec&lt;/code&gt; or &lt;code&gt;subprocess&lt;/code&gt;), you're one prompt injection away from a remote code execution vulnerability.&lt;/p&gt;

&lt;p&gt;The solution is a &lt;strong&gt;check pipeline&lt;/strong&gt;: a set of deterministic validations applied to every code artifact before it is accepted. These checks are not a replacement for human review, but they catch the low-hanging fruit that causes 2 AM pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10 Checks
&lt;/h2&gt;

&lt;p&gt;We'll implement each as a function that takes a code string (and optional context) and returns a boolean or raises an exception. We'll then compose them into a pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Syntax Validation
&lt;/h3&gt;

&lt;p&gt;Use Python's &lt;code&gt;ast.parse&lt;/code&gt; to ensure the code is syntactically valid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&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="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;SyntaxError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Syntax error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Import Allowlist
&lt;/h3&gt;

&lt;p&gt;Agents love to import random packages. Restrict imports to a known allowlist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Import&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Disallowed import: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImportFrom&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Disallowed import: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. No Dangerous Builtins
&lt;/h3&gt;

&lt;p&gt;Block &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;compile&lt;/code&gt;, and &lt;code&gt;__import__&lt;/code&gt; unless explicitly allowed. &lt;strong&gt;Security warning:&lt;/strong&gt; these functions can execute arbitrary code. Never enable them in untrusted contexts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DANGEROUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eval&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;compile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__import__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_dangerous_builtins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DANGEROUS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dangerous call: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. No Subprocess or OS System Calls
&lt;/h3&gt;

&lt;p&gt;Block &lt;code&gt;subprocess&lt;/code&gt;, &lt;code&gt;os.system&lt;/code&gt;, and similar modules unless the agent is explicitly authorized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_subprocess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Import&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subprocess&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;os&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Disallowed module: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImportFrom&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subprocess&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;os&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Disallowed module: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Function Signature Check
&lt;/h3&gt;

&lt;p&gt;If the agent is modifying an existing function, ensure the signature matches the original. This prevents accidental API breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_function_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionDef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signature mismatch for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; != &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_params&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Function &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Test Coverage Threshold
&lt;/h3&gt;

&lt;p&gt;Run the agent's generated tests and require a minimum coverage. Use &lt;code&gt;coverage.py&lt;/code&gt; programmatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_coverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_percent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;80.0&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Write code to a temp file and run coverage
&lt;/span&gt;    &lt;span class="c1"&gt;# Simplified for brevity; in practice, use a temp directory
&lt;/span&gt;    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Coverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Execute the code (in a sandbox!)
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&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;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Execution error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_percent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security warning:&lt;/strong&gt; &lt;code&gt;exec&lt;/code&gt; here runs arbitrary code. Only use this in an isolated sandbox (e.g., Docker container with no network). Never run untrusted code on your host.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Lint with Flake8
&lt;/h3&gt;

&lt;p&gt;Catch style issues and potential bugs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_lint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flake8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Type Check with Mypy
&lt;/h3&gt;

&lt;p&gt;Static type checking catches many runtime errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_types&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mypy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9. Dependency Audit
&lt;/h3&gt;

&lt;p&gt;If the agent adds dependencies, check them against a vulnerability database (e.g., &lt;code&gt;pip-audit&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_dependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pip-audit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. Explicit Agent Loop Termination
&lt;/h3&gt;

&lt;p&gt;If your agent uses a loop, define a maximum iteration count and a success condition. Without this, the agent may never stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_ITERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success_check&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;success_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent loop exceeded maximum iterations without success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation: The Check Pipeline
&lt;/h2&gt;

&lt;p&gt;Compose the checks into a single function. Use a list of (name, check) tuples. Each check returns True if passed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;expected_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;syntax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_imports&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dangerous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_dangerous_builtins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subprocess&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_subprocess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_lint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;types&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_types&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_function_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrate this into your agent's output step: only accept code that passes all checks. For execution, always use a sandbox (e.g., &lt;code&gt;docker run --rm -v $(pwd):/code python:3.11-slim&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vibe code is a liability.&lt;/strong&gt; Verify every artifact with deterministic checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security first.&lt;/strong&gt; Never &lt;code&gt;exec&lt;/code&gt; untrusted code on your host. Use containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit termination.&lt;/strong&gt; Agent loops must have a max iteration count and a clear success condition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose checks.&lt;/strong&gt; A pipeline of small, focused validators is easier to maintain than one monolithic validator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bookmark this.&lt;/strong&gt; At 2 AM, you'll want these checks in your back pocket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These 10 checks won't catch every bug, but they'll stop the most common causes of broken deployments. Add them to your agent's CI/CD, and sleep better.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>REA: Let Your AI Agent Reverse Engineer the App Features You Wish You Had</title>
      <dc:creator>heavy shield</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:45:52 +0000</pubDate>
      <link>https://dev.to/gu_cci_f94bedb90083e6aab4/rea-let-your-ai-agent-reverse-engineer-the-app-features-you-wish-you-had-8i</link>
      <guid>https://dev.to/gu_cci_f94bedb90083e6aab4/rea-let-your-ai-agent-reverse-engineer-the-app-features-you-wish-you-had-8i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You've been there: you're using an app, and you think, "If only it had feature X." But instead of waiting for the product team, you can build an AI agent that reverse engineers the app's API and implements that feature yourself. This post introduces REA (Reverse Engineering Agent), a pattern for building such agents. We'll walk through a concrete implementation in Python that inspects a web app's network traffic, infers API endpoints, and generates code to replicate missing functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Modern web apps are complex. Their features are often powered by undocumented internal APIs. When you want to extend or automate them, you're left guessing endpoints, parameters, and authentication. Manually reverse engineering is tedious and error-prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: REA
&lt;/h2&gt;

&lt;p&gt;REA is an agent that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Captures HTTP traffic from a target app (using a proxy like mitmproxy).&lt;/li&gt;
&lt;li&gt;Analyzes requests/responses to identify API patterns.&lt;/li&gt;
&lt;li&gt;Generates Python code that replicates desired features.&lt;/li&gt;
&lt;li&gt;Validates the generated code against the live API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent operates in a loop with explicit termination conditions: either it successfully generates and validates the code, or it exhausts a maximum number of iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;We'll build a simplified REA using Python, &lt;code&gt;mitmproxy&lt;/code&gt;, and OpenAI's API. The agent will reverse engineer a hypothetical "todo" feature from a demo app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;mitmproxy openai requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Capture Traffic
&lt;/h3&gt;

&lt;p&gt;We'll use mitmproxy's scripting to log requests to a file. Save this as &lt;code&gt;capture.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mitmproxy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Capture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pretty_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pretty_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traffic.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;addons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Capture&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run mitmproxy with this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mitmproxy &lt;span class="nt"&gt;-s&lt;/span&gt; capture.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure your browser to use mitmproxy as a proxy, then use the app to trigger the feature you want to replicate. The traffic will be logged to &lt;code&gt;traffic.jsonl&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Analyze Traffic and Generate Code
&lt;/h3&gt;

&lt;p&gt;Now, we'll write the REA agent. It reads the captured traffic, asks an LLM to infer the API, and generates Python code. We'll use OpenAI's function calling to structure the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Load captured traffic
&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traffic.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Prepare a summary for the LLM
&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_snippet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Given these HTTP requests and responses from a web app, infer the API endpoints and generate Python code using the `requests` library to perform the following feature: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create a new todo item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.

Traffic summary:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Return a JSON object with keys: `endpoint`, `method`, `headers`, `payload_template`, and `code`. The `code` should be a complete Python function that takes necessary parameters and returns the response.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generated code:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Validate Generated Code
&lt;/h3&gt;

&lt;p&gt;The agent should test the generated code against the live API. We'll extract the function and run it with sample data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# Extract the function from generated code (simplified)
# In production, use a sandboxed environment!
&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# SECURITY WARNING: Never exec untrusted code in production.
&lt;/span&gt;
&lt;span class="c1"&gt;# Assuming the function is named `create_todo`
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test todo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Validation successful!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Validation failed:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error during validation:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Agent Loop
&lt;/h3&gt;

&lt;p&gt;A full REA agent would iterate: if validation fails, it feeds the error back to the LLM to refine the code. The loop terminates when validation succeeds or after &lt;code&gt;max_iterations&lt;/code&gt; (e.g., 5).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;max_iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Generate code (as above)
&lt;/span&gt;    &lt;span class="c1"&gt;# Validate
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validation_success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Add error to prompt for next iteration
&lt;/span&gt;        &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Previous attempt failed with error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;REA agents can automate reverse engineering of web APIs by combining traffic capture with LLM-powered code generation.&lt;/li&gt;
&lt;li&gt;Always include explicit termination conditions in agent loops to avoid infinite execution.&lt;/li&gt;
&lt;li&gt;Security: Never execute generated code without sandboxing. Use containers or restricted environments.&lt;/li&gt;
&lt;li&gt;This approach is for educational purposes; respect terms of service and privacy laws.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By building your own REA, you can unlock features that apps don't provide, tailored to your workflow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This implementation is inspired by agentic patterns but is not a formal protocol. The term "REA" is used descriptively.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🎥 Video: The Display Class in Limn Engine</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:45:19 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-27ib</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-27ib</guid>
      <description>&lt;h1&gt;
  
  
  🎥 Video: The Display Class in Limn Engine
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A full walkthrough of the Display class — creating the game window, setting the background, and controlling the canvas.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Watch the Video
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;Learn about the Display class in Limn Engine — YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the next video in the Limn Engine tutorial series. It focuses on the &lt;code&gt;Display&lt;/code&gt; class — the foundation of every Limn Engine game. It walks through creating the game window, setting the background color, and controlling the canvas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Every Limn Engine game starts with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&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;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line creates the canvas, sets up the game loop, and prepares the engine to draw. Everything else — every component, every particle, every score, every pixel — is drawn through the Display.&lt;/p&gt;

&lt;p&gt;The Display class is easy to use but easy to misuse. There are a few things you need to get right — the order of &lt;code&gt;perform()&lt;/code&gt; and &lt;code&gt;start()&lt;/code&gt;, the variable name &lt;code&gt;display&lt;/code&gt;, and the difference between the canvas and the world. Getting them wrong doesn't crash the engine, but it does make your game behave in ways that are hard to debug.&lt;/p&gt;

&lt;p&gt;This video walks through the Display class from start to finish.&lt;/p&gt;

&lt;p&gt;Here's what the video covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a Display and starting the canvas&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;perform()&lt;/code&gt; method — why it comes first, and what breaks if you forget&lt;/li&gt;
&lt;li&gt;Setting the background color&lt;/li&gt;
&lt;li&gt;Sizing the canvas&lt;/li&gt;
&lt;li&gt;The camera and how it relates to the Display&lt;/li&gt;
&lt;li&gt;Common mistakes and how to avoid them&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎬 What the Video Covers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Creating a Display.&lt;/strong&gt; The video starts with the one-line setup — &lt;code&gt;new Display()&lt;/code&gt; — and explains what happens under the hood when you create it. The engine creates a canvas element, sets up a 2D drawing context, and prepares the game loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;perform()&lt;/code&gt; method.&lt;/strong&gt; Most of the video focuses on this. &lt;code&gt;display.perform()&lt;/code&gt; is what switches the engine from the default &lt;code&gt;setInterval&lt;/code&gt; loop to &lt;code&gt;requestAnimationFrame&lt;/code&gt;. Without it, the game runs at about 50fps with inconsistent pacing. With it, the game runs at a smooth 60fps (or whatever the monitor's refresh rate is).&lt;/p&gt;

&lt;p&gt;The order matters: &lt;code&gt;perform()&lt;/code&gt; must come before &lt;code&gt;start()&lt;/code&gt;. If you call &lt;code&gt;start()&lt;/code&gt; first, the patch doesn't take effect, and the game runs on the slow loop. This is one of the most common mistakes beginners make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting the background color.&lt;/strong&gt; The video shows &lt;code&gt;display.backgroundColor()&lt;/code&gt;, which sets the canvas background via CSS. It also shows the gradient options — &lt;code&gt;lgradient()&lt;/code&gt; for linear gradients and &lt;code&gt;rgradient()&lt;/code&gt; for radial ones — for more polished backgrounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing the canvas.&lt;/strong&gt; &lt;code&gt;display.start(width, height)&lt;/code&gt; sets the canvas dimensions. The video covers how to choose a size, how the size relates to mobile screens, and how to scale the canvas with CSS without changing its internal resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The camera.&lt;/strong&gt; The Display has a camera attached to it. The video explains how the camera moves independently of the canvas, how world coordinates differ from screen coordinates, and how to set the camera's world size so it doesn't scroll past the edges of the level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes.&lt;/strong&gt; The video finishes with the most common Display-related bugs: forgetting &lt;code&gt;perform()&lt;/code&gt;, using a different variable name than &lt;code&gt;display&lt;/code&gt;, and calling &lt;code&gt;start()&lt;/code&gt; more than once.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The engine:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The online editor (no download required):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The arcade (play games made by others):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📺 Full Video Series
&lt;/h2&gt;

&lt;p&gt;This video is part of the ongoing Limn Engine tutorial series. Here's the current lineup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5" rel="noopener noreferrer"&gt;Series Start&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Introduction to the series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Setup and first Display&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Components and movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What Limn is, and how to get started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;The Display Class&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creating the game window, background colors, and canvas control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  📝 Quick Reference: The Display Class
&lt;/h2&gt;

&lt;p&gt;Here's the minimal Display setup from the video, in case you want to follow along without typing every line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Display Demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"epic.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the engine&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&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;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// MUST come before start()&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Create the canvas&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set the background&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0a0a0a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Game loop&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Game logic goes here&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the gradient version, for a more polished background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lgradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottom&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;#0f3460&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;#16213e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rgradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#1a1a2e&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;#0a0a0f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;new Display()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates the canvas, the game loop, and the camera&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.perform()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Switches to &lt;code&gt;requestAnimationFrame&lt;/code&gt; for smooth 60fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Order matters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;perform()&lt;/code&gt; must come before &lt;code&gt;start()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.start(w, h)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets the canvas size and begins the game loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.backgroundColor()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets the canvas background via CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.lgradient()&lt;/code&gt; / &lt;code&gt;rgradient()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gradient backgrounds for a more polished look&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.camera&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controls what part of the world is visible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Forgetting &lt;code&gt;perform()&lt;/code&gt;.&lt;/strong&gt; Without it, your game runs at about 50fps on an unreliable &lt;code&gt;setInterval&lt;/code&gt; loop. Movement feels inconsistent, and &lt;code&gt;dt&lt;/code&gt; doesn't behave the way you expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calling &lt;code&gt;start()&lt;/code&gt; before &lt;code&gt;perform()&lt;/code&gt;.&lt;/strong&gt; The patch doesn't take effect if &lt;code&gt;start()&lt;/code&gt; has already run. Always call &lt;code&gt;perform()&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming the Display something else.&lt;/strong&gt; The engine references &lt;code&gt;display&lt;/code&gt; internally. Calling your instance &lt;code&gt;game&lt;/code&gt; or &lt;code&gt;app&lt;/code&gt; will throw errors in places you don't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calling &lt;code&gt;start()&lt;/code&gt; more than once.&lt;/strong&gt; The engine creates a canvas on every &lt;code&gt;start()&lt;/code&gt; call. Multiple calls stack multiple canvases on the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Found a Bug?
&lt;/h2&gt;

&lt;p&gt;If you find something in the video that doesn't work, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please include your browser, your device, and the timestamp in the video where the issue appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;The Display Class&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Previous Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio (Editor)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"A full walkthrough of the Display class — creating the game window, setting the background, sizing the canvas, and controlling the camera in Limn Engine."&lt;/strong&gt; 🎮🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one Display at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>javascript</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>When a desktop automation is unsure whether Save worked</title>
      <dc:creator>macro8n | Desktop Automation</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:44:04 +0000</pubDate>
      <link>https://dev.to/macro8n/a-desktop-automation-needs-a-failure-plan-not-just-a-click-sequence-7d3</link>
      <guid>https://dev.to/macro8n/a-desktop-automation-needs-a-failure-plan-not-just-a-click-sequence-7d3</guid>
      <description>&lt;p&gt;A desktop workflow submits a form. The app pauses, then the window changes. Did the record save, or did the request fail?&lt;/p&gt;

&lt;p&gt;That is an &lt;strong&gt;unknown outcome&lt;/strong&gt;, and it deserves its own state in the workflow. Treating every uncertainty as a failure can create duplicate records. Treating it as success can silently lose work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the result as three states
&lt;/h2&gt;

&lt;p&gt;For a record-entry task, track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not submitted:&lt;/strong&gt; the workflow has not triggered Save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmed:&lt;/strong&gt; the app shows a reliable success signal, or the record can be found afterward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown:&lt;/strong&gt; Save was triggered, but the workflow did not get enough evidence to decide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the first state is safe to submit again automatically. In the unknown state, stop and inspect before retrying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer an idempotent operation when one exists
&lt;/h2&gt;

&lt;p&gt;If the service offers an API, ask whether it supports an idempotency key or a unique external reference. Repeating the same request with the same key can then return the original result instead of creating a second record. A desktop interface may not offer that guarantee, so do not assume that clicking Save twice is harmless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the workflow a way to reconcile
&lt;/h2&gt;

&lt;p&gt;A unique value can help the workflow check whether a submission already landed. For example, use an order reference or a task-specific label that the app can search. After an uncertain save, look for that reference before deciding whether to submit again. Keep the search narrow and avoid logging the full form if it contains personal or confidential data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the fallback explicit
&lt;/h2&gt;

&lt;p&gt;If the record is found, mark the step complete and continue. If it is absent and the app is responsive, a bounded retry may be reasonable. If the search itself fails or returns ambiguous results, stop and ask a person to review. Do not turn “I could not verify it” into “it did not happen.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the uncertain path
&lt;/h2&gt;

&lt;p&gt;Use a test record and interrupt the flow immediately after Save: close the dialog, delay the response, or make the confirmation hard to read. Then check that the workflow enters the unknown state and does not blindly submit a second time. This edge case is often more valuable than another successful happy-path run.&lt;/p&gt;

&lt;p&gt;A reliable automation records what it knows, distinguishes that from what it merely assumes, and chooses a safe next step. That matters most around actions that create, send, delete, or charge something.&lt;/p&gt;

&lt;p&gt;macro8n is a local-first desktop app for building visual workflows. These design ideas apply to any UI automation tool.&lt;/p&gt;

&lt;p&gt;Disclosure: this article was generated with AI assistance for the macro8n account.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Excel to JSON: Free Online Excel to JSON Converter — How-To Guide</title>
      <dc:creator>Layoff Atlas</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:43:33 +0000</pubDate>
      <link>https://dev.to/layoffatlas/excel-to-json-free-online-excel-to-json-converter-how-to-guide-1jdi</link>
      <guid>https://dev.to/layoffatlas/excel-to-json-free-online-excel-to-json-converter-how-to-guide-1jdi</guid>
      <description>&lt;h1&gt;
  
  
  Excel to JSON: Free Online Excel to JSON Converter — How-To Guide
&lt;/h1&gt;

&lt;p&gt;Spreadsheets are easy to edit but terrible to feed into code. JSON is the opposite: every API, test suite, config file, and front-end app speaks it fluently. &lt;strong&gt;Excel to JSON&lt;/strong&gt; bridges the gap — it converts the first worksheet of any Excel file into clean JSON records, right in your browser, with zero sign-up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it free: &lt;a href="https://www.toolsfam.com/tools/excel-to-json" rel="noopener noreferrer"&gt;Excel to JSON&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool does
&lt;/h2&gt;

&lt;p&gt;Excel to JSON parses your &lt;code&gt;.xlsx&lt;/code&gt; (or &lt;code&gt;.xls&lt;/code&gt;) file with the real XLSX library, takes the first worksheet, and turns each row into a JSON object keyed by the column headers. You get instant validation, a copy button, and a download button, plus sample data if you want to experiment before uploading your own file. Because the parsing happens client-side, nothing is ever sent to a server — ToolsFam never sees your spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this tool is most useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API testing:&lt;/strong&gt; you have fixture data in a spreadsheet and need JSON payloads for Postman, Insomnia, or an automated test suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend prototypes:&lt;/strong&gt; loading a product catalog, employee list, or dataset from a static JSON file instead of wiring up a database on day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration checks:&lt;/strong&gt; comparing old-system spreadsheet exports against the JSON shape the new system expects, before the real import.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config files:&lt;/strong&gt; turning a settings table maintained by non-developers into the JSON config your app actually reads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to use it, step by step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open the tool&lt;/strong&gt; at &lt;a href="https://www.toolsfam.com/tools/excel-to-json" rel="noopener noreferrer"&gt;Excel to JSON&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop in your Excel file&lt;/strong&gt; — drag and drop or use the file picker. Only the first worksheet is converted, so move the data you need onto Sheet1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review the parsed table.&lt;/strong&gt; Check that headers look right, row counts match, and empty cells and numeric-looking strings (like zip codes with leading zeros) are handled as you expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy or download.&lt;/strong&gt; Use the copy button to grab the JSON for pasting into code or API testers, or download it as a &lt;code&gt;.json&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanity-check before production.&lt;/strong&gt; Confirm the output matches the schema your destination system expects — objects vs. arrays, escaped characters, and missing values are the usual suspects.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3 practical tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean your headers first.&lt;/strong&gt; Consistent, typo-free headers in row 1 become your JSON keys. Spaces and merged cells in the header row cause the most conversion headaches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use small representative samples.&lt;/strong&gt; When testing a migration or API import, run a handful of rows first so the output shape is predictable before you convert the full file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch out for nested values and dates.&lt;/strong&gt; Review how the tool represents dates, booleans, and cells containing commas or quotes — different destinations expect different formats.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What does Excel to JSON convert?&lt;/strong&gt;&lt;br&gt;
A: The first worksheet of your Excel file. Each row becomes a JSON record, keyed by the column headers in row 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is my data uploaded anywhere?&lt;/strong&gt;&lt;br&gt;
A: No. The workbook is parsed locally in your browser with the XLSX library. Your files are never sent to ToolsFam for conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What input shape works best?&lt;/strong&gt;&lt;br&gt;
A: Consistent rows, clear headers, valid syntax, and small representative samples produce the most predictable conversion. Clean headers first for the cleanest output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What should I check after conversion?&lt;/strong&gt;&lt;br&gt;
A: Escaped characters, missing values, arrays versus objects, and whether the destination system expects a specific schema — review before using converted output in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy note
&lt;/h2&gt;

&lt;p&gt;Excel to JSON runs 100% in your browser — files never leave your device. There is no account, no upload, and no server-side processing, so even sensitive spreadsheets are safe to convert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it free: &lt;a href="https://www.toolsfam.com/tools/excel-to-json" rel="noopener noreferrer"&gt;Excel to JSON&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>howto</category>
    </item>
    <item>
      <title>Originality.ai Review 2026: Accuracy, Features, Pricing, and Best AI Detector Alternatives</title>
      <dc:creator>Arnie Parks</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:38:52 +0000</pubDate>
      <link>https://dev.to/arnie_parks_159b7eefac931/originalityai-review-2026-accuracy-features-pricing-and-best-ai-detector-alternatives-57mj</link>
      <guid>https://dev.to/arnie_parks_159b7eefac931/originalityai-review-2026-accuracy-features-pricing-and-best-ai-detector-alternatives-57mj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8h8mhd3tpx609f4mtt0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8h8mhd3tpx609f4mtt0d.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI-generated content is everywhere now. From blog posts and academic essays to marketing copy and technical documentation, tools like ChatGPT, Claude, and Gemini have changed how people write.&lt;/p&gt;

&lt;p&gt;That has also created a new challenge: &lt;strong&gt;how can you tell whether a piece of writing was created by a human or generated by AI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI detection tools are designed to help answer that question. One of the better-known options is Originality.ai, a platform that focuses on identifying potentially AI-generated writing and checking content for plagiarism.&lt;/p&gt;

&lt;p&gt;But how reliable is Originality.ai in 2026? What features does it offer? And are there alternatives worth considering?&lt;/p&gt;

&lt;p&gt;In this Originality.ai review, we'll explore how the platform works, what its detection results mean, where it performs well, and how it compares with other AI detectors, including Winston AI.&lt;/p&gt;

&lt;p&gt;Whether you're a blogger, editor, teacher, student, or content manager, understanding these differences can help you choose a tool that fits your workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Originality.ai?
&lt;/h2&gt;

&lt;p&gt;Originality.ai is a content verification platform designed to help users evaluate written material.&lt;/p&gt;

&lt;p&gt;Its primary features include AI content detection and plagiarism checking, making it relevant to publishers, website owners, editors, and teams managing large amounts of written content.&lt;/p&gt;

&lt;p&gt;For example, imagine you run a website that publishes articles from several freelance writers.&lt;/p&gt;

&lt;p&gt;You want to maintain consistent editorial standards, verify originality, and understand whether submitted content may have been generated using AI.&lt;/p&gt;

&lt;p&gt;Instead of reviewing every article manually for possible AI involvement, you can use a detection tool as an additional screening step.&lt;/p&gt;

&lt;p&gt;Originality.ai provides an AI detection assessment that can help identify content worth reviewing more closely.&lt;/p&gt;

&lt;p&gt;However, there's an important distinction to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An AI detection score is an estimate based on patterns in writing. It is not definitive proof of who wrote a document.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That limitation applies to Originality.ai and other AI detectors.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Does Originality.ai Detect AI-Generated Content?
&lt;/h2&gt;

&lt;p&gt;AI detectors analyze characteristics of written text to estimate whether it resembles human writing or content generated by language models.&lt;/p&gt;

&lt;p&gt;These systems are generally trained using examples of human-written and AI-generated text.&lt;/p&gt;

&lt;p&gt;During analysis, the detector evaluates patterns in the submitted material and produces a result indicating how strongly the text resembles AI-generated writing.&lt;/p&gt;

&lt;p&gt;Originality.ai uses its own detection models to generate these assessments.&lt;/p&gt;

&lt;p&gt;For users, the process is relatively straightforward.&lt;/p&gt;

&lt;p&gt;You submit content, initiate a scan, and review the results.&lt;/p&gt;

&lt;p&gt;The platform then provides an assessment that can help guide your next steps.&lt;/p&gt;

&lt;p&gt;For instance, an editor might use the result to decide whether an article needs additional review.&lt;/p&gt;

&lt;p&gt;A content manager might compare the result with a writer's draft history or editorial guidelines.&lt;/p&gt;

&lt;p&gt;The important part is what happens after the scan.&lt;/p&gt;

&lt;p&gt;Detection results become more useful when they are considered alongside other evidence rather than treated as a final verdict.&lt;/p&gt;




&lt;h2&gt;
  
  
  Originality.ai Accuracy in 2026: How Reliable Is It?
&lt;/h2&gt;

&lt;p&gt;Accuracy is usually the first question people ask when researching an AI detector.&lt;/p&gt;

&lt;p&gt;And it makes sense.&lt;/p&gt;

&lt;p&gt;If you're paying for a tool that identifies AI-generated writing, you want to know whether its results are dependable.&lt;/p&gt;

&lt;p&gt;However, AI detection accuracy is more complicated than a single percentage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding AI Detection Accuracy
&lt;/h3&gt;

&lt;p&gt;When evaluating an AI detector, several factors matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;True positives&lt;/strong&gt; occur when the detector correctly identifies AI-generated content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;True negatives&lt;/strong&gt; occur when it correctly recognizes human-written content.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;false positive&lt;/strong&gt; happens when human-written text is incorrectly flagged as AI-generated.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;false negative&lt;/strong&gt; happens when AI-generated content is classified as human-written.&lt;/p&gt;

&lt;p&gt;These distinctions are especially important in education and professional publishing.&lt;/p&gt;

&lt;p&gt;A false positive could lead someone to question an authentic piece of writing.&lt;/p&gt;

&lt;p&gt;A false negative could allow AI-generated content to pass through a screening process without being identified.&lt;/p&gt;

&lt;p&gt;A detector that performs well in one testing environment may not achieve the same results with every writing style, language, or document type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Accuracy Results Can Vary
&lt;/h3&gt;

&lt;p&gt;Consider three different pieces of content.&lt;/p&gt;

&lt;p&gt;The first is a completely AI-generated blog post.&lt;/p&gt;

&lt;p&gt;The second is a human-written essay with a few AI-assisted edits.&lt;/p&gt;

&lt;p&gt;The third is a technical article written by a person using highly structured language.&lt;/p&gt;

&lt;p&gt;Even though these documents have different origins, some of their writing patterns may overlap.&lt;/p&gt;

&lt;p&gt;AI detectors can find fully generated passages easier to classify in some circumstances than heavily edited or mixed-authorship documents.&lt;/p&gt;

&lt;p&gt;Short passages can also be more difficult to assess because they provide less text for analysis.&lt;/p&gt;

&lt;p&gt;This is why independent testing should consider multiple document types rather than relying on a handful of examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Originality.ai Detect ChatGPT, Claude, and Gemini?
&lt;/h3&gt;

&lt;p&gt;Originality.ai is designed to identify AI-generated writing, including content associated with major language models.&lt;/p&gt;

&lt;p&gt;However, performance can vary depending on the model, the amount of editing, the length of the passage, and the detector version being used.&lt;/p&gt;

&lt;p&gt;A passage copied directly from an AI chatbot may produce a different result from the same passage after substantial human revision.&lt;/p&gt;

&lt;p&gt;That does not necessarily mean the detector is malfunctioning.&lt;/p&gt;

&lt;p&gt;It reflects the broader challenge of identifying authorship from text alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most practical approach is to treat AI detection as a screening method, not a guaranteed authorship test.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Originality.ai Features: What Does It Offer?
&lt;/h2&gt;

&lt;p&gt;Originality.ai is more than a basic text checker.&lt;/p&gt;

&lt;p&gt;Its appeal comes from combining several content review functions in one platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. AI Content Detection
&lt;/h3&gt;

&lt;p&gt;The platform's main feature is its ability to assess whether writing resembles AI-generated content.&lt;/p&gt;

&lt;p&gt;This is useful for publishers reviewing submissions, editors managing freelance writers, and teams maintaining content standards.&lt;/p&gt;

&lt;p&gt;For example, a blog editor might scan an article before publication and use the result to determine whether additional editorial review is appropriate.&lt;/p&gt;

&lt;p&gt;The score should not automatically decide whether the article gets published.&lt;/p&gt;

&lt;p&gt;Instead, it can help identify questions worth investigating.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Plagiarism Checking
&lt;/h3&gt;

&lt;p&gt;Plagiarism detection and AI detection are often discussed together, but they address different concerns.&lt;/p&gt;

&lt;p&gt;AI detection estimates whether writing resembles machine-generated text.&lt;/p&gt;

&lt;p&gt;Plagiarism checking looks for similarities between submitted content and existing sources.&lt;/p&gt;

&lt;p&gt;An article can be entirely human-written and still contain plagiarized passages.&lt;/p&gt;

&lt;p&gt;Likewise, AI-generated content can be original in wording without necessarily being appropriate for a particular assignment or publication.&lt;/p&gt;

&lt;p&gt;For content teams, having access to both types of checks can be useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Readability Analysis
&lt;/h3&gt;

&lt;p&gt;Readability matters because content needs to be understandable to its intended audience.&lt;/p&gt;

&lt;p&gt;A technically accurate article may still be difficult to read if the sentences are unnecessarily complicated.&lt;/p&gt;

&lt;p&gt;Readability tools help writers and editors assess whether the language is suitable for their readers.&lt;/p&gt;

&lt;p&gt;For website publishers, this can support the editing process, although readability scores should never replace thoughtful writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Website and Content Review Workflows
&lt;/h3&gt;

&lt;p&gt;Originality.ai also targets teams that need to evaluate content across websites or larger publishing workflows.&lt;/p&gt;

&lt;p&gt;This makes it particularly relevant to SEO professionals, content agencies, and businesses managing multiple contributors.&lt;/p&gt;

&lt;p&gt;A team reviewing dozens of articles each week may benefit from a structured process for checking submitted content.&lt;/p&gt;

&lt;p&gt;However, the value of these features depends on how frequently you publish and what your editorial process requires.&lt;/p&gt;




&lt;h2&gt;
  
  
  Originality.ai Pricing: Is It Worth Paying For?
&lt;/h2&gt;

&lt;p&gt;Pricing is an important part of any Originality.ai review.&lt;/p&gt;

&lt;p&gt;The platform has offered credit-based and subscription-oriented purchasing options, but available plans, limits, and prices can change.&lt;/p&gt;

&lt;p&gt;Rather than relying on an outdated price quoted in an article, it's better to check the current pricing details before subscribing.&lt;/p&gt;

&lt;p&gt;The more useful question is whether the platform's features match your workload.&lt;/p&gt;

&lt;p&gt;If you occasionally check a short article, your needs will differ from those of an agency processing hundreds of documents.&lt;/p&gt;

&lt;p&gt;A publisher may value plagiarism checks and content management features.&lt;/p&gt;

&lt;p&gt;A teacher may care more about readable reports and clear explanations of flagged passages.&lt;/p&gt;

&lt;p&gt;A student might simply want a way to review a draft before submitting it.&lt;/p&gt;

&lt;p&gt;Before choosing a plan, consider how many words you expect to scan, which features are included, and whether repeated scans consume additional credits.&lt;/p&gt;

&lt;p&gt;These details can affect the actual cost of using the platform.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Originality.ai Alternatives in 2026
&lt;/h1&gt;

&lt;p&gt;Originality.ai is one option in a growing AI detection market.&lt;/p&gt;

&lt;p&gt;Depending on your needs, another platform may offer a more suitable workflow, interface, or combination of features.&lt;/p&gt;

&lt;p&gt;Here are several alternatives worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Winston AI — A Practical Alternative for Content Verification
&lt;/h2&gt;

&lt;p&gt;One alternative worth considering is &lt;strong&gt;Winston AI&lt;/strong&gt;, an AI detection platform designed to help users review writing for signs of AI generation.&lt;/p&gt;

&lt;p&gt;It is relevant to educators, writers, editors, publishers, and professionals who regularly evaluate written content.&lt;/p&gt;

&lt;p&gt;One of its useful features is sentence-level highlighting, which helps users identify portions of a document that deserve closer attention.&lt;/p&gt;

&lt;p&gt;Rather than relying only on an overall detection result, users can review the highlighted sections and compare them with the rest of the text.&lt;/p&gt;

&lt;p&gt;For example, imagine you're editing a long article containing several sections.&lt;/p&gt;

&lt;p&gt;If a scan identifies particular passages as more likely to be AI-generated, you can examine those passages individually instead of treating the entire article as one uniform piece of writing.&lt;/p&gt;

&lt;p&gt;Winston AI also offers plagiarism checking and other writing-related features, including an AI Essay Grader.&lt;/p&gt;

&lt;p&gt;These features may be helpful for people who want more than a single AI detection score.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Does Winston AI Compare With Originality.ai?
&lt;/h3&gt;

&lt;p&gt;Both platforms aim to help users evaluate content, but their usefulness depends on the situation.&lt;/p&gt;

&lt;p&gt;Originality.ai is often considered by website publishers and content teams looking for AI detection alongside editorial verification features.&lt;/p&gt;

&lt;p&gt;Winston AI may appeal to users who want an approachable detection workflow with clear reporting and passage-level review.&lt;/p&gt;

&lt;p&gt;If you're researching detection performance, Winston AI also publishes information about its testing and benchmark results.&lt;/p&gt;

&lt;p&gt;You can explore its discussion of &lt;a href="https://gowinston.ai/most-accurate-ai-detector-across-leading-benchmarks/" rel="noopener noreferrer"&gt;AI detector accuracy across leading benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind that vendor-published benchmarks should be evaluated alongside independent tests, especially when comparing competing tools.&lt;/p&gt;

&lt;p&gt;Neither platform should be treated as a perfect method for proving authorship.&lt;/p&gt;

&lt;p&gt;The better choice is the one that fits your documents, review process, and expectations.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. GPTZero — An Option for Reviewing AI-Generated Writing
&lt;/h2&gt;

&lt;p&gt;GPTZero is another widely recognized name in AI content detection.&lt;/p&gt;

&lt;p&gt;It focuses on helping users evaluate whether text may have been generated by AI.&lt;/p&gt;

&lt;p&gt;The platform is frequently discussed in educational settings, where teachers and institutions may want additional context when reviewing written assignments.&lt;/p&gt;

&lt;p&gt;For example, an instructor might use GPTZero to identify passages that deserve a closer look.&lt;/p&gt;

&lt;p&gt;However, the instructor should also consider a student's writing history, assignment requirements, and available drafts.&lt;/p&gt;

&lt;p&gt;Like other detectors, GPTZero can produce incorrect classifications.&lt;/p&gt;

&lt;p&gt;Its results are best used as part of a broader review process.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Copyleaks — AI Detection and Plagiarism Checking
&lt;/h2&gt;

&lt;p&gt;Copyleaks is another alternative for people interested in content verification.&lt;/p&gt;

&lt;p&gt;It offers AI detection and plagiarism-related functionality, making it relevant to educational institutions and businesses.&lt;/p&gt;

&lt;p&gt;For organizations that already use formal document review processes, having multiple verification capabilities available can be convenient.&lt;/p&gt;

&lt;p&gt;Imagine a training organization reviewing submitted assignments.&lt;/p&gt;

&lt;p&gt;The team may need to check for similarities with existing sources while also evaluating whether the writing resembles AI-generated content.&lt;/p&gt;

&lt;p&gt;These are separate questions, and a platform that supports both checks can help organize the review.&lt;/p&gt;

&lt;p&gt;As always, the final decision should involve human judgment.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Turnitin — Academic Integrity and Institutional Workflows
&lt;/h2&gt;

&lt;p&gt;Turnitin is strongly associated with academic integrity and similarity checking.&lt;/p&gt;

&lt;p&gt;It is commonly used by educational institutions to review student submissions.&lt;/p&gt;

&lt;p&gt;Turnitin also offers AI writing detection capabilities in supported institutional workflows.&lt;/p&gt;

&lt;p&gt;However, access to particular features depends on the institution and its subscription.&lt;/p&gt;

&lt;p&gt;Unlike a general-purpose tool that an individual can immediately use for personal checks, Turnitin is often accessed through schools and universities.&lt;/p&gt;

&lt;p&gt;Its strongest fit is usually an established academic review process.&lt;/p&gt;

&lt;p&gt;Teachers should still avoid treating an AI detection indicator as conclusive evidence of misconduct.&lt;/p&gt;

&lt;p&gt;Student drafts, assignment context, and direct conversations remain important.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. ZeroGPT — A Straightforward AI Detection Option
&lt;/h2&gt;

&lt;p&gt;ZeroGPT is another name users encounter when searching for AI detection tools.&lt;/p&gt;

&lt;p&gt;Its appeal is largely tied to the simplicity of submitting text and receiving a detection assessment.&lt;/p&gt;

&lt;p&gt;For someone who wants a quick preliminary check, that straightforward approach may be convenient.&lt;/p&gt;

&lt;p&gt;However, ease of use does not establish accuracy.&lt;/p&gt;

&lt;p&gt;If you're using ZeroGPT or any similar platform for important decisions, you should test it with known human-written and AI-generated examples relevant to your work.&lt;/p&gt;

&lt;p&gt;A simple interface is helpful, but reliable interpretation matters more.&lt;/p&gt;




&lt;h1&gt;
  
  
  Originality.ai vs. Winston AI: Which Should You Choose?
&lt;/h1&gt;

&lt;p&gt;Comparing Originality.ai and Winston AI becomes easier when you focus on what you actually need.&lt;/p&gt;

&lt;p&gt;Suppose you're running a content agency.&lt;/p&gt;

&lt;p&gt;Your team publishes dozens of articles each month, works with multiple freelancers, and needs a consistent review process.&lt;/p&gt;

&lt;p&gt;In that situation, you might prioritize plagiarism checking, content management capabilities, and the cost of scanning large volumes of text.&lt;/p&gt;

&lt;p&gt;Now imagine you're a teacher reviewing essays or a writer checking a document before publication.&lt;/p&gt;

&lt;p&gt;You might care more about clear detection results, highlighted passages, and a workflow that makes it easy to inspect individual sections.&lt;/p&gt;

&lt;p&gt;Winston AI's sentence-level highlighting can be useful in that kind of review.&lt;/p&gt;

&lt;p&gt;The platforms overlap in their core purpose, but they do not necessarily offer identical experiences.&lt;/p&gt;

&lt;p&gt;There is also no universal winner for every document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best AI detector is the one that performs consistently on the kinds of content you actually review.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If possible, test the same collection of documents with more than one detector.&lt;/p&gt;

&lt;p&gt;Include known human-written samples, unedited AI-generated samples, and mixed-authorship documents.&lt;/p&gt;

&lt;p&gt;Then compare how understandable and actionable the results are.&lt;/p&gt;

&lt;p&gt;This gives you a more meaningful basis for choosing a tool than relying on a single advertised accuracy percentage.&lt;/p&gt;




&lt;h1&gt;
  
  
  Can AI Detectors Be Trusted in 2026?
&lt;/h1&gt;

&lt;p&gt;AI detection has improved as the technology has developed, but the underlying problem remains difficult.&lt;/p&gt;

&lt;p&gt;Modern language models can generate fluent, varied writing.&lt;/p&gt;

&lt;p&gt;At the same time, human writers often use predictable sentence structures, formal vocabulary, and standardized formats.&lt;/p&gt;

&lt;p&gt;That creates overlap between the patterns detectors are trying to distinguish.&lt;/p&gt;

&lt;p&gt;A well-written human essay may appear unusually consistent.&lt;/p&gt;

&lt;p&gt;An AI-generated article that has been heavily revised may look less like a typical model output.&lt;/p&gt;

&lt;p&gt;Because of this, even sophisticated detection systems can make mistakes.&lt;/p&gt;

&lt;p&gt;The right question is not whether an AI detector is perfect.&lt;/p&gt;

&lt;p&gt;It's whether the detector provides useful information while keeping its limitations clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why False Positives Matter
&lt;/h2&gt;

&lt;p&gt;A false positive occurs when a detector flags human-written text as AI-generated.&lt;/p&gt;

&lt;p&gt;This can be especially serious in education.&lt;/p&gt;

&lt;p&gt;Imagine a student writes an assignment independently but receives a high AI detection score.&lt;/p&gt;

&lt;p&gt;If the teacher treats that score as proof, the student could face an unfair accusation.&lt;/p&gt;

&lt;p&gt;A more responsible approach would involve examining the writing process, reviewing earlier drafts, and discussing the assignment with the student.&lt;/p&gt;

&lt;p&gt;The same principle applies to professional writing.&lt;/p&gt;

&lt;p&gt;An editor should not automatically reject a freelancer's work based solely on a detection result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why False Negatives Matter
&lt;/h2&gt;

&lt;p&gt;False negatives create the opposite problem.&lt;/p&gt;

&lt;p&gt;A detector may classify AI-generated writing as human-written.&lt;/p&gt;

&lt;p&gt;This can happen when content has been revised, shortened, paraphrased, or blended with human writing.&lt;/p&gt;

&lt;p&gt;That means a low AI detection score does not guarantee that a document was written entirely by a person.&lt;/p&gt;

&lt;p&gt;For publishers, it reinforces the importance of editorial review.&lt;/p&gt;

&lt;p&gt;For teachers, it shows why AI policies should focus on acceptable use and transparency rather than detector scores alone.&lt;/p&gt;




&lt;h1&gt;
  
  
  How to Test an AI Detector Before Using It
&lt;/h1&gt;

&lt;p&gt;If you're comparing Originality.ai with Winston AI or other alternatives, a small practical test can reveal a lot.&lt;/p&gt;

&lt;p&gt;Start with a human-written document whose authorship you know.&lt;/p&gt;

&lt;p&gt;Choose something representative of your normal work, such as an essay, blog post, or technical article.&lt;/p&gt;

&lt;p&gt;Next, create an AI-generated sample on a similar topic.&lt;/p&gt;

&lt;p&gt;Keep a copy of the original output before making any changes.&lt;/p&gt;

&lt;p&gt;Then prepare a third sample containing both human-written and AI-assisted sections.&lt;/p&gt;

&lt;p&gt;Run each document through the detectors you're considering.&lt;/p&gt;

&lt;p&gt;Pay attention to more than the overall score.&lt;/p&gt;

&lt;p&gt;Look at whether the tools flag the expected sections, whether the reports explain the results clearly, and whether the same document produces substantially different assessments.&lt;/p&gt;

&lt;p&gt;You should also consider practical details.&lt;/p&gt;

&lt;p&gt;Does the platform support your preferred document format?&lt;/p&gt;

&lt;p&gt;Can you review the results without a complicated process?&lt;/p&gt;

&lt;p&gt;Are the privacy terms appropriate for the content you're uploading?&lt;/p&gt;

&lt;p&gt;How much will repeated testing cost?&lt;/p&gt;

&lt;p&gt;This approach won't produce a scientifically definitive accuracy ranking.&lt;/p&gt;

&lt;p&gt;However, it can help you understand which platform works best for your specific workflow.&lt;/p&gt;




&lt;h1&gt;
  
  
  Is Originality.ai Good for SEO Content?
&lt;/h1&gt;

&lt;p&gt;This is another common question.&lt;/p&gt;

&lt;p&gt;SEO professionals often want to know whether AI-generated content affects Google rankings and whether an AI detector can help them publish better articles.&lt;/p&gt;

&lt;p&gt;The first thing to understand is that &lt;strong&gt;AI detection and SEO quality are not the same thing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An article can be written by a human and still provide little value.&lt;/p&gt;

&lt;p&gt;Likewise, content created with AI assistance can be useful when it is accurate, original, and carefully edited.&lt;/p&gt;

&lt;p&gt;Search performance depends on many factors, including relevance, helpfulness, technical quality, and how well a page serves its audience.&lt;/p&gt;

&lt;p&gt;An AI detector cannot tell you whether an article deserves to rank on Google.&lt;/p&gt;

&lt;p&gt;It can only provide an assessment related to possible AI authorship.&lt;/p&gt;

&lt;p&gt;For content teams, the practical value of Originality.ai or Winston AI is in supporting a review process.&lt;/p&gt;

&lt;p&gt;These tools may help editors examine content more closely, but they cannot replace fact-checking, subject-matter expertise, or thoughtful editing.&lt;/p&gt;

&lt;p&gt;If your goal is stronger SEO content, focus on answering real questions, providing accurate information, and offering something readers cannot easily find elsewhere.&lt;/p&gt;




&lt;h1&gt;
  
  
  Frequently Asked Questions About Originality.ai
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Is Originality.ai 100% Accurate?
&lt;/h2&gt;

&lt;p&gt;No AI detector should be assumed to be 100% accurate across all writing styles and situations.&lt;/p&gt;

&lt;p&gt;Performance can vary depending on the content, language model, editing process, and test conditions.&lt;/p&gt;

&lt;p&gt;Even strong detection results should be interpreted carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can Originality.ai Detect ChatGPT Content?
&lt;/h2&gt;

&lt;p&gt;Originality.ai is designed to detect AI-generated writing, including content produced by tools such as ChatGPT.&lt;/p&gt;

&lt;p&gt;However, detection performance is not guaranteed for every passage.&lt;/p&gt;

&lt;p&gt;Substantial editing, mixed authorship, and short text samples can affect results.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Best Alternative to Originality.ai?
&lt;/h2&gt;

&lt;p&gt;Winston AI is one alternative worth exploring, especially if you want clear AI detection reports and sentence-level highlighting.&lt;/p&gt;

&lt;p&gt;GPTZero, Copyleaks, Turnitin, and ZeroGPT are also options, depending on your needs.&lt;/p&gt;

&lt;p&gt;The best choice depends on the documents you review, the features you need, and the results you observe during testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Winston AI Better Than Originality.ai?
&lt;/h2&gt;

&lt;p&gt;Neither tool is automatically better for everyone.&lt;/p&gt;

&lt;p&gt;Winston AI offers features such as AI detection, sentence-level highlighting, and plagiarism checking.&lt;/p&gt;

&lt;p&gt;Originality.ai also provides AI detection and content verification capabilities.&lt;/p&gt;

&lt;p&gt;A direct comparison using representative documents is more useful than assuming one platform will always outperform the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can AI Detectors Identify Paraphrased AI Content?
&lt;/h2&gt;

&lt;p&gt;Sometimes, but performance varies.&lt;/p&gt;

&lt;p&gt;Paraphrasing can change the patterns detectors use to classify writing.&lt;/p&gt;

&lt;p&gt;This makes heavily edited or mixed-authorship content more challenging to assess reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should Teachers Use AI Detection Scores to Grade Students?
&lt;/h2&gt;

&lt;p&gt;AI detection scores should not determine a student's grade or serve as the sole basis for an academic misconduct allegation.&lt;/p&gt;

&lt;p&gt;Teachers can use detection tools as one source of information while considering drafts, writing history, classroom expectations, and direct discussions with students.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Google Penalize Content Just Because It Was Written by AI?
&lt;/h2&gt;

&lt;p&gt;Google's published guidance emphasizes content quality and usefulness rather than treating all AI-assisted writing as automatically unacceptable.&lt;/p&gt;

&lt;p&gt;Using an AI detector is not a substitute for creating helpful, trustworthy content.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Verdict: Is Originality.ai Worth It in 2026?
&lt;/h1&gt;

&lt;p&gt;Originality.ai remains a relevant option for people who want to assess AI-generated writing and check content for plagiarism.&lt;/p&gt;

&lt;p&gt;Its combination of verification features makes it worth considering for editors, publishers, and teams managing written content.&lt;/p&gt;

&lt;p&gt;However, whether it's worth paying for depends on your workflow.&lt;/p&gt;

&lt;p&gt;If you regularly review large volumes of content, features related to scanning and editorial management may matter most.&lt;/p&gt;

&lt;p&gt;If you prefer a clear interface and want to examine potentially AI-generated passages more closely, Winston AI is another option to explore.&lt;/p&gt;

&lt;p&gt;GPTZero, Copyleaks, Turnitin, and ZeroGPT may also be suitable in different situations.&lt;/p&gt;

&lt;p&gt;The biggest takeaway is that &lt;strong&gt;AI detection tools are most useful when they support human judgment rather than replace it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No single score can fully explain how a document was written.&lt;/p&gt;

&lt;p&gt;A reliable review process still requires context, careful reading, and an understanding of what the results can and cannot prove.&lt;/p&gt;

&lt;p&gt;As AI writing tools continue to evolve, choosing an AI detector will become less about finding a supposedly perfect score and more about finding a platform that helps you make better-informed decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you tried Originality.ai or Winston AI for checking written content? Which features have been most useful in your workflow?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Instagram DM Automation With n8n: Build a Keyword-Based Auto-Reply Flow (2026)</title>
      <dc:creator>Peter </dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:38:31 +0000</pubDate>
      <link>https://dev.to/peter11241342/instagram-dm-automation-with-n8n-build-a-keyword-based-auto-reply-flow-2026-3172</link>
      <guid>https://dev.to/peter11241342/instagram-dm-automation-with-n8n-build-a-keyword-based-auto-reply-flow-2026-3172</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb8a1k5w7in8np3xfm82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb8a1k5w7in8np3xfm82.png" alt="Cover image" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want Instagram DM automation without paying for another SaaS tool? Here's how to wire up a keyword-based auto-reply flow in n8n, step by step.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instagram DM automation sounds complicated, but at its core it's pretty simple: a message comes in, your system checks what it says, and it sends the right reply. That's it. You can build that with n8n, the open-source workflow tool, in an afternoon. This guide walks you through the full setup, from connecting Instagram to routing messages by keyword.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Connect Instagram to n8n using a webhook or the Instagram trigger node.&lt;/li&gt;
&lt;li&gt;Route incoming messages with a keyword-based router (n8n's Switch or IF nodes work great).&lt;/li&gt;
&lt;li&gt;Send replies back through the Instagram node.&lt;/li&gt;
&lt;li&gt;Test everything on a second account before it touches real followers.&lt;/li&gt;
&lt;li&gt;Respect Instagram's rules: official API only, no spam patterns, honor the messaging window.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What This Flow Actually Does
&lt;/h2&gt;

&lt;p&gt;Every workflow has the same skeleton: &lt;strong&gt;listen → decide → reply&lt;/strong&gt;. In n8n that looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;strong&gt;Instagram trigger&lt;/strong&gt; fires when someone DMs you or comments with a keyword.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Switch node&lt;/strong&gt; checks the message text for keywords like "price", "book", or "hours".&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Instagram send-message node&lt;/strong&gt; replies with the right answer for each keyword.&lt;/li&gt;
&lt;li&gt;A fallback path sends a generic "thanks for reaching out, here's how we can help" reply when no keyword matches.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can build this entire thing with zero custom code. That's the real appeal of n8n for this job.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Need Before You Start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An Instagram account switched to a &lt;strong&gt;Professional (Business or Creator)&lt;/strong&gt; account. This is non-negotiable: the official Instagram API only works with professional accounts, and any automation that ignores this is either scraping or begging to be banned.&lt;/li&gt;
&lt;li&gt;A Meta app with the Instagram messaging permissions, connected through OAuth. Keep the flow inside Instagram's official API. Third-party hacks that log in with your password are exactly how accounts get flagged.&lt;/li&gt;
&lt;li&gt;n8n running somewhere: their cloud trial, a VPS, or self-hosted with Docker. If self-hosted, make sure it's reachable over HTTPS so Meta can send webhooks to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, the Meta app setup is the fiddliest part. The actual n8n flow takes maybe 30 minutes once webhooks are flowing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Connect Instagram to n8n
&lt;/h2&gt;

&lt;p&gt;Create a workflow in n8n and add an &lt;strong&gt;Instagram trigger&lt;/strong&gt; as the first node. You'll authenticate with your Meta app credentials through OAuth. Set the trigger to listen for new direct messages.&lt;/p&gt;

&lt;p&gt;A quick sanity check: send yourself a DM from another account and confirm the execution fires in n8n. If it does, the plumbing works and everything after this is logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Add the Keyword Router
&lt;/h2&gt;

&lt;p&gt;This is the heart of the flow. Add a &lt;strong&gt;Switch node&lt;/strong&gt; after the trigger and create rules based on the incoming message text. A simple version looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rule 1:&lt;/strong&gt; message contains "price" or "cost" → send the pricing reply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule 2:&lt;/strong&gt; message contains "book" or "appointment" → send the booking link reply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule 3:&lt;/strong&gt; message contains "hours" or "open" → send the opening hours reply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule 4 (default):&lt;/strong&gt; everything else → send a friendly fallback reply that lists what people can ask about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep keywords lowercase and use "contains" matching so "What are your PRICES?" still hits. Don't over-engineer this. Four to six keyword buckets cover most small-business inboxes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Write the Replies
&lt;/h2&gt;

&lt;p&gt;Connect an &lt;strong&gt;Instagram send-message node&lt;/strong&gt; to each rule output. A few rules for replies that don't feel like a robot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One job per message.&lt;/strong&gt; Answer the question and add at most one next step, like a link. Nobody wants a wall of text in a DM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sound like a person.&lt;/strong&gt; "Hey! Here's our price list 👇" beats "Thank you for your inquiry. Please find attached pricing information."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include a fallback offer.&lt;/strong&gt; Every reply should tell the person what to do if that wasn't what they meant. Something like "not what you were after? just type 'help' and we'll sort it out."&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Add Guardrails
&lt;/h2&gt;

&lt;p&gt;A router without guardrails is how you end up spamming people. Add these before you go live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A per-user message cap.&lt;/strong&gt; Track who you've replied to and don't send more than one or two automated replies per person per day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "human takeover" path.&lt;/strong&gt; If the same person messages three times without a keyword match, stop replying automatically and flag the conversation for a human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 24-hour window.&lt;/strong&gt; Instagram's messaging rules limit promotional messages to people who contacted you recently. Keep automated replies conversational, not sales blasts, and you stay on the right side of it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5: Test on a Second Account First
&lt;/h2&gt;

&lt;p&gt;This one is boring and absolutely essential. Get a second Instagram account, DM your business account with each keyword, and watch the flow run. Check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keywords matching when they should (and not when they shouldn't)&lt;/li&gt;
&lt;li&gt;Replies landing in the right order&lt;/li&gt;
&lt;li&gt;The fallback firing on nonsense input like "asdfgh"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only point it at real followers when the test account works cleanly three times in a row.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the safest way to do Instagram DM automation?
&lt;/h3&gt;

&lt;p&gt;The safest approach is automation that runs on Instagram's official API, authenticates with OAuth through Facebook Login, and never touches your password. Tools that log in as you or scrape the site get accounts flagged. If a tool can't tell you exactly how it connects to Instagram, that's your answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is building my own flow with n8n better than using a dedicated tool?
&lt;/h3&gt;

&lt;p&gt;It depends on what you value. Building it yourself in n8n gives you full control and can be cheaper, but you're now the one maintaining webhooks, handling API changes, and fixing things at midnight when Meta ships an update. A dedicated platform handles all of that for you. The trade-off is control versus maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will Instagram ban my account for using n8n?
&lt;/h3&gt;

&lt;p&gt;Instagram doesn't ban tools; it bans behavior. Automation through the official API that replies to people who messaged you first, at sane volumes, is how thousands of businesses run their inboxes. What gets accounts restricted is spam behavior: blasting cold DMs, replying to the same person ten times a day, or using unofficial apps that scrape.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You'd Rather Not Self-Host
&lt;/h2&gt;

&lt;p&gt;Building this in n8n is fun and cheap, but it's also a weekend project that becomes an ongoing maintenance job. If you'd rather have the whole thing managed, here's the short version: Zextra is an Instagram DM automation platform and official Meta tech partner, so it runs comment-to-DM, keyword auto-replies, and follow-ups through the official API without you touching webhooks or servers. &lt;a href="https://zextra.io/instagram-dm-automation" rel="noopener noreferrer"&gt;That's the one option I'd point at if DIY isn't your thing.&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The best Instagram DM automation is the kind nobody notices. A keyword router built in n8n gets you 90% of the way there: listen for messages, match keywords, reply like a human, and stay quiet when you're not sure. The tool matters less than the discipline. Official API, sensible volumes, tested replies, and a human watching the edge cases will beat the fanciest setup with none of those.&lt;/p&gt;

</description>
      <category>instagram</category>
      <category>automation</category>
      <category>n8n</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Net Worth Calculator: What Your Number Means (and the Two Ratios Worth Watching)</title>
      <dc:creator>Smart Budget Sheets</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:31:37 +0000</pubDate>
      <link>https://dev.to/smartbudgetsheets/net-worth-calculator-what-your-number-means-and-the-two-ratios-worth-watching-23cb</link>
      <guid>https://dev.to/smartbudgetsheets/net-worth-calculator-what-your-number-means-and-the-two-ratios-worth-watching-23cb</guid>
      <description>&lt;p&gt;Net worth is one subtraction: everything you own minus everything you owe. The number itself is less useful than two things around it: how much of it you could actually reach in an emergency, and which way it is moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. List assets at today's value
&lt;/h2&gt;

&lt;p&gt;Cash, savings, retirement accounts, investments, your home at a realistic sale price, vehicles at resale value (not what you paid). Leave out furniture and electronics; they rarely sell for much.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. List every debt at its current balance
&lt;/h2&gt;

&lt;p&gt;Mortgage, car loan, student loans, credit cards, buy-now-pay-later, money owed to family.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Look at debt-to-asset ratio
&lt;/h2&gt;

&lt;p&gt;Debts divided by assets. Under 30% is strong, 30-60% is moderate, above 60% is high, and above 100% means you owe more than you own (common early in a career, not a verdict).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Look at liquid net worth
&lt;/h2&gt;

&lt;p&gt;Take out the home and vehicles, and the mortgage and car loan against them. What remains is what you could actually use if income stopped. Many people with a healthy headline number have a liquid figure near zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Track the change, not the snapshot
&lt;/h2&gt;

&lt;p&gt;Write the number down once a quarter. A rising net worth while debt falls is the real sign a budget is working.&lt;/p&gt;

&lt;p&gt;I built a free calculator that does all of this in the browser (no signup, nothing stored), including the change versus 12 months ago: &lt;a href="https://smartbudgetsheets.surge.sh/calculators/net-worth-calculator/" rel="noopener noreferrer"&gt;Net Worth Calculator&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>finance</category>
    </item>
    <item>
      <title>Build a job feed from Greenhouse, Lever and Ashby career pages (no scraping browser needed)</title>
      <dc:creator>Hải Hà Phan Thanh</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:27:51 +0000</pubDate>
      <link>https://dev.to/hi_hphanthanh_41c4083/build-a-job-feed-from-greenhouse-lever-and-ashby-career-pages-no-scraping-browser-needed-30f3</link>
      <guid>https://dev.to/hi_hphanthanh_41c4083/build-a-job-feed-from-greenhouse-lever-and-ashby-career-pages-no-scraping-browser-needed-30f3</guid>
      <description>&lt;p&gt;Most tech companies do not host their own job pages. Their careers page is a thin wrapper around an applicant tracking system (ATS) such as Greenhouse, Lever or Ashby, and those vendors expose public JSON endpoints for job boards. That means you can build a job feed, a hiring-signal tracker or a salary dataset without a headless browser.&lt;/p&gt;

&lt;p&gt;Here is how to do it yourself, and how to do it for eight ATS vendors at once with an Apify actor I built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DIY route: three endpoints
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# Greenhouse
&lt;/span&gt;&lt;span class="n"&gt;gh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://boards-api.greenhouse.io/v1/boards/stripe/jobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gh&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;gh&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;gh&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;absolute_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Lever (EU accounts use api.eu.lever.co)
&lt;/span&gt;&lt;span class="n"&gt;lv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.lever.co/v0/postings/spotify?mode=json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lv&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;lv&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;categories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Ashby
&lt;/span&gt;&lt;span class="n"&gt;ab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.ashbyhq.com/posting-api/job-board/openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeCompensation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ab&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ab&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(These are the vendors' public job-board endpoints; field names can change, so check their docs.) The field names differ for every vendor, dates come in different formats, and Workable, Workday, SmartRecruiters, Recruitee and BambooHR each need their own approach. Workday in particular needs a tenant, a datacenter number and a site name.&lt;/p&gt;

&lt;p&gt;To find a company's slug, open a job on their careers page and read the URL: &lt;code&gt;boards.greenhouse.io/&amp;lt;slug&amp;gt;&lt;/code&gt;, &lt;code&gt;jobs.lever.co/&amp;lt;slug&amp;gt;&lt;/code&gt;, &lt;code&gt;jobs.ashbyhq.com/&amp;lt;slug&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One schema for eight ATS
&lt;/h2&gt;

&lt;p&gt;My &lt;a href="https://apify.com/bachiembmt/ats-jobs-scraper" rel="noopener noreferrer"&gt;ATS Jobs Scraper actor&lt;/a&gt; covers Greenhouse, Lever, Ashby, Workable, Workday, SmartRecruiters, Recruitee and BambooHR and normalises them. No-code path: open the actor, paste board URLs (any mix) into &lt;code&gt;companies&lt;/code&gt;, set &lt;code&gt;keywords&lt;/code&gt; and &lt;code&gt;locations&lt;/code&gt;, click Start, export CSV.&lt;/p&gt;

&lt;p&gt;Python path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install apify-client pandas
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bachiembmt/ats-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://boards.greenhouse.io/stripe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jobs.lever.co/spotify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ashby:openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# source:slug form is accepted
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engineer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;london&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postedWithinDays&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxJobsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salaryMin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salaryMax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output fields: &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;company&lt;/code&gt;, &lt;code&gt;jobId&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;remote&lt;/code&gt;, &lt;code&gt;employmentType&lt;/code&gt;, &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt;, &lt;code&gt;postedAt&lt;/code&gt;, &lt;code&gt;updatedAt&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;applyUrl&lt;/code&gt;, &lt;code&gt;descriptionText&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A daily "new jobs" digest
&lt;/h2&gt;

&lt;p&gt;Schedule the actor in the Console with &lt;code&gt;postedWithinDays: 1&lt;/code&gt;, then fetch the last run's dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bachiembmt/ats-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send that to Slack, email or a Google Sheet from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits you should know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Salary is patchy.&lt;/strong&gt; Only Ashby, Lever and Recruitee publish pay ranges, and only when the employer fills them in. Greenhouse, Workday, SmartRecruiters, BambooHR and Workable have no public salary field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workday dates are approximate.&lt;/strong&gt; The list only says "Posted 3 Days Ago", and 30+ days is reported as 30. With &lt;code&gt;includeDescription&lt;/code&gt; you get the exact start date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BambooHR&lt;/strong&gt; list pages have no date; &lt;code&gt;postedAt&lt;/code&gt; is filled only with descriptions on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includeDescription&lt;/code&gt; costs one extra request per job on Workable, Workday, SmartRecruiters and BambooHR, at about 1 request per second.&lt;/li&gt;
&lt;li&gt;You need the company's board slug. The actor does not discover which companies exist, and it only reads companies that use these eight vendors.&lt;/li&gt;
&lt;li&gt;Only public, published postings. Boards that do not exist are skipped with a warning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Price
&lt;/h2&gt;

&lt;p&gt;Pay per event, charged only for jobs that pass your filters. It is about $1 per 1,000 jobs at the time of writing (check the Store page; I may adjust it). Scanning 50 company boards with ~40 jobs each is 2,000 jobs, so about $2.&lt;/p&gt;

&lt;p&gt;For a handful of companies on Greenhouse or Lever, the DIY snippets are free and fine. I built the actor for the mixed-vendor case where normalising eight formats is the real work. Disclosure: it's my project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://apify.com/bachiembmt/ats-jobs-scraper" rel="noopener noreferrer"&gt;ATS Jobs Scraper&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>jobs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Developer's Guide to Pricing Handmade Goods on Etsy</title>
      <dc:creator>seller-mind</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:26:05 +0000</pubDate>
      <link>https://dev.to/sellermind/the-developers-guide-to-pricing-handmade-goods-on-etsy-1apf</link>
      <guid>https://dev.to/sellermind/the-developers-guide-to-pricing-handmade-goods-on-etsy-1apf</guid>
      <description>&lt;p&gt;When I first started selling my handcrafted leather goods on Etsy, I thought pricing was incredibly straightforward. My formula was simple: materials plus time plus a little markup equals the final price. I’d look at similar items on the platform, pick a number in the same ballpark, and hit publish.&lt;/p&gt;

&lt;p&gt;Then my first batch of orders came in, and I actually looked at my payout statements. &lt;/p&gt;

&lt;p&gt;I was stunned. After listing fees, transaction fees, payment processing, and the occasional offsite ads fee, my "profit" was practically zero. On a few items, I was literally paying the customer for the privilege of making the product. I was doing the math in my head or using a messy, error-prone spreadsheet, and it was failing me.&lt;/p&gt;

&lt;p&gt;I realized that guessing my prices was a fast track to burnout. So I decided to build something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost of Ignoring Fee Math
&lt;/h2&gt;

&lt;p&gt;Etsy’s fee structure is notoriously complex, and it’s designed in a way that makes mental math nearly impossible. When you ignore the exact breakdown of these fees, you aren't just leaving money on the table; you are actively subsidizing your own business.&lt;/p&gt;

&lt;p&gt;Let's break down the standard costs for a US-based seller:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Listing fee:&lt;/strong&gt; $0.20 per item (lasts 4 months or until it sells).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction fee:&lt;/strong&gt; 6.5% of the total order amount (including shipping).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment processing fee:&lt;/strong&gt; 3% of the total plus a $0.25 fixed fee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offsite ads fee:&lt;/strong&gt; 12% if an offsite ad drives the sale (mandatory for shops making over $10k/year, optional for smaller shops).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest trap for new sellers is the fixed $0.25 payment processing fee. If you sell a $10 item, that fixed fee eats up 2.5% of your revenue instantly. If you sell a $50 item, it's only 0.5%. This means low-priced items suffer from a disproportionately high fee percentage. &lt;/p&gt;

&lt;p&gt;If you price a $15 item and assume a $5 profit margin, the reality after fees might be a $2 margin. Once you factor in the cost of materials and the hours spent crafting, you are essentially paying for the hobby of working. The cost of ignoring this math isn't just lower profits; it's the slow, painful death of your small business.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Technical Approach to Pricing and SEO
&lt;/h2&gt;

&lt;p&gt;To solve this, I needed a reliable, programmatic way to calculate my exact margins before I even thought about listing an item. I started by writing a vanilla JavaScript function to map out the exact fee breakdown. &lt;/p&gt;

&lt;p&gt;Here is the core logic I use to calculate Etsy fees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Etsy fee calculation logic&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateEtsyFees&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="nx"&gt;quantity&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="nx"&gt;hasOffsiteAds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&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;listingFee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&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;transactionFee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.065&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// 6.5%&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentFee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.03&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 3% + $0.25&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsiteAdFee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hasOffsiteAds&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.12&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalFees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listingFee&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;transactionFee&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;paymentFee&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offsiteAdFee&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;netProfit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;totalFees&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="nx"&gt;listingFee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transactionFee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paymentFee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offsiteAdFee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;totalFees&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;netProfit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;netProfit&lt;/span&gt; &lt;span class="o"&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;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&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="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&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="s1"&gt;N/A&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="c1"&gt;// Example: $25 handmade candle&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateEtsyFees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fees&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// netProfit: $21.30, margin: 85.2%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script was a game-changer. By passing in my target sale price, I could instantly see the &lt;code&gt;netProfit&lt;/code&gt; and the &lt;code&gt;margin&lt;/code&gt; percentage. If the margin was below my target threshold after factoring in material costs, I knew I had to raise the price or rethink the product entirely.&lt;/p&gt;

&lt;p&gt;But pricing is only half the battle. If your item is perfectly priced but nobody sees it in search, it doesn't matter. I needed to ensure my listings were optimized for Etsy's search algorithm. I wrote a quick script to analyze listing titles, as the title is the most heavily weighted SEO factor on the platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Analyze listing title for SEO quality&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analyzeTitleSEO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&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;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&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;issues&lt;/span&gt; &lt;span class="o"&gt;=&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;words&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title too short — add more keywords&lt;/span&gt;&lt;span class="dl"&gt;'&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;words&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;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title too long — may get truncated&lt;/span&gt;&lt;span class="dl"&gt;'&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No numbers — consider adding quantity/size&lt;/span&gt;&lt;span class="dl"&gt;'&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;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ALL CAPS — looks spammy&lt;/span&gt;&lt;span class="dl"&gt;'&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;wordCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;words&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="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;score&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;max&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="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;issues&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;20&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;This simple analyzer checks for common pitfalls. It ensures the title is long enough to contain long-tail keywords but short enough not to get truncated in mobile search results. It also flags the absence of numbers. Adding specifics like "Set of 2" or "12oz" not only helps with search matching but also sets clear expectations for the buyer, which reduces return rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Results and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Implementing this technical approach to my shop fundamentally changed how I operate. &lt;/p&gt;

&lt;p&gt;What worked best was shifting from "cost-plus" pricing to "margin-first" pricing. By using the fee calculator before listing, I stopped offering items that simply didn't make financial sense. I dropped my lowest-priced items and focused on higher-value goods where the fixed $0.25 payment fee didn't destroy my margins. My average order value went up, and more importantly, my stress levels went down.&lt;/p&gt;

&lt;p&gt;What surprised me the most was the impact of the SEO title analysis. I realized I had been writing titles for humans rather than search algorithms. I was using poetic, short titles like "Midnight Leather Wallet." By changing my approach to include more descriptive, keyword-rich phrases like "Mens RFID Blocking Leather Wallet Minimalist Bifold," my organic traffic increased noticeably. Adding numbers (like specifying the number of card slots) also significantly improved my click-through rates because buyers knew exactly what they were getting.&lt;/p&gt;

&lt;p&gt;If I could change one thing about my early approach, I would have integrated these calculations directly into my daily workflow from day one, rather than treating them as an afterthought. Relying on mental math or basic spreadsheets is a recipe for hidden losses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;p&gt;I eventually ended up packaging these fee calculators, SEO checks, and listing tools into a comprehensive toolkit called SellerMind. I built it to be the resource I wish I had when I first opened my shop.&lt;/p&gt;

&lt;p&gt;If you are currently struggling with the math or just want to double-check your margins before your next product launch, you can use the free Etsy Fee Calculator I built. It provides a full breakdown of listing, transaction, payment, and offsite ad fees so you can price with confidence.&lt;/p&gt;

&lt;p&gt;You can try it out here: &lt;a href="https://thesellermind.com/tools/fee-calculator" rel="noopener noreferrer"&gt;https://thesellermind.com/tools/fee-calculator&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full disclosure: I'm the developer of SellerMind. I built this because I needed it myself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>etsy</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>smallbusiness</category>
    </item>
  </channel>
</rss>
