<?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: quantoracledev</title>
    <description>The latest articles on DEV Community by quantoracledev (@quantoracle).</description>
    <link>https://dev.to/quantoracle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3888019%2F30856f4d-e8e0-4119-a17d-d56d1983bd6a.png</url>
      <title>DEV Community: quantoracledev</title>
      <link>https://dev.to/quantoracle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/quantoracle"/>
    <language>en</language>
    <item>
      <title>Detecting a website's tech stack: why grepping for framework names gets you wrong answers</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:05:11 +0000</pubDate>
      <link>https://dev.to/quantoracle/detecting-a-websites-tech-stack-why-grepping-for-framework-names-gets-you-wrong-answers-5a7</link>
      <guid>https://dev.to/quantoracle/detecting-a-websites-tech-stack-why-grepping-for-framework-names-gets-you-wrong-answers-5a7</guid>
      <description>&lt;p&gt;The obvious way to work out what a site is built with is to fetch the HTML and look for telltale strings. React, Next, Shopify, Cloudflare. It works often enough to feel like it works, which is the problem.&lt;/p&gt;

&lt;p&gt;Here is a live example. Fetch &lt;code&gt;vercel.com&lt;/code&gt; and grep for &lt;code&gt;svelte&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://vercel.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oi&lt;/span&gt; svelte | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hits. Vercel does not run SvelteKit. Both matches come from a footer link:&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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/docs/frameworks/full-stack/sveltekit"&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;SvelteKit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a docs nav item. Any detector built on "does the page contain the word" reports SvelteKit on a site that runs Next.js, and you would never notice, because the output looks exactly like a real detection.&lt;/p&gt;

&lt;p&gt;That is the whole difficulty. Not fetching. &lt;strong&gt;Distinguishing a technology that is running from a word that happens to appear.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually identifies a stack
&lt;/h2&gt;

&lt;p&gt;Five signal types, in rough order of how much I trust them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Response headers.&lt;/strong&gt; The most under-used and often the most precise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSI&lt;/span&gt; https://vercel.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"server&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;x-powered-by"&lt;/span&gt;
&lt;span class="c"&gt;# Server: Vercel&lt;/span&gt;
&lt;span class="c"&gt;# X-Powered-By: Next.js, Payload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single header names both the framework &lt;em&gt;and&lt;/em&gt; the CMS. Payload is not visible anywhere in the rendered page — no asset path, no DOM marker, no JS global. One header, one detection you cannot get any other way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Asset origins.&lt;/strong&gt; What the page loads, and from where. &lt;code&gt;cdn.shopify.com&lt;/code&gt; on a page means Shopify is serving it, in a way that a mention of the word "Shopify" does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://www.allbirds.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"cdn&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;shopify"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# 13&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that with 83 occurrences of the bare string &lt;code&gt;Shopify&lt;/code&gt; on the same page — most of them copy, schema markup, and script variable names. The 13 asset references are the evidence; the 83 are noise that happens to correlate. (Both counts drift every time they ship a homepage change — I watched them move while writing this. The ratio is the durable part, not the numbers.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cookies.&lt;/strong&gt; &lt;code&gt;_shopify_y&lt;/code&gt;, &lt;code&gt;wordpress_logged_in&lt;/code&gt;, &lt;code&gt;ASP.NET_SessionId&lt;/code&gt;. Hard to fake incidentally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. DOM markers.&lt;/strong&gt; &lt;code&gt;[data-reactroot]&lt;/code&gt;, &lt;code&gt;#__next&lt;/code&gt;, &lt;code&gt;[data-svelte]&lt;/code&gt;, &lt;code&gt;wp-content&lt;/code&gt; paths in the markup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. JS globals after hydration.&lt;/strong&gt; &lt;code&gt;window.__NUXT__&lt;/code&gt;, &lt;code&gt;window.__NEXT_DATA__&lt;/code&gt;, &lt;code&gt;window.Shopify&lt;/code&gt;, &lt;code&gt;React&lt;/code&gt; on &lt;code&gt;window&lt;/code&gt;. This is the tier that needs a real browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where static fetching genuinely runs out
&lt;/h2&gt;

&lt;p&gt;I want to be fair here, because "you need a headless browser" is over-claimed. Static fetching gets you further than people assume — Next.js sites usually announce themselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://www.notion.so | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"__NEXT_DATA__"&lt;/span&gt;   &lt;span class="c"&gt;# 1&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSI&lt;/span&gt; https://www.notion.so | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-powered-by      &lt;span class="c"&gt;# x-powered-by: Next.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both work with no browser at all.&lt;/p&gt;

&lt;p&gt;Where it does break down is &lt;strong&gt;client-rendered SPAs that ship an empty shell&lt;/strong&gt;. If the server returns &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; and everything else arrives via JS, there is nothing in the static HTML to match — the framework only becomes visible once the bundle executes and mutates the DOM. Same for anything injected by a tag manager, which is most analytics and chat widgets on a lot of sites.&lt;/p&gt;

&lt;p&gt;So the honest split: headers and asset origins cover a surprising amount. A browser is what you need for the client-side tier, and for avoiding the false-positive trap by checking what the page &lt;em&gt;did&lt;/em&gt; rather than what it &lt;em&gt;said&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DIY version
&lt;/h2&gt;

&lt;p&gt;A decent detector is a few hundred lines:&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;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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;follow&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;html&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// headers — highest confidence&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;powered&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;headers&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;x-powered-by&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;powered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;hits&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;powered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;framework&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`header: x-powered-by: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;powered&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="c1"&gt;// asset origins, NOT bare strings&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/cdn&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;shopify&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com/&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;html&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;hits&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="na"&gt;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;Shopify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecommerce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asset: cdn.shopify.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// DOM markers&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;div id="__next"/&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;html&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;hits&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="na"&gt;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;Next.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;framework&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom: #__next&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;p&gt;Note the shape: &lt;strong&gt;every hit carries the reason it fired.&lt;/strong&gt; That is not decoration. Without it you cannot tell a real detection from a footer link, and you will not find out which you have until someone acts on it.&lt;/p&gt;

&lt;p&gt;The ongoing cost is the part people underestimate. Signatures rot — frameworks change their markers between majors, &lt;code&gt;data-reactroot&lt;/code&gt; disappeared in React 18, CDNs get renamed, hosts add and drop headers. A detector is a maintenance commitment, not a weekend project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-call version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/fetchbase/tech-stack-detector" rel="noopener noreferrer"&gt;Tech Stack Detector&lt;/a&gt; renders each site in a real browser and returns one row per site, with the evidence for every match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/fetchbase~tech-stack-detector/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "urls": ["https://www.allbirds.com", "https://nextjs.org"] }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.allbirds.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"techCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shopify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"frameworks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"React"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google Analytics (GA4), Hotjar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hosting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cloudflare"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"technologies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shopify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecommerce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asset: https://cdn.shopify.com/…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"React"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"framework"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dom: [data-reactroot]"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stripe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asset: https://js.stripe.com/v3"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around 100 technologies across CMS, e-commerce, JS frameworks, analytics, marketing, CDN and hosting, server and language, payments, and consent tooling. Charged per site analysed; failures cost nothing.&lt;/p&gt;

&lt;p&gt;It is also callable as an MCP tool, which is genuinely the more interesting use — an agent asked "what is this company running" can go and find out rather than guessing from training data.&lt;/p&gt;

&lt;p&gt;Three ready-made variants if you want to skip the input wiring:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/tech-stack-detector/examples/detect-website-tech-stack" rel="noopener noreferrer"&gt;Detect any website's tech stack&lt;/a&gt; — the general sweep, evidence included&lt;br&gt;
&lt;a href="https://apify.com/fetchbase/tech-stack-detector/examples/check-if-site-uses-wordpress" rel="noopener noreferrer"&gt;Check whether a site runs WordPress&lt;/a&gt; — the single-question version&lt;br&gt;
&lt;a href="https://apify.com/fetchbase/tech-stack-detector/examples/detect-shopify-store" rel="noopener noreferrer"&gt;Find out if a store is on Shopify&lt;/a&gt; — asset-origin check, not a word search&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The underrated thing
&lt;/h2&gt;

&lt;p&gt;If you only do one thing from this post: &lt;strong&gt;read &lt;code&gt;x-powered-by&lt;/code&gt; before you parse any HTML.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is one header, most detectors treat it as an afterthought, and it gave up Vercel's CMS — Payload — which is invisible by every other method. For competitive research that is often the single highest-value field on the whole response, and it costs you a &lt;code&gt;HEAD&lt;/code&gt; request.&lt;/p&gt;

&lt;p&gt;The corollary, and the reason I wrote this: &lt;strong&gt;any detection without evidence attached is a guess wearing a confident face.&lt;/strong&gt; Vercel "uses SvelteKit" is what you get otherwise, and it looks identical to a real answer.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>devtools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The App Store review ceiling nobody mentions — and how to get 8x past it</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Thu, 23 Jul 2026 02:55:43 +0000</pubDate>
      <link>https://dev.to/quantoracle/the-app-store-review-ceiling-nobody-mentions-and-how-to-get-8x-past-it-484h</link>
      <guid>https://dev.to/quantoracle/the-app-store-review-ceiling-nobody-mentions-and-how-to-get-8x-past-it-484h</guid>
      <description>&lt;p&gt;If you've ever pulled App Store reviews programmatically, you've hit a wall you may not have noticed: &lt;strong&gt;you can only get 500 reviews per app.&lt;/strong&gt; Not 500 per request — 500, total, forever.&lt;/p&gt;

&lt;p&gt;Apple's public customer-reviews RSS feed is paginated at 50 entries and stops at page 10. Request page 11 and you get nothing back. Every App Store review tool inherits that ceiling, because they all read the same feed.&lt;/p&gt;

&lt;p&gt;Here's the part that isn't widely known: &lt;strong&gt;the ceiling is per storefront, not per app.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Each country is its own feed
&lt;/h2&gt;

&lt;p&gt;Apple runs ~175 country storefronts, and each one paginates independently. Same app, same endpoint shape, different &lt;code&gt;us&lt;/code&gt; / &lt;code&gt;gb&lt;/code&gt; / &lt;code&gt;de&lt;/code&gt; in the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://itunes.apple.com/us/rss/customerreviews/page=1/id=310633997/sortBy=mostRecent/json"&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://itunes.apple.com/de/rss/customerreviews/page=1/id=310633997/sortBy=mostRecent/json"&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://itunes.apple.com/br/rss/customerreviews/page=1/id=310633997/sortBy=mostRecent/json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No key, no auth, no scraping — this is Apple's own public feed.&lt;/p&gt;

&lt;p&gt;I measured it against WhatsApp across 10 storefronts. Result: &lt;strong&gt;4,250 unique reviews in 3.2 seconds, zero duplicates.&lt;/strong&gt; Versus the 500 you'd get from the US alone. That's &lt;strong&gt;8.5× on ten countries&lt;/strong&gt;; sweep all ~175 and the ceiling moves to roughly 87,500.&lt;/p&gt;

&lt;h2&gt;
  
  
  The more interesting output
&lt;/h2&gt;

&lt;p&gt;Depth was what I went looking for. The per-country breakdown turned out to be the more useful thing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storefront&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;th&gt;Ratings count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🇲🇽 Mexico&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5,893,510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇧🇷 Brazil&lt;/td&gt;
&lt;td&gt;4.78&lt;/td&gt;
&lt;td&gt;8,313,182&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇬🇧 UK&lt;/td&gt;
&lt;td&gt;4.71&lt;/td&gt;
&lt;td&gt;4,116,608&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇺🇸 US&lt;/td&gt;
&lt;td&gt;4.69&lt;/td&gt;
&lt;td&gt;18,293,098&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇰🇷 Korea&lt;/td&gt;
&lt;td&gt;4.68&lt;/td&gt;
&lt;td&gt;127,839&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇮🇳 India&lt;/td&gt;
&lt;td&gt;4.62&lt;/td&gt;
&lt;td&gt;7,960,859&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇩🇪 Germany&lt;/td&gt;
&lt;td&gt;4.60&lt;/td&gt;
&lt;td&gt;3,530,320&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇫🇷 France&lt;/td&gt;
&lt;td&gt;4.56&lt;/td&gt;
&lt;td&gt;3,432,960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🇯🇵 Japan&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;254,035&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same app, same week: &lt;strong&gt;4.78 in Mexico, 4.55 in Japan.&lt;/strong&gt; If you only ever query the US store, that spread is invisible to you — and it's exactly the thing an international product team wants to know. Which markets are we losing, and what are people saying there?&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it yourself
&lt;/h2&gt;

&lt;p&gt;The mechanics are simple; the tedium is in the details. Roughly:&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;PAGE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MAX_PAGES&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reviewsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cc&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;out&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&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;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_PAGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;page&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://itunes.apple.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/customerreviews/page=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sortBy=mostRecent/json`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;json&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="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&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="nx"&gt;url&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// past the ceiling Apple stops returning JSON&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;entry&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;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// page 1 can lead with an app-summary entry that has no im:rating — skip it&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;im:rating&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="nx"&gt;reviews&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;out&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="nx"&gt;reviews&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;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="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="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;im:rating&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&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;label&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;im:version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;label&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="nx"&gt;reviews&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="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&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;out&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;Things that will bite you: page 1 sometimes leads with an app-summary entry that has no rating; Apple returns non-JSON rather than an empty feed past the ceiling, so you need the &lt;code&gt;try/catch&lt;/code&gt; to stop cleanly; and not every app exists in every storefront — China commonly returns nothing, which is app availability, not an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or one call
&lt;/h2&gt;

&lt;p&gt;I packaged this as &lt;a href="https://apify.com/fetchbase/app-store-intelligence" rel="noopener noreferrer"&gt;App Store Reviews API&lt;/a&gt; — storefront sweeping, dedup, per-country rating breakdown, rating filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"310633997"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"countries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxReviewsPerCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few use cases worth stealing whether you build it or not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global complaints, filtered.&lt;/strong&gt; &lt;code&gt;minRating: 1, maxRating: 2&lt;/code&gt; across every storefront gives you a worldwide bug-and-churn report you can hand straight to an LLM.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/app-store-intelligence/examples/app-store-negative-reviews" rel="noopener noreferrer"&gt;Find negative reviews worldwide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Rating drift monitoring.&lt;/strong&gt; &lt;code&gt;includeReviews: false&lt;/code&gt; returns just the per-country ratings — cheap enough to run on a schedule and alert when a market starts sliding.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/app-store-intelligence/examples/app-rating-by-country" rel="noopener noreferrer"&gt;Compare an app's rating by country&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Competitive teardown.&lt;/strong&gt; Several apps, several countries, one dataset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/app-store-intelligence/examples/competitor-app-review-analysis" rel="noopener noreferrer"&gt;Compare competitor app reviews&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What this can't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;500 per storefront is Apple's limit, not a tool's.&lt;/strong&gt; Nothing gets past it for a single country. Sweeping storefronts is the only lever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent-first.&lt;/strong&gt; This is the live picture, not full review history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Google Play here.&lt;/strong&gt; Play has no equivalent public API, so it needs actual scraping — a different risk profile, deliberately out of scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing runs on Apple's documented public endpoints, which is why it's fast, doesn't need proxies, and doesn't break when a page layout changes. The rest of the suite is at &lt;a href="https://apify.com/fetchbase" rel="noopener noreferrer"&gt;apify.com/fetchbase&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curious whether anyone has found a documented way past the 10-page cap — if you have, I'd genuinely like to know.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>api</category>
      <category>data</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Scrape any company's job postings — Greenhouse, Lever &amp; Ashby, with one API call</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Wed, 22 Jul 2026 02:11:12 +0000</pubDate>
      <link>https://dev.to/quantoracle/scrape-any-companys-job-postings-greenhouse-lever-ashby-with-one-api-call-4db</link>
      <guid>https://dev.to/quantoracle/scrape-any-companys-job-postings-greenhouse-lever-ashby-with-one-api-call-4db</guid>
      <description>&lt;p&gt;Almost every tech company's job board runs on one of a handful of ATS platforms — Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee. And nearly all of them expose a &lt;strong&gt;public, documented JSON API&lt;/strong&gt; for their postings.&lt;/p&gt;

&lt;p&gt;Which means "scrape job postings" isn't really a scraping problem. It's a &lt;em&gt;normalization&lt;/em&gt; problem: six different response shapes, board-slug discovery, HTML-encoded descriptions, and compensation data that's structured differently everywhere it exists at all.&lt;/p&gt;

&lt;p&gt;Here's the DIY version, the one-call version, and one genuinely underrated thing hiding in this data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The raw APIs
&lt;/h2&gt;

&lt;p&gt;No key, no auth. These are live right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Greenhouse&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://boards-api.greenhouse.io/v1/boards/stripe/jobs"&lt;/span&gt;
&lt;span class="c"&gt;# Lever&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://api.lever.co/v0/postings/palantir?mode=json"&lt;/span&gt;
&lt;span class="c"&gt;# Ashby&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://api.ashbyhq.com/posting-api/job-board/openai"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of writing that's &lt;strong&gt;540 open roles at Stripe, 286 at Palantir, and 749 at OpenAI&lt;/strong&gt; — three calls, three completely different JSON shapes. (Those counts move every day. The shapes don't.)&lt;/p&gt;

&lt;p&gt;Greenhouse nests &lt;code&gt;offices&lt;/code&gt; and &lt;code&gt;departments&lt;/code&gt; as arrays of objects. Lever flattens everything into &lt;code&gt;categories&lt;/code&gt;. Ashby puts the city in &lt;code&gt;location.name&lt;/code&gt; and hides compensation behind a separate &lt;code&gt;includeCompensation=true&lt;/code&gt; flag. Descriptions come back as escaped HTML on some, Markdown-ish on others. Multiply by six platforms and you've got a weekend project plus ongoing maintenance every time one of them changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-call version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/fetchbase/job-postings-scraper" rel="noopener noreferrer"&gt;Job Postings API&lt;/a&gt; auto-detects which ATS a company uses — from a bare slug or a full careers URL — and returns one normalized row per job. Here's a &lt;strong&gt;real, unedited&lt;/strong&gt; record from a live run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/fetchbase~job-postings-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "companies": ["stripe", "gitlab", "https://jobs.ashbyhq.com/openai"] }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stripe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"greenhouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7954688"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Account Executive, AI Sales (Grower)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1654 Account Executives (AI)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"San Francisco, CA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://stripe.com/jobs/search?gh_jid=7954688"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T18:51:19-04:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"## Who we are&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;### About Stripe&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Stripe is a financial infrastructure..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;remote&lt;/code&gt; and &lt;code&gt;salary&lt;/code&gt; are &lt;code&gt;null&lt;/code&gt; there — and that's the honest part. &lt;strong&gt;Those fields are only as good as what the ATS publishes.&lt;/strong&gt; Greenhouse boards frequently omit both. The actor normalizes the shape; it can't invent data the source doesn't expose.&lt;/p&gt;

&lt;p&gt;Which brings us to the interesting bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underrated part: some boards publish full comp
&lt;/h2&gt;

&lt;p&gt;Ashby boards expose structured compensation, and plenty of companies leave it on. Of those &lt;strong&gt;749 OpenAI roles, 602 — about 80% — carry a published pay range&lt;/strong&gt;, formatted like &lt;code&gt;$257K – $335K • Offers Equity&lt;/code&gt;. &lt;strong&gt;477 are flagged remote.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a counting trap here worth naming, because I walked into it myself. The &lt;code&gt;compensation&lt;/code&gt; object is present on &lt;strong&gt;every&lt;/strong&gt; job, even when there's no range inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// a role with no published range — object exists, summary is null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"compensation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensationTierSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensationTiers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count the objects and you get a triumphant 100%. Count &lt;code&gt;compensationTierSummary&lt;/code&gt; and you get the real 80%. If you're building on this, key off the summary, not the parent.&lt;/p&gt;

&lt;p&gt;Even at 80%, that's a real, public, structured compensation dataset that most people assume you have to buy from Levels.fyi or scrape out of rendered HTML. It's sitting behind a GET request.&lt;/p&gt;

&lt;p&gt;Useful inputs while you're exploring:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;companies&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Slugs or careers URLs — mix ATSes freely in one run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remoteOnly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keep only roles the board flags remote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;titleFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Substring match on title (&lt;code&gt;"engineer"&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxJobsPerCompany&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cap big boards (default 1000)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;includeDescriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;false&lt;/code&gt; for a fast, light index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You're billed per job returned, so filters cut cost as well as noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things worth building with it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A "who's hiring in AI" snapshot.&lt;/strong&gt; One run across several labs, compare volume and department mix:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/job-postings-scraper/examples/ai-companies-hiring-now" rel="noopener noreferrer"&gt;See which AI companies are hiring right now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A remote-jobs alert.&lt;/strong&gt; &lt;code&gt;remoteOnly&lt;/code&gt; + &lt;code&gt;titleFilter: "engineer"&lt;/code&gt; across your target companies, on a schedule, diffed against yesterday's dataset → new postings to Slack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/job-postings-scraper/examples/remote-engineering-jobs-startups" rel="noopener noreferrer"&gt;Find remote engineering jobs at top startups&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Comp research.&lt;/strong&gt; Pull a whole Ashby board and you get ranges attached to titles and locations:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://apify.com/fetchbase/job-postings-scraper/examples/scrape-openai-jobs" rel="noopener noreferrer"&gt;Scrape OpenAI's openings&lt;/a&gt; — 749 live roles, ~80% carrying pay ranges&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Hiring as a market signal.&lt;/strong&gt; Posting counts over time are a leading indicator — teams that are shipping are hiring, and teams in trouble quietly stop. Snapshot weekly and you've built a trends dataset nobody sells you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the API route beats HTML scraping here
&lt;/h2&gt;

&lt;p&gt;These are &lt;em&gt;intended-use, documented&lt;/em&gt; endpoints. No bot walls, no proxy budget, no 3am breakage when a careers page gets redesigned. The tedious part — ATS detection, six-way shape merge, HTML→Markdown, comp parsing — is the part worth not rewriting yourself.&lt;/p&gt;

&lt;p&gt;Pay per job returned, failed runs cost nothing, and Apify's free credits cover plenty of testing. The rest of the utility suite lives at &lt;a href="https://apify.com/fetchbase" rel="noopener noreferrer"&gt;apify.com/fetchbase&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built this because job data shouldn't require scraping infrastructure. If you want another ATS supported or normalized salary output, say so in the comments or the actor's Issues tab.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>career</category>
      <category>data</category>
    </item>
    <item>
      <title>How to check Core Web Vitals for hundreds of URLs at once (API + code)</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Wed, 15 Jul 2026 02:37:16 +0000</pubDate>
      <link>https://dev.to/quantoracle/how-to-check-core-web-vitals-for-hundreds-of-urls-at-once-api-code-45ni</link>
      <guid>https://dev.to/quantoracle/how-to-check-core-web-vitals-for-hundreds-of-urls-at-once-api-code-45ni</guid>
      <description>&lt;p&gt;Google's PageSpeed Insights is great for auditing &lt;strong&gt;one&lt;/strong&gt; page. But the moment you want to track Core Web Vitals across a whole site — or every client site you manage, or every page after a deploy — clicking through a web UI (or fighting per-key rate limits) stops scaling.&lt;/p&gt;

&lt;p&gt;Here's how to measure &lt;strong&gt;LCP, CLS, FCP and TTFB in bulk&lt;/strong&gt;, in real Chromium, with a plain API call and no rate-limit juggling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're measuring
&lt;/h2&gt;

&lt;p&gt;The metrics that actually affect Google ranking and user experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LCP&lt;/strong&gt; (Largest Contentful Paint) — when the main content appears. Target &amp;lt; 2.5s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLS&lt;/strong&gt; (Cumulative Layout Shift) — how much the layout jumps. Target &amp;lt; 0.1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FCP&lt;/strong&gt; (First Contentful Paint) — first pixel of content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTFB&lt;/strong&gt; (Time To First Byte) — server responsiveness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus the &lt;em&gt;why&lt;/em&gt;: total page weight, request count, third-party load, DOM size — the things you actually change to fix a bad score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bulk approach
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/fetchbase/website-performance-audit" rel="noopener noreferrer"&gt;Website Performance Audit&lt;/a&gt; loads each URL in a real browser, measures Core Web Vitals via the standard &lt;code&gt;PerformanceObserver&lt;/code&gt; APIs (lab data, comparable to Lighthouse), and returns a 0–100 score plus a prioritized list of opportunities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/fetchbase~website-performance-audit/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "urls": ["https://example.com", "https://example.com/pricing", "https://example.com/blog"], "device": "mobile" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each URL comes back like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lcpMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fcpMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1290&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ttfbMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalKb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2648&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"requests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"thirdPartyRequests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"opportunities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Render-blocking resources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5 synchronous scripts/stylesheets in &amp;lt;head&amp;gt;…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Heavy JavaScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scripts total 1180 KB…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Make it a performance budget in CI
&lt;/h2&gt;

&lt;p&gt;The useful part isn't a one-time number — it's catching regressions. Fail your build if a key page's score drops or LCP crosses a threshold:&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;urls&lt;/span&gt; &lt;span class="o"&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;https://example.com&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;https://example.com/pricing&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;results&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.apify.com/v2/acts/fetchbase~website-performance-audit/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="dl"&gt;"&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;APIFY_TOKEN&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="s2"&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="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lcpMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&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;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lcpMs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lcpMs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cls&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;failures&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="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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Performance budget exceeded:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &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;f&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;failures&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;error&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — score &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, LCP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lcpMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms, CLS &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cls&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that in a GitHub Action after deploy and you get a hard gate on performance regressions — across as many pages as you want, in one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing a whole site on a schedule
&lt;/h2&gt;

&lt;p&gt;Feed it your sitemap URLs (or pair it with a crawler) and run it on an Apify &lt;strong&gt;Schedule&lt;/strong&gt; weekly. Sort the resulting dataset by &lt;code&gt;score&lt;/code&gt; ascending and you've got a prioritized worklist: worst pages first, each with the specific opportunities to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO, not just speed?
&lt;/h2&gt;

&lt;p&gt;Core Web Vitals are one ranking input. If you also want on-page SEO — titles, meta descriptions, headings, canonical tags, structured data, broken links — the companion &lt;a href="https://apify.com/fetchbase/website-seo-audit" rel="noopener noreferrer"&gt;SEO Audit&lt;/a&gt; actor returns all of that &lt;em&gt;plus&lt;/em&gt; Core Web Vitals in one record, each issue paired with a concrete fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Pay per page audited, no subscription, no per-key rate limits, failures free. Auditing a 100-page site is pocket change and runs in a couple of minutes — and Apify's free credits cover plenty of testing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built this because bulk Core Web Vitals shouldn't require a spreadsheet of PageSpeed tabs. Feedback and feature requests (INP? filmstrips?) via the actor's issues tab. The full utility suite is at &lt;a href="https://apify.com/fetchbase" rel="noopener noreferrer"&gt;apify.com/fetchbase&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Turn any website into clean Markdown for your RAG pipeline (with code)</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Sun, 12 Jul 2026 16:18:30 +0000</pubDate>
      <link>https://dev.to/quantoracle/turn-any-website-into-clean-markdown-for-your-rag-pipeline-with-code-34oe</link>
      <guid>https://dev.to/quantoracle/turn-any-website-into-clean-markdown-for-your-rag-pipeline-with-code-34oe</guid>
      <description>&lt;p&gt;If you've built anything with retrieval-augmented generation (RAG), you know the unglamorous truth: &lt;strong&gt;80% of the work is getting clean text in.&lt;/strong&gt; Your embeddings are only as good as the content you feed them, and raw web pages are a mess of nav bars, cookie banners, ads, share widgets, and &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; soup.&lt;/p&gt;

&lt;p&gt;This post shows a fast, reliable way to convert &lt;strong&gt;any URL — or a whole site — into clean, LLM-ready Markdown&lt;/strong&gt;, with code you can drop into a pipeline today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just &lt;code&gt;requests&lt;/code&gt; + BeautifulSoup?
&lt;/h2&gt;

&lt;p&gt;You can, and for a single static page it's fine:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;html&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://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&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_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this falls over fast in the real world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You get everything&lt;/strong&gt; — menus, footers, "related articles," cookie notices — all polluting your chunks and your embeddings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript-rendered sites&lt;/strong&gt; (React, Vue, Next.js) return an empty shell; you need a real browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure is lost&lt;/strong&gt; — headings, lists, tables and code blocks matter to an LLM, and &lt;code&gt;.get_text()&lt;/code&gt; flattens them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawling a whole docs site&lt;/strong&gt; means writing link-following, dedup, robots.txt handling, and concurrency yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Readability + a headless browser + a Markdown converter solves the quality problem, but now you're maintaining browser infrastructure. That's the part worth outsourcing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hosted approach
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/fetchbase/website-to-markdown" rel="noopener noreferrer"&gt;Website to Markdown&lt;/a&gt; runs the whole pipeline for you — real Chromium rendering → Mozilla Readability (main-content extraction) → Markdown with headings, links, tables and code preserved. It respects &lt;code&gt;robots.txt&lt;/code&gt;, can crawl a site via its sitemap, and returns one clean record per page.&lt;/p&gt;

&lt;h3&gt;
  
  
  One page
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/fetchbase~website-to-markdown/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "urls": ["https://en.wikipedia.org/wiki/Retrieval-augmented_generation"] }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://en.wikipedia.org/wiki/Retrieval-augmented_generation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Retrieval-augmented generation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wordCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"**Retrieval-augmented generation (RAG)** is a technique that…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A whole documentation site
&lt;/h3&gt;

&lt;p&gt;Point it at the root, turn on sitemap discovery, and filter to the section you care about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://docs.your-product.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"crawl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"useSitemap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeUrlPatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/docs/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"excludeUrlPatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/changelog/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wiring it into a RAG pipeline
&lt;/h2&gt;

&lt;p&gt;Here's the whole ingest step in Node — fetch clean Markdown, chunk it, embed it:&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;runResp&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.apify.com/v2/acts/fetchbase~website-to-markdown/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="dl"&gt;"&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;APIFY_TOKEN&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="s2"&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="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://docs.your-product.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;crawl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useSitemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxPages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&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;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runResp&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;// Chunk each page's markdown and embed&lt;/span&gt;
&lt;span class="k"&gt;for &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;page&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pages&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;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chunkMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;for &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&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;embedding&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;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&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;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&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="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;Because the output is clean Markdown, your chunker splits on real headings instead of guessing, and your citations point at real page titles and URLs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For big crawls (hundreds of pages), use the async endpoint (&lt;code&gt;POST /v2/acts/…/runs&lt;/code&gt;)&lt;br&gt;
and poll the run, or run it on a &lt;strong&gt;Schedule&lt;/strong&gt; — the &lt;code&gt;run-sync-*&lt;/code&gt; endpoint used&lt;br&gt;
above is best for a handful of pages, since it caps at ~5 minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Don't forget PDFs and Word docs
&lt;/h2&gt;

&lt;p&gt;Half of most companies' knowledge lives in PDFs and &lt;code&gt;.docx&lt;/code&gt; files, not web pages. The companion &lt;a href="https://apify.com/fetchbase/document-to-markdown" rel="noopener noreferrer"&gt;PDF &amp;amp; DOCX to Markdown&lt;/a&gt; actor handles those with the same output shape, so your ingest code stays uniform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fileUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/whitepaper.pdf"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"markdown"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keep the index fresh
&lt;/h2&gt;

&lt;p&gt;Wrap the run in an Apify &lt;strong&gt;Schedule&lt;/strong&gt; (say, nightly) and re-embed changed pages. Use the &lt;code&gt;sinceDate&lt;/code&gt; pattern on feeds or a content hash to avoid re-embedding everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;It's pay-per-page with no subscription and no startup fee — you're only billed for pages that extract successfully, and free-tier Apify credits cover a generous amount of testing. For a few hundred docs pages that's cents, versus the ongoing cost of running and babysitting your own browser fleet.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you try it, I'd genuinely like feedback — what breaks, what's missing. Open an issue on the actor and it gets fixed fast. There's a whole suite of these (screenshots, SEO/Core-Web-Vitals audits, tech-stack detection, feeds, job APIs) at &lt;a href="https://apify.com/fetchbase" rel="noopener noreferrer"&gt;apify.com/fetchbase&lt;/a&gt; if they're useful to you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AgentKit vs LangChain vs Direct HTTP — picking the right integration for paid agent APIs</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Thu, 14 May 2026 01:24:30 +0000</pubDate>
      <link>https://dev.to/quantoracle/agentkit-vs-langchain-vs-direct-http-picking-the-right-integration-for-paid-agent-apis-2582</link>
      <guid>https://dev.to/quantoracle/agentkit-vs-langchain-vs-direct-http-picking-the-right-integration-for-paid-agent-apis-2582</guid>
      <description>&lt;p&gt;When you're plugging an LLM agent into an external API, you have three reasonable patterns: hand-rolled HTTP, AgentKit's action provider model, or LangChain's tool calling. They all work. They produce identical outputs against the same input.&lt;/p&gt;

&lt;p&gt;So which one should you actually use?&lt;/p&gt;

&lt;p&gt;I built the exact same agent three different ways — answering the same Kelly Criterion question — and the answer to "which one" depends on your stack, your team, and (most underrated) your wallet model. Here's the honest comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test case
&lt;/h2&gt;

&lt;p&gt;Question: &lt;em&gt;"I have a 55% win rate, $150 average win, $100 average loss. What's my Kelly fraction?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Answer: &lt;strong&gt;f* = 17.5%&lt;/strong&gt; (full Kelly), or &lt;strong&gt;8.75%&lt;/strong&gt; (half-Kelly — what most quant funds actually use).&lt;/p&gt;

&lt;p&gt;The math doesn't care which integration computes it. Kelly is a 1956 formula that fits in a tweet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f* = (p · b − q) / b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where p = win probability, q = 1-p, b = avg_win/avg_loss.&lt;/p&gt;

&lt;p&gt;What changes between integrations is everything around the math: how the agent discovers the tool, how it formats inputs, how it handles errors, and — for paid services — how it pays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1 — Direct HTTP
&lt;/h2&gt;

&lt;p&gt;The minimum-viable integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.quantoracle.dev/v1/risk/kelly &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "mode": "discrete",
    "win_rate": 0.55,
    "avg_win": 150,
    "avg_loss": 100
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"full_kelly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"half_kelly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quarter_kelly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0438&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"edge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;32.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payoff_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommended"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HALF_KELLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; zero dependencies. Zero auth setup (this API has a 1K-calls/day free tier). Works from any language. Easy to cache, easy to mock for tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; you handle everything yourself. Schema validation, error retries, rate limit handling, payment if there are paid tiers. The LLM doesn't know about the endpoint — you're putting raw HTTP into your agent loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this is right:&lt;/strong&gt; you're building a deterministic backtest pipeline, a CI script, or any pre-prompted workflow where the agent doesn't need to discover tools at runtime. Or you're building a thin proxy that wraps a paid API for resale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Coinbase AgentKit (TypeScript)
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting if your agent has a wallet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgentKit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CdpEvmWalletProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@coinbase/agentkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;quantoracleActionProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./quantoracle&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;walletProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CdpEvmWalletProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureWithWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKeyId&lt;/span&gt;&lt;span class="p"&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;CDP_API_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKeySecret&lt;/span&gt;&lt;span class="p"&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;CDP_API_KEY_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;networkId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base-mainnet&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentkit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AgentKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;walletProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;actionProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;quantoracleActionProvider&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// LLM picks `calculate_kelly` because the Zod schema's .describe()&lt;/span&gt;
&lt;span class="c1"&gt;// text matches "Kelly fraction" / "win rate" / "payoff"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&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;getLangChainTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentkit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM doesn't see raw HTTP. It sees an action called &lt;code&gt;calculate_kelly&lt;/code&gt; with parameters documented via Zod schemas. AgentKit handles the network call and returns the parsed result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The killer feature:&lt;/strong&gt; the same agent can call paid endpoints (e.g. &lt;code&gt;assess_portfolio_risk&lt;/code&gt; at $0.04 USDC) and AgentKit's wallet handles payment automatically via x402. The LLM doesn't write any payment code. No API key. No signup. No billing system. The wallet just needs USDC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; clean tool model for the LLM. Wallet-native payment (huge if you're building agents that need to pay other agents). Type-safe via TypeScript + Zod. Built-in tracing through LangChain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; TypeScript-only as of today. Adds the @coinbase/agentkit dependency tree. Wallet provisioning is one more thing to set up (though &lt;code&gt;CdpEvmWalletProvider.configureWithWallet&lt;/code&gt; is basically two env vars).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this is right:&lt;/strong&gt; Coinbase-stack agents, x402-native agents, EVM or Solana wallets, autonomous trading bots that need to pay for premium tools, anything where wallet-native auth is the natural model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3 — LangChain (Python)
&lt;/h2&gt;

&lt;p&gt;For when you're in the Python ecosystem and want broad tool access:&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;langchain_quantoracle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QuantOracleToolkit&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_tool_calling_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentExecutor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;

&lt;span class="c1"&gt;# All 73 tools from this API (63 calculators + 10 composites)
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Or filter
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&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;risk&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;stats&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_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&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-4o&lt;/span&gt;&lt;span class="sh"&gt;"&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_messages&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;system&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;Use the provided tools for any math.&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;human&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;{input}&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;placeholder&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;{agent_scratchpad}&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="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_tool_calling_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&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;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&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="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&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;I have 55% win rate, $150 avg win, $100 avg loss — Kelly?&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;LangChain's toolkit pattern is the established way to give an agent a curated set of tools. Pydantic schemas describe each tool to the LLM. The toolkit handles HTTP, retries, and error wrapping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Python-native (huge for the quant + ML crowd). Composes with LangGraph for stateful workflows. Works with any LangChain-compatible LLM (OpenAI, Anthropic, local Llama, etc.). Exposes all 73 endpoints by default — useful when you don't want to pre-curate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; no native wallet integration — if you want paid endpoints, you handle x402 separately. The breadth (73 tools) can confuse smaller LLMs that aren't great at narrowing from many options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this is right:&lt;/strong&gt; Python-native pipelines, multi-tool agents (combining this API with web search, file ops, vector DBs), LangGraph workflows, anywhere you want broad tool access without curating a subset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision rule
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scripts / backtests / CI&lt;/td&gt;
&lt;td&gt;Direct HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building on Coinbase / x402 / CDP wallets&lt;/td&gt;
&lt;td&gt;AgentKit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, LangChain, or LangGraph workflows&lt;/td&gt;
&lt;td&gt;LangChain Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI custom GPT&lt;/td&gt;
&lt;td&gt;GPT Actions (a 4th path I didn't cover here)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP client (Claude Desktop, Cursor)&lt;/td&gt;
&lt;td&gt;MCP server (a 5th path)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solana ecosystem, want sub-second x402 settlement&lt;/td&gt;
&lt;td&gt;AgentKit with &lt;code&gt;SolanaKeypairWalletProvider&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All paths hit the same underlying API. The math is byte-identical across integrations for the same inputs. &lt;strong&gt;Pick by ergonomics and wallet model, not by capability.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two patterns from production that combine these
&lt;/h2&gt;

&lt;p&gt;These aren't mutually exclusive. In real systems you'll often use more than one:&lt;/p&gt;

&lt;h3&gt;
  
  
  Backtest in Python, deploy in TypeScript
&lt;/h3&gt;

&lt;p&gt;You develop your strategy in a Jupyter notebook with &lt;code&gt;langchain-quantoracle&lt;/code&gt; (all 73 tools available, easy to explore). When you find an edge worth productionizing, you re-implement the agent in TypeScript with AgentKit (curated 5-tool subset, wallet-native payments). Same API answers both. Your research notebook and your production agent agree because they're hitting the same engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free tier for research, paid for production
&lt;/h3&gt;

&lt;p&gt;The free tier (1K calls/IP/day) covers backtests across thousands of historical days. Once you've validated the strategy and want it running 24/7 as a paid signal service, you switch to the AgentKit + x402 pattern so the wallet pays per call. The economics scale linearly with usage instead of forcing a subscription decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this isn't about
&lt;/h2&gt;

&lt;p&gt;This isn't about which framework is "better." All three are excellent. AgentKit's wallet integration is unique value if you need x402; LangChain's tool ecosystem is unique value if you're orchestrating many tools; raw HTTP is unique value when you want maximum control.&lt;/p&gt;

&lt;p&gt;It's also not about quant finance specifically. The same decision rule applies to any external API your agent might use — weather, web search, on-chain data, image generation. The frameworks differ in how they help your agent &lt;em&gt;discover and pay for&lt;/em&gt; tools, not in what those tools can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  My pick — and why
&lt;/h2&gt;

&lt;p&gt;If I had to start from zero today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Python team, no wallet&lt;/strong&gt; → LangChain. Most flexible. Most existing tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript team with crypto-native agents&lt;/strong&gt; → AgentKit. The wallet-paid x402 flow is genuinely magical when you have it working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed team or no strong preference&lt;/strong&gt; → start with direct HTTP for the first integration, then add a framework when you have a second one. The HTTP version is your reference implementation either way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The wrong move is "framework first, problem second." All three integrations work because the underlying API is well-designed. The framework is a thin layer on top. Pick the thin layer that matches the rest of your stack.&lt;/p&gt;




&lt;p&gt;The QuantOracle API (the one I used for these examples) is at &lt;a href="https://quantoracle.dev" rel="noopener noreferrer"&gt;quantoracle.dev&lt;/a&gt; — free tier of 1,000 calls per IP per day, no signup. All three integration paths are documented at &lt;a href="https://github.com/QuantOracledev/quantoracle/tree/main/integrations" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The AgentKit action provider files are &lt;a href="https://github.com/QuantOracledev/quantoracle/tree/main/integrations/agentkit" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The Python LangChain toolkit is &lt;code&gt;pip install langchain-quantoracle&lt;/code&gt;. The OpenAPI spec for the direct HTTP path is at &lt;a href="https://api.quantoracle.dev/openapi.json" rel="noopener noreferrer"&gt;api.quantoracle.dev/openapi.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What integration pattern are you using for your agent? Always curious how others land on the trade-off.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>agentkit</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Give Your LangChain Agent Reliable Quant Finance Math (in 10 minutes)</title>
      <dc:creator>quantoracledev</dc:creator>
      <pubDate>Mon, 20 Apr 2026 14:12:18 +0000</pubDate>
      <link>https://dev.to/quantoracle/how-to-give-your-langchain-agent-reliable-quant-finance-math-in-10-minutes-5fki</link>
      <guid>https://dev.to/quantoracle/how-to-give-your-langchain-agent-reliable-quant-finance-math-in-10-minutes-5fki</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Large language models are great at reasoning about finance and noticeably unreliable at &lt;strong&gt;doing&lt;/strong&gt; finance math. Ask an LLM to price an option and the price it returns can drift across runs. Ask for the Greeks and the higher-order ones (vanna, charm, speed) frequently come back wrong or inconsistent.&lt;/p&gt;

&lt;p&gt;This is a known failure mode — and it's not specific to any particular model. The fix is standard engineering: call a dedicated calculator. This post walks through how to give any LangChain agent access to &lt;strong&gt;73 deterministic quantitative finance endpoints&lt;/strong&gt; (options pricing, Greeks, risk metrics, portfolio optimization, Monte Carlo, backtests, etc.) via one line of code. First 1,000 calls/day are free — no signup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What you hope happens
&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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&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 a European call: spot=100, strike=105, 6 months, 20% vol, 5% rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# "$4.58" ✓
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What actually happens in production
# - Price may land close, but drifts run-to-run
# - Delta, gamma, vega often reasonable; vanna, charm, speed, color frequently wrong
# - Numerics that depend on chained reasoning (IV solver, barrier options, path-dependent) degrade further
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The math is deterministic. The model isn't. For anything agent-driven — backtests, risk management, paper trading, analysis pipelines — you need &lt;strong&gt;same-input-same-output&lt;/strong&gt; calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: QuantOracle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://quantoracle.dev" rel="noopener noreferrer"&gt;QuantOracle&lt;/a&gt; is a REST API with 63 pure quant calculators plus 10 "composite" workflows (strategy backtests, portfolio rebalance plans, options strategy optimizers, hedging recommendations, full risk tearsheets). All citation-verified against Hull, Wilmott, Bailey &amp;amp; Lopez de Prado.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000 free calls/IP/day, no API key&lt;/li&gt;
&lt;li&gt;Paid tier uses &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402 micropayments&lt;/a&gt; in USDC on Base or Solana ($0.002–$0.10/call)&lt;/li&gt;
&lt;li&gt;Deterministic: same inputs always produce the same outputs&lt;/li&gt;
&lt;li&gt;MCP server, LangChain toolkit, OpenAI GPT, and plain REST all supported&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hook it into LangChain in one line
&lt;/h2&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;langchain-quantoracle langchain-openai langchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;langchain_quantoracle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QuantOracleToolkit&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create_tool_calling_agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;

&lt;span class="c1"&gt;# Load every QuantOracle tool — all 73 endpoints become LangChain tools
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&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-4o&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_messages&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;system&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;You are a quant analyst. Use QuantOracle tools for all financial math — never compute in-context.&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;human&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;{input}&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;placeholder&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;{agent_scratchpad}&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="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_tool_calling_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&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;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&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="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your agent now has Black-Scholes, 22 portfolio risk metrics, Kelly sizing, 13 technical indicators, Monte Carlo, strategy backtests, and 60+ others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Price an option with Greeks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&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;Price a European call with spot=100, strike=105, 6 months to expiry, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20% annualized vol, 5% risk-free. I want the price, delta, gamma, vega, and theta.&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;The agent picks the right tool (&lt;code&gt;options_price&lt;/code&gt;), calls it, and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$4.58&lt;/span&gt;
&lt;span class="na"&gt;Greeks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Delta&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.4612&lt;/span&gt;
  &lt;span class="na"&gt;Gamma&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0281&lt;/span&gt;
  &lt;span class="na"&gt;Theta&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-0.0211 (daily)&lt;/span&gt;
  &lt;span class="na"&gt;Vega&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;0.2808&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the &lt;em&gt;exact&lt;/em&gt; Black-Scholes values. Reproducible across runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: Full risk analysis from a returns series
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&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;Here are daily returns: [0.01, -0.02, 0.03, 0.005, -0.01, 0.02, -0.015, 0.025, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.01, -0.005, 0.015]. Give me a complete risk breakdown.&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;The agent calls the &lt;code&gt;risk_full-analysis&lt;/code&gt; composite (one API call that replaces 7 individual ones) and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Risk Tearsheet (11 periods)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Sharpe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.83&lt;/span&gt;
  &lt;span class="na"&gt;Sortino&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4.59&lt;/span&gt;
  &lt;span class="na"&gt;VaR (95%)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-0.03&lt;/span&gt;
  &lt;span class="na"&gt;Max Drawdown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-0.03&lt;/span&gt;
  &lt;span class="na"&gt;Kelly leverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.65x&lt;/span&gt;
  &lt;span class="na"&gt;Hurst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.50 (neutral — random walk)&lt;/span&gt;
  &lt;span class="na"&gt;CAGR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;122.98%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same inputs always produce the same output. No drift, no hallucinations, no flaky Sharpe calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: Backtest a strategy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&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;Backtest a 20/50 SMA crossover on this price series: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[100, 101, 102, ...]. Initial capital $10000, 5 bps commission.&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;The agent calls &lt;code&gt;backtest_strategy&lt;/code&gt; (a composite endpoint that replaces ~10 individual calls) and gets back: Sharpe ratio, Calmar, max drawdown, win rate, list of trades, equity curve, and a buy-and-hold benchmark comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use composites vs individual calculators vs batch
&lt;/h2&gt;

&lt;p&gt;The toolkit exposes three tiers of tools, each for a different situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Individual calculators&lt;/strong&gt; (&lt;code&gt;options_price&lt;/code&gt;, &lt;code&gt;risk_portfolio&lt;/code&gt;, &lt;code&gt;stats_hurst-exponent&lt;/code&gt;, ...) — fine-grained control, one concept per call. Free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite workflows&lt;/strong&gt; (&lt;code&gt;backtest_strategy&lt;/code&gt;, &lt;code&gt;portfolio_rebalance-plan&lt;/code&gt;, &lt;code&gt;options_strategy-optimizer&lt;/code&gt;, &lt;code&gt;hedging_recommend&lt;/code&gt;, &lt;code&gt;risk_full-analysis&lt;/code&gt;, ...) — bundle 5–15 calculator calls into one round trip with a purpose-built output. Paid-only ($0.015–$0.10 each), but dramatically cheaper and faster than hand-chaining the pieces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch endpoint&lt;/strong&gt; (&lt;code&gt;POST /v1/batch&lt;/code&gt;) — run up to 100 arbitrary calculator calls in a single HTTP request. Ideal for parameter sweeps, walk-forward backtests, or any workload where latency dominates cost. Price is the sum of the individual prices — no markup. First batch call per IP is free; subsequent batches are paid via x402.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One calculation → individual calculator&lt;/li&gt;
&lt;li&gt;Named workflow (risk analysis, backtest, hedge selection) → composite&lt;/li&gt;
&lt;li&gt;Many small calculations at once → batch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single backtest run that would be 200 HTTP calls one at a time becomes 2 batch calls. If your agent iterates, batch usually wins on both latency and cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filter by category to keep tool lists small
&lt;/h2&gt;

&lt;p&gt;A common LangChain pitfall: 73 tools in the prompt confuses smaller models. Filter by category:&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;# Options-only agent
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&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;options&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;derivatives&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_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Risk/portfolio-only agent
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&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;risk&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;portfolio&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;stats&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_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Crypto-focused agent
&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuantOracleToolkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&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;crypto&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;simulate&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_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available categories: &lt;code&gt;options&lt;/code&gt;, &lt;code&gt;derivatives&lt;/code&gt;, &lt;code&gt;risk&lt;/code&gt;, &lt;code&gt;indicators&lt;/code&gt;, &lt;code&gt;simulate&lt;/code&gt;, &lt;code&gt;portfolio&lt;/code&gt;, &lt;code&gt;fixed-income&lt;/code&gt;, &lt;code&gt;fi&lt;/code&gt;, &lt;code&gt;stats&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt;, &lt;code&gt;fx&lt;/code&gt;, &lt;code&gt;macro&lt;/code&gt;, &lt;code&gt;tvm&lt;/code&gt;, &lt;code&gt;trade&lt;/code&gt;, &lt;code&gt;pairs&lt;/code&gt;, &lt;code&gt;backtest&lt;/code&gt;, &lt;code&gt;hedging&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Past the free tier
&lt;/h2&gt;

&lt;p&gt;After 1,000 calls/day (per IP), the API returns HTTP 402 with an x402 payment requirements header. If you're using an x402-capable HTTP client (e.g. &lt;a href="https://agentcash.dev" rel="noopener noreferrer"&gt;AgentCash&lt;/a&gt;, Coinbase AgentKit), payments are automatic — USDC on Base or Solana, $0.002–$0.10 per call. Otherwise the toolkit raises an exception and you can add a payment layer yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for agentic systems
&lt;/h2&gt;

&lt;p&gt;When an agent makes 50 tool calls during a backtest, &lt;strong&gt;every calculation has to be right&lt;/strong&gt;. An LLM that's 85% accurate on Black-Scholes doesn't produce a backtest — it produces noise. Moving all math to a deterministic calculator means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reproducible results (your next run produces the same Sharpe)&lt;/li&gt;
&lt;li&gt;Cacheable (you can memoize by input hash)&lt;/li&gt;
&lt;li&gt;Auditable (you can replay any step)&lt;/li&gt;
&lt;li&gt;Fast (sub-millisecond per calculation on the server)&lt;/li&gt;
&lt;li&gt;Cheap (orders of magnitude less than equivalent LLM tokens)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern — &lt;strong&gt;LLM for reasoning + deterministic APIs for compute&lt;/strong&gt; — is the one thing that actually works for production agent systems. Pick it up now and you don't have to rebuild once your agent starts taking real actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;API docs: &lt;a href="https://api.quantoracle.dev/docs" rel="noopener noreferrer"&gt;https://api.quantoracle.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tool discovery: &lt;a href="https://api.quantoracle.dev/tools" rel="noopener noreferrer"&gt;https://api.quantoracle.dev/tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;x402 discovery (Base + Solana): &lt;a href="https://api.quantoracle.dev/.well-known/x402" rel="noopener noreferrer"&gt;https://api.quantoracle.dev/.well-known/x402&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/QuantOracledev/quantoracle" rel="noopener noreferrer"&gt;https://github.com/QuantOracledev/quantoracle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pypi: &lt;a href="https://pypi.org/project/langchain-quantoracle/" rel="noopener noreferrer"&gt;&lt;code&gt;langchain-quantoracle&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP server: &lt;code&gt;npx quantoracle-mcp&lt;/code&gt; (&lt;a href="https://www.npmjs.com/package/quantoracle-mcp" rel="noopener noreferrer"&gt;npm&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;OpenAI GPT: &lt;a href="https://chatgpt.com/g/g-69d9c28bddb481918e674e2f9d9f3e97-quantoracle" rel="noopener noreferrer"&gt;https://chatgpt.com/g/g-69d9c28bddb481918e674e2f9d9f3e97-quantoracle&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free tier is generous, no signup required, MIT licensed. If you're building an agent that touches financial math — options pricing, portfolio analytics, risk, backtests — try it before rolling your own.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>python</category>
      <category>ai</category>
      <category>finance</category>
    </item>
  </channel>
</rss>
