<?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: Kevin Meneses González</title>
    <description>The latest articles on DEV Community by Kevin Meneses González (@kevin_menesesgonzlez).</description>
    <link>https://dev.to/kevin_menesesgonzlez</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%2F2412421%2F23a50b66-3e3e-4d13-8809-9bc0b03c0f40.png</url>
      <title>DEV Community: Kevin Meneses González</title>
      <link>https://dev.to/kevin_menesesgonzlez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevin_menesesgonzlez"/>
    <language>en</language>
    <item>
      <title>5 Best Free AI Courses in 2026 (With Certificates)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:22:26 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/5-best-free-ai-courses-in-2026-with-certificates-igp</link>
      <guid>https://dev.to/kevin_menesesgonzlez/5-best-free-ai-courses-in-2026-with-certificates-igp</guid>
      <description>&lt;p&gt;Most people assume learning AI seriously means paying for a bootcamp or a $200 certification program.&lt;/p&gt;

&lt;p&gt;That's not true anymore.&lt;/p&gt;

&lt;p&gt;Open any search for "AI courses" and you'll land on the same problem every time: dozens of tabs, half of them paywalled, half of them outdated, and no clear sense of which one actually matches your level. You start an advanced course with zero foundations. You finish a beginner course you'd already outgrown. You bookmark five links and never open them again.&lt;/p&gt;

&lt;p&gt;That's not a knowledge problem.&lt;/p&gt;

&lt;p&gt;It's a filtering problem.&lt;/p&gt;

&lt;p&gt;Most "best AI courses" roundups make it worse. They repeat the same generic Coursera or edX links, list them in no particular order, and never tell you whether a course assumes you can code or whether it teaches anything you couldn't already find in the docs for free.&lt;/p&gt;

&lt;p&gt;The real problem isn't finding a course. It's finding the right one for where you actually are, in the right order.&lt;/p&gt;

&lt;p&gt;That's what this list solves. Five free courses, all with certificates, ordered from complete beginner to advanced developer.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting from zero and just want to understand what AI actually is,&lt;/li&gt;
&lt;li&gt;a developer wanting to build with LLMs and tools like Claude,&lt;/li&gt;
&lt;li&gt;or already technical and looking to go deeper into Transformers and NLP,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;there's a course below built for exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Anthropic Academy - Claude 101
&lt;/h2&gt;

&lt;p&gt;Anthropic is the company behind Claude, one of the most capable AI models available today, with a rapidly growing set of tools around it.&lt;/p&gt;

&lt;p&gt;Claude 101 is the foundational course inside Anthropic Academy. It covers what Claude actually is, how to start a conversation, and how to write better prompts to get more reliable results.&lt;/p&gt;

&lt;p&gt;It also goes further than most beginner courses by introducing &lt;strong&gt;Projects&lt;/strong&gt;, &lt;strong&gt;Skills&lt;/strong&gt;, and &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;, which is how Claude connects to external tools and data sources to extend what it can do.&lt;/p&gt;

&lt;p&gt;The course includes a certificate you can add to LinkedIn or your resume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://anthropic.skilljar.com/claude-101" rel="noopener noreferrer"&gt;Start Claude 101 on Anthropic Academy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covers both fundamentals and practical prompt optimization&lt;/li&gt;
&lt;li&gt;Introduces MCP, a concept most beginner courses skip entirely&lt;/li&gt;
&lt;li&gt;Certificate included at no cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focused specifically on the Claude ecosystem, not model-agnostic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone starting from zero who wants a foundation built around a real, production-grade AI tool instead of abstract theory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anthropic Academy also offers a follow-up course on &lt;strong&gt;Claude Code&lt;/strong&gt;, aimed at developers who want to use AI for programming tasks. Worth checking once you finish Claude 101.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. AI For Everyone - Andrew Ng
&lt;/h2&gt;

&lt;p&gt;If there's one instructor almost universally recommended for AI fundamentals, it's Andrew Ng.&lt;/p&gt;

&lt;p&gt;AI For Everyone is a beginner-level course, roughly 7 hours long, that covers what AI actually is, the terminology you'll keep running into, and the practical differences between AI models. It also walks through small projects so the concepts don't stay purely theoretical.&lt;/p&gt;

&lt;p&gt;Like the first course on this list, it's free and includes a certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.coursera.org/learn/ai-for-everyone" rel="noopener noreferrer"&gt;Start AI For Everyone on Coursera&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taught by one of the most recognized instructors in the field&lt;/li&gt;
&lt;li&gt;Clear, jargon-light explanations of AI terminology&lt;/li&gt;
&lt;li&gt;Includes practical mini-projects, not just lectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intentionally high-level. Not for anyone wanting technical depth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; complete beginners who want a solid conceptual map before touching any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Hugging Face - NLP Course
&lt;/h2&gt;

&lt;p&gt;Hugging Face functions as the GitHub of AI models. If you're building anything involving language models, you'll end up there eventually.&lt;/p&gt;

&lt;p&gt;This course is more technical and assumes you already have some AI foundation. It covers &lt;strong&gt;Transformers&lt;/strong&gt;, &lt;strong&gt;NLP fundamentals&lt;/strong&gt;, &lt;strong&gt;tokenization&lt;/strong&gt;, and how to fine-tune and optimize models so they work well with your own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://huggingface.co/learn/llm-course/chapter1/1" rel="noopener noreferrer"&gt;Start the NLP Course on Hugging Face&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep, hands-on coverage of Transformers and tokenization&lt;/li&gt;
&lt;li&gt;Directly applicable to real model fine-tuning work&lt;/li&gt;
&lt;li&gt;Free, with strong community support around it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not beginner-friendly. Requires prior AI/ML context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers ready to move from "using AI tools" to actually building and customizing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Google - Generative AI Learning Path
&lt;/h2&gt;

&lt;p&gt;Google's ecosystem includes Gemini, one of the leading models alongside Claude and ChatGPT.&lt;/p&gt;

&lt;p&gt;This learning path covers the fundamentals of generative AI, how Google's infrastructure supports these models, and how to build applications ranging from image generation to voice. It also explains what an LLM actually is and how to make use of the resources Google makes available to developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.skills.google/paths/118" rel="noopener noreferrer"&gt;Start the Generative AI Learning Path on Google Skills&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covers multiple modalities: text, image, and voice generation&lt;/li&gt;
&lt;li&gt;Backed by Google's own infrastructure and documentation&lt;/li&gt;
&lt;li&gt;Multiple course tracks depending on your interest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broader scope means less depth per topic compared to Hugging Face's course&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who want to understand the infrastructure side of generative AI, not just the model layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. ChatGPT - Building Agents and Workflows
&lt;/h2&gt;

&lt;p&gt;The last course on this list focuses on something increasingly relevant in 2026: &lt;strong&gt;agents and workflow automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This free course, built around the ChatGPT ecosystem, takes about 90 minutes and covers how to build agents and automated workflows to handle repetitive tasks and improve your daily productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://academy.openai.com/pages/courses" rel="noopener noreferrer"&gt;Start Agents and Workflows on OpenAI Academy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short and focused. Around 90 minutes total&lt;/li&gt;
&lt;li&gt;Directly applicable to automating real tasks&lt;/li&gt;
&lt;li&gt;Free, no certificate paywall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shorter format means less depth than the other courses on this list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone who already understands AI basics and wants to start automating tasks with agents right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose Your Path
&lt;/h2&gt;

&lt;p&gt;You don't need all five. Pick based on where you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starting from zero?&lt;/strong&gt; Anthropic Academy's Claude 101, then Andrew Ng's course.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already technical, want to build models?&lt;/strong&gt; Hugging Face's NLP course.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interested in the infrastructure behind generative AI?&lt;/strong&gt; Google's learning path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want to automate tasks right now?&lt;/strong&gt; The ChatGPT agents course.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want a personalized AI roadmap instead of a generic course list?&lt;/strong&gt;&lt;br&gt;
I offer 1:1 consulting sessions based on your specific goals and current AI setup.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Book a consultation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;The best AI courses in 2026 are free and include certificates, no need to pay for a bootcamp to start.&lt;/li&gt;
&lt;li&gt;Level matters more than course popularity. Match the course to where you actually are, not where you want to be.&lt;/li&gt;
&lt;li&gt;MCP, agents, and workflow automation are now core topics, not optional extras, for anyone learning AI in 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Are there really free AI courses with certificates in 2026?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Anthropic Academy, Andrew Ng's AI For Everyone, and Google's Generative AI Learning Path all offer free certificates you can add to LinkedIn or your resume.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the best AI course for complete beginners?&lt;/strong&gt;&lt;br&gt;
✅ Start with Anthropic Academy's Claude 101 or Andrew Ng's AI For Everyone. Both are designed for zero prior knowledge and take just a few hours to complete.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need to know how to code to take these courses?&lt;/strong&gt;&lt;br&gt;
✅ No. Courses 1, 2, 4, and 5 on this list require no coding background. Only Hugging Face's NLP course assumes prior technical experience.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are you a software or API company that needs technical content, not marketing fluff?&lt;/strong&gt;&lt;br&gt;
I write SEO-optimized articles, tutorials, and video content that actually explain your product to developers.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Get in touch on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want more AI and fintech content like this?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Explore more articles&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>How I Built a Portfolio Risk &amp; Return Tracker with EODHD</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:33:40 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-i-built-a-portfolio-risk-return-tracker-with-eodhd-5gkl</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-i-built-a-portfolio-risk-return-tracker-with-eodhd-5gkl</guid>
      <description>&lt;p&gt;Most retail investors track their portfolio in a spreadsheet.&lt;/p&gt;

&lt;p&gt;Some upgrade to a free app that shows total value and daily change. That's it.&lt;/p&gt;

&lt;p&gt;Neither tells you the numbers that actually matter: your annualized volatility, your Sharpe ratio, your beta against the market, your maximum drawdown, how correlated your positions really are with each other.&lt;/p&gt;

&lt;p&gt;Institutional investors have had this for decades. Retail investors get a pie chart.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;managing your own portfolio across multiple brokers,&lt;/li&gt;
&lt;li&gt;building a fintech side project,&lt;/li&gt;
&lt;li&gt;or just tired of guessing how risky your holdings actually are,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spreadsheet problem
&lt;/h2&gt;

&lt;p&gt;Excel is where most portfolios go to die.&lt;/p&gt;

&lt;p&gt;You start with a clean sheet. One column per position, one row per transaction. It works — for about three months.&lt;/p&gt;

&lt;p&gt;Then you add a new broker account. Then you start tracking dividends separately. Then a formula breaks because you inserted a row in the wrong place, and you don't notice until your total is off by 4%.&lt;/p&gt;

&lt;p&gt;Spreadsheets don't calculate volatility, beta, or drawdown by default. You'd have to build those formulas yourself, pull historical prices manually, and keep them updated — forever, by hand.&lt;/p&gt;

&lt;p&gt;Most people don't. So they end up managing a portfolio without knowing its actual risk profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The paid-tool problem
&lt;/h2&gt;

&lt;p&gt;The other option is a paid portfolio tracker or a Bloomberg-style terminal.&lt;/p&gt;

&lt;p&gt;These solve the calculation problem. They don't solve the trust problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your position sizes, tickers, and entry prices sit on a third-party server.&lt;/li&gt;
&lt;li&gt;You pay a monthly fee for metrics that are, mathematically, not complicated.&lt;/li&gt;
&lt;li&gt;Many of these tools are optimized to upsell you into their brokerage or premium tier, not to give you a neutral view of your risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sharpe ratio, beta, drawdown, correlation — these aren't proprietary black-box models. They're well-defined formulas. Paying $30/month to have someone else run them on your data, on their servers, is a trade-off a lot of people make without questioning it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real problem isn't a lack of financial data. It's a lack of a tool that computes real risk metrics, locally, without asking you to hand your portfolio to a third party.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vault: a portfolio tracker that runs in your browser
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/Kevinelectronics/portafoliotracker" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;, an open source portfolio tracker with React 19 and Vite that uses the &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD API&lt;/a&gt; to pull real market data and calculate six metrics that any serious investor should be tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Returns&lt;/strong&gt; — portfolio performance vs. S&amp;amp;P 500, indexed and per-position&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diversification&lt;/strong&gt; — position weights and concentration (Herfindahl-Hirschman Index)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk&lt;/strong&gt; — annualized volatility, Sharpe ratio, beta vs. market, 95% daily VaR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawdown&lt;/strong&gt; — underwater curve and maximum historical drawdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlations&lt;/strong&gt; — Pearson correlation matrix between your holdings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sector exposure&lt;/strong&gt; — GICS sector breakdown from EODHD fundamentals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No backend. No account. No server storing your positions.&lt;/p&gt;

&lt;p&gt;Everything runs in the browser, and your portfolio is saved in &lt;code&gt;localStorage&lt;/code&gt; — on your machine, not on mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quick note on EODHD
&lt;/h3&gt;

&lt;p&gt;I've used EODHD across several projects before this one, mainly because of three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One API key covers historical prices, fundamentals, and search — no juggling three different providers&lt;/li&gt;
&lt;li&gt;The free tier is generous enough to actually build and test a real project on it&lt;/li&gt;
&lt;li&gt;Response format is consistent across endpoints, which cuts down on glue code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to follow along or fork Vault, you'll need a key — &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;get one free here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the calculations actually work
&lt;/h2&gt;

&lt;p&gt;All the math lives in a single file: &lt;a href="https://github.com/Kevinelectronics/portafoliotracker/blob/main/src/utils/finance.js" rel="noopener noreferrer"&gt;&lt;code&gt;src/utils/finance.js&lt;/code&gt;&lt;/a&gt;. No external statistics libraries — every formula is implemented from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time-weighted returns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each day is weighted by the previous day's closing value. This matters more than it sounds: without it, adding a new position mid-month distorts your daily portfolio return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volatility and Sharpe ratio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard deviation of daily returns, annualized with &lt;code&gt;√252&lt;/code&gt;, then converted to a Sharpe ratio against a configurable risk-free rate.&lt;/p&gt;

&lt;p&gt;Here's the core of it, simplified to Python for clarity:&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;annualized_volatility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sharpe_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;risk_free_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;excess_returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;risk_free_rate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excess_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Beta&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Covariance of portfolio returns against the S&amp;amp;P 500 (&lt;code&gt;GSPC.INDX&lt;/code&gt; on EODHD), divided by the benchmark's variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drawdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Percentage drop from the running peak of a growth-indexed curve (base 100). This is the number that tells you what it would have actually felt like to hold this portfolio through its worst stretch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correlation matrix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pairwise Pearson coefficient between assets, aligned by date. This is the metric most retail portfolios get wrong without knowing it — five stocks can feel diversified and still move together 90% of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concentration (HHI)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Herfindahl-Hirschman Index applied to position weights. One number that tells you how concentrated you actually are, beyond "I have 12 positions so I'm diversified."&lt;/p&gt;

&lt;p&gt;From here, the same building blocks extend into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated risk alerts&lt;/li&gt;
&lt;li&gt;portfolio rebalancing scripts&lt;/li&gt;
&lt;li&gt;backtesting engines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;You add a position, EODHD's Search + EOD endpoints autocomplete the ticker and the closing price on your purchase date.&lt;/p&gt;

&lt;p&gt;From there, Vault renders your returns curve against the S&amp;amp;P 500, your correlation heatmap, and your sector exposure pulled straight from fundamentals data.&lt;/p&gt;

&lt;p&gt;No manual formula-building. No re-entering prices when a position updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Portfolio risk metrics (Sharpe, beta, drawdown, correlation) aren't proprietary — they're standard formulas most tools just gatekeep behind a paywall&lt;/li&gt;
&lt;li&gt;Running them client-side means your positions never leave your browser&lt;/li&gt;
&lt;li&gt;EODHD's Search, EOD, and Fundamentals endpoints cover everything needed to build this without stitching together multiple data providers&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kevinelectronics/portafoliotracker.git
&lt;span class="nb"&gt;cd &lt;/span&gt;portafoliotracker
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your EODHD key to &lt;code&gt;.env&lt;/code&gt;, run &lt;code&gt;npm run dev&lt;/code&gt;, and add your first position.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get your free EODHD API key&lt;/strong&gt;&lt;br&gt;
Vault runs entirely on EODHD's Search, EOD, and Fundamentals endpoints — no backend, no paywall on the metrics.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your EODHD API key&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content — not marketing fluff — feel free to connect with me on LinkedIn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need technical content like this for your product?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>rag</category>
      <category>llm</category>
      <category>pdf</category>
    </item>
    <item>
      <title>How to Build a RAG Pipeline from PDFs Using Python</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Fri, 24 Jul 2026 14:07:41 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-to-build-a-rag-pipeline-from-pdfs-using-python-1903</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-to-build-a-rag-pipeline-from-pdfs-using-python-1903</guid>
      <description>&lt;h1&gt;
  
  
  How to Build a RAG Pipeline from PDFs Using Python
&lt;/h1&gt;

&lt;p&gt;Most RAG pipelines don't fail at retrieval. They don't fail at the model either.&lt;/p&gt;

&lt;p&gt;They fail at ingestion — the moment a messy PDF gets dumped straight into a text splitter and comes out the other side as noise.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Feeding raw PDF text into a chunker is where most RAG pipelines quietly break: tables split mid-row, headings disappear, chunks lose their context.&lt;/li&gt;
&lt;li&gt;The fix isn't a better chunking algorithm. It's converting the PDF into clean, structured Markdown &lt;em&gt;before&lt;/em&gt; you chunk anything.&lt;/li&gt;
&lt;li&gt;Nutrient's Python SDK (&lt;code&gt;nutrient_dws&lt;/code&gt;) turns a PDF into Markdown in one call, preserving headings, lists, and tables — so your chunker can split on structure instead of guessing at character counts.&lt;/li&gt;
&lt;li&gt;Below: a full Python walkthrough — PDF → Markdown → heading-aware chunks → ready for embeddings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually breaks in a naive RAG pipeline
&lt;/h2&gt;

&lt;p&gt;The typical first version of a RAG pipeline looks like this: extract raw text with a generic PDF library, split it every N characters, embed each chunk, done.&lt;/p&gt;

&lt;p&gt;It works on a clean text file. It falls apart on a real PDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A table gets flattened into a single line of numbers with no column boundaries — the chunk that gets retrieved is unreadable&lt;/li&gt;
&lt;li&gt;A heading ends up alone at the bottom of one chunk, with its content starting the next one — retrieval finds the content but the model never sees what section it belongs to&lt;/li&gt;
&lt;li&gt;Multi-column layouts get merged out of order, so a chunk contains half of one paragraph and half of an unrelated one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this shows up in a demo with three test PDFs. It shows up in production, on document #47, and it looks like a "retrieval quality" problem when it's actually an ingestion problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: structure before chunking
&lt;/h2&gt;

&lt;p&gt;A RAG pipeline that holds up in production treats ingestion as its own stage — not a one-liner before the real work starts.&lt;/p&gt;

&lt;p&gt;The order that actually works is: &lt;strong&gt;extract structure, then chunk on structure, then embed.&lt;/strong&gt; Chunk boundaries should follow headings and paragraph breaks the document already has, not an arbitrary character count that cuts through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF
 │
 ▼
Python SDK (nutrient_dws) — mode="text", output_format="markdown"
 │
 ▼
Clean Markdown (headings, lists, tables preserved)
 │
 ▼
Heading-aware chunker (splits on headings, then paragraph breaks)
 │
 ▼
Embedding model
 │
 ▼
Vector store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chunker is the piece most tutorials skip past. It's also the piece that determines whether your retrieved chunks make sense to the model or arrive as disconnected fragments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: PDF to Markdown with the Python SDK
&lt;/h2&gt;

&lt;p&gt;Nutrient's official Python client, &lt;code&gt;nutrient_dws&lt;/code&gt;, wraps document parsing in a simple async call. For born-digital PDFs, &lt;code&gt;mode="text"&lt;/code&gt; is the fastest and cheapest path — 1 credit per page:&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nutrient_dws&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NutrientClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NutrientClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_processor_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;extract_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_extract_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pdf_to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&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;markdown&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;That's the entire extraction step. The output preserves headings, lists, and tables as real Markdown syntax — not a flat text dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Employee Handbook&lt;/span&gt;

&lt;span class="gu"&gt;## Time Off Policy&lt;/span&gt;

Full-time employees accrue 1.5 days of PTO per month...

| Tenure | Annual PTO |
|--------|-----------|
| 0–2 years | 15 days |
| 3–5 years | 20 days |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your source documents are scanned rather than born-digital, switch to &lt;code&gt;mode="structure"&lt;/code&gt; so OCR runs before the Markdown conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: chunking on structure, not character count
&lt;/h2&gt;

&lt;p&gt;Once you have Markdown, split on headings first — so no chunk ever straddles a section boundary — then fall back to paragraph breaks for sections that are still too long:&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;re&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chunk_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_chars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Split on Markdown headings so a chunk never crosses a section boundary
&lt;/span&gt;    &lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?=^#{1,3} )&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MULTILINE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_chars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="c1"&gt;# Long section: split further on paragraph breaks
&lt;/span&gt;        &lt;span class="n"&gt;paragraphs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paragraphs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_chars&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="nb"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
            &lt;span class="nb"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting both pieces together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;markdown&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;pdf_to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company_handbook.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chunk_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generated &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chunks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Next: embed each chunk and upsert into your vector store of choice
&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every chunk that comes out of this now maps to a real section of the document — a heading and the content underneath it, or a table with its rows intact — instead of an arbitrary character window.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need more than Markdown
&lt;/h2&gt;

&lt;p&gt;Markdown chunking covers most RAG ingestion. But some documents need the model to answer questions that depend on exact table structure — "what was the Q3 number in the third row" — where flattening a table into Markdown text still loses precision.&lt;/p&gt;

&lt;p&gt;For those cases, request spatial JSON output instead of Markdown from the same &lt;code&gt;parse()&lt;/code&gt; call, or use &lt;code&gt;mode="understand"&lt;/code&gt; for documents with complex layouts, key-value regions, or handwriting. Spatial output gives you row/column-indexed table cells with bounding boxes and confidence scores — useful when you need to validate or cite the exact source location, not just retrieve a paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and cons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One SDK call replaces a generic PDF parser plus custom table/heading recovery logic&lt;/li&gt;
&lt;li&gt;Markdown output is chunker-friendly out of the box — headings and tables survive&lt;/li&gt;
&lt;li&gt;Switching to &lt;code&gt;mode="structure"&lt;/code&gt; handles scanned PDFs without a separate OCR step&lt;/li&gt;
&lt;li&gt;Same client can return spatial JSON for documents needing exact structure, not just clean text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown and spatial JSON are separate outputs from the same request — decide upfront which one a document needs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mode="text"&lt;/code&gt; assumes a born-digital PDF; scanned documents need &lt;code&gt;mode="structure"&lt;/code&gt;, which costs more and runs slower&lt;/li&gt;
&lt;li&gt;The chunker above is heading-aware but simple — documents with deeply nested headings may need a more sophisticated splitter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building RAG pipelines from real-world PDFs — internal knowledge bases, policy documents, technical manuals — where retrieval quality depends on chunks that actually correspond to a coherent section of the source document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ingestion, not retrieval, is where most RAG pipelines from PDFs actually break.&lt;/li&gt;
&lt;li&gt;Converting to structured Markdown before chunking means your splitter works with real section boundaries instead of guessing at character counts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nutrient_dws&lt;/code&gt;'s &lt;code&gt;parse()&lt;/code&gt; call handles this in one step for born-digital PDFs, with &lt;code&gt;mode="structure"&lt;/code&gt; as the fallback for scans.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need a separate OCR step before this pipeline?&lt;/strong&gt;&lt;br&gt;
✅ No. Switching &lt;code&gt;mode="text"&lt;/code&gt; to &lt;code&gt;mode="structure"&lt;/code&gt; runs OCR as part of the same &lt;code&gt;parse()&lt;/code&gt; call for scanned or image-based PDFs — no separate pipeline to maintain.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Why not just chunk by a fixed character count?&lt;/strong&gt;&lt;br&gt;
✅ Fixed-length chunking ignores document structure — a chunk can start mid-table or split a heading from its content, which hurts both retrieval relevance and what the model can make sense of once a chunk is retrieved.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I get both Markdown and spatial JSON from one request?&lt;/strong&gt;&lt;br&gt;
✅ No — they're separate output formats on the same &lt;code&gt;parse()&lt;/code&gt; call. If a document needs both (Markdown for RAG, spatial JSON for exact table validation), send two requests.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What if my documents have deeply nested headings or unusual structure?&lt;/strong&gt;&lt;br&gt;
✅ The chunker in this walkthrough handles up to three heading levels (&lt;code&gt;#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;###&lt;/code&gt;). For more complex documents, you can extend the regex or add a maximum nesting depth before falling back to paragraph-level splitting.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is &lt;code&gt;mode="text"&lt;/code&gt; accurate enough for documents with some tables?&lt;/strong&gt;&lt;br&gt;
✅ For simple tables in born-digital PDFs, yes — they convert to Markdown table syntax correctly. For scanned tables or documents where table accuracy is critical, use &lt;code&gt;mode="structure"&lt;/code&gt; or &lt;code&gt;mode="understand"&lt;/code&gt; instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nutrient Data Extraction API&lt;/strong&gt;&lt;br&gt;
Turn PDFs into clean Markdown or spatial JSON — one Python SDK, born-digital or scanned.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.nutrient.io/api/data-extraction-api/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=rag-pipeline-pdfs-nutrient-python" rel="noopener noreferrer"&gt;Get started free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kevin Meneses González&lt;/strong&gt;&lt;br&gt;
Technical content for fintech and API companies — articles, tutorials, and video.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>rag</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>OCR vs AI Document Extraction: What's the Difference</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:32:27 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/ocr-vs-ai-document-extraction-whats-the-difference-38b2</link>
      <guid>https://dev.to/kevin_menesesgonzlez/ocr-vs-ai-document-extraction-whats-the-difference-38b2</guid>
      <description>&lt;p&gt;"Which OCR should I use?" is usually the wrong question.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automating invoice or form processing,&lt;/li&gt;
&lt;li&gt;building a RAG pipeline from PDFs,&lt;/li&gt;
&lt;li&gt;or setting up a compliance workflow that needs traceable data,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the question that actually matters is: do you need text, or do you need structure?&lt;/p&gt;

&lt;p&gt;The goal isn't just to make a document readable. The goal is to make document data usable, reviewable, and ready for downstream systems.&lt;/p&gt;

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

&lt;p&gt;OCR answers "what does this text say?" — it converts pixels into characters.&lt;/p&gt;

&lt;p&gt;AI document extraction answers "what is this content, where is it on the page, and how does it relate to the rest of the document?" — it preserves structure like tables, fields, reading order, and source context.&lt;/p&gt;

&lt;p&gt;Nutrient Data Extraction API exposes this as four processing modes — Text, Structure, Understand, and Agentic — so you can match extraction depth to document complexity.&lt;/p&gt;

&lt;p&gt;Use Parse when you need full document structure as Markdown or spatial JSON. Use Extract when you need specific schema-defined fields returned as structured JSON with source context for review.&lt;/p&gt;

&lt;p&gt;Below: the same document requested in two output formats, so you can see exactly where plain text extraction stops and structure begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "OCR" became the wrong catch-all term
&lt;/h2&gt;

&lt;p&gt;Most people use "OCR" to mean "get text out of an image." That's technically correct and practically misleading.&lt;/p&gt;

&lt;p&gt;Plain OCR usually gives you recognized text, sometimes with word or line positions. But it does not reliably preserve higher-level structure like tables, field relationships, heading hierarchy, or reading order across complex layouts. Some OCR engines return positions or basic layout hints, but that's not the same as understanding how a page is organized.&lt;/p&gt;

&lt;p&gt;Then teams plug that output into a downstream system expecting structure — and discover the table became "Invoice #2024-0892\nItem Amount\nWidget A $45.00", with no indication of which value belongs to which column.&lt;/p&gt;

&lt;p&gt;That's not an OCR bug. It's OCR doing exactly what it was built to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real difference: reading vs understanding
&lt;/h2&gt;

&lt;p&gt;OCR reads. It converts pixels to characters, in roughly left-to-right, top-to-bottom order, and stops there.&lt;/p&gt;

&lt;p&gt;Document understanding — the job behind AI-powered document extraction — analyzes layout. It detects tables and preserves rows and columns. It recognizes reading order across multi-column pages. It classifies elements (paragraph, heading, table, key-value region) instead of just returning a wall of text.&lt;/p&gt;

&lt;p&gt;The distinction that matters in practice: plain text extraction gives you the words. Structured extraction gives you the words with layout, relationships, and context&lt;/p&gt;

&lt;h2&gt;
  
  
  Four modes, one spectrum
&lt;/h2&gt;

&lt;p&gt;Nutrient Data Extraction API makes this spectrum concrete with four processing modes, all reachable through the same API:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Text&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Understand&lt;/th&gt;
&lt;th&gt;Agentic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Text extraction&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table structure&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key-value regions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handwriting&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formulas&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, as LaTeX&lt;/td&gt;
&lt;td&gt;Yes, as LaTeX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading order&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Layout-aware&lt;/td&gt;
&lt;td&gt;Layout-aware&lt;/td&gt;
&lt;td&gt;Layout-aware, deeper reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit for&lt;/td&gt;
&lt;td&gt;Fast Markdown/text extraction for born-digital documents&lt;/td&gt;
&lt;td&gt;Spatial JSON and OCR-backed layout for scans and image-based documents&lt;/td&gt;
&lt;td&gt;Complex layouts, tables, handwriting, formulas, and richer structure&lt;/td&gt;
&lt;td&gt;The most complex documents that need deeper visual reasoning and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relative cost/speed&lt;/td&gt;
&lt;td&gt;Fastest, cheapest&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;td&gt;Highest, slowest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Text mode&lt;/strong&gt; —  Fast text extraction for born-digital documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure mode&lt;/strong&gt; — OCR-backed structure extraction for scans and image-based documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understand mode&lt;/strong&gt; — ICR for complex layouts, handwriting, formulas, OCR correction, and richer document structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agentic mode&lt;/strong&gt; —  VLM-enhanced ICR for deeper visual reasoning, semantic understanding, and recovery.&lt;/p&gt;

&lt;p&gt;Under the hood, components like OCR and ICR (intelligent character recognition) do the character-level work, but the mode you choose is what actually changes what comes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Studio-first, API-next
&lt;/h2&gt;

&lt;p&gt;Before deciding which mode fits a document, Data Extraction API Studio lets you upload a file, run it through Parse, compare modes side by side, and inspect the Markdown or JSON output directly in your browser — 5,000 free credits, no credit card required. It's the fastest way to see the difference on your own documents before writing any integration code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.nutrient.io/api/data-extraction-api/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=ocr-vs-ai-document-extraction" rel="noopener noreferrer"&gt;Get started free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Parse vs. Extract, and Markdown vs. JSON
&lt;/h2&gt;

&lt;p&gt;There are two separate choices that shape what comes back from the API, and it's worth naming both before writing any code: what job you want the API to perform, and what output format your workflow needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Parse&lt;/strong&gt; when you want the full document structure. The Markdown response is clean, readable content for AI, RAG, search, and document Q&amp;amp;A. It is easier to work with than raw OCR text, but it is not the best format when you need layout-aware elements, coordinates, confidence, and table cell relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Extract&lt;/strong&gt; when you need specific schema-defined fields — invoice number, vendor, total, line items, contract dates, form values — returned as structured JSON with source context for review. Studio's schema generator can scaffold a starting schema from a sample document, which you can review and refine before running extraction.&lt;/p&gt;

&lt;p&gt;The processing mode (Text/Structure/Understand/Agentic) determines how deeply the document is analyzed. The output format determines how the result is handed back to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: the same document, two output formats
&lt;/h2&gt;

&lt;p&gt;Here's the same intake form requested in two output formats — Markdown for clean, readable content, and JSON for layout-aware structure — using the REST API:&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 https://api.nutrient.io/extraction/parse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$NUTRIENT_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@intake_form.pdf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'instructions={"output":{"format":"markdown"}}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.nutrient.io/extraction/parse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$NUTRIENT_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@intake_form.pdf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'instructions={"output":{"format":"json"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same file, same endpoint. The processing mode is set as a separate request parameter alongside the output format — check the API documentation for the current field name and accepted values, or set it directly in Studio and copy the generated request.&lt;/p&gt;

&lt;p&gt;The Markdown response is a clean, flat rendering of the content — accurate, but with table rows collapsed into plain text. A simplified spatial JSON response might look 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;"elements"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"boundingBox"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;680&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;340&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;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tableCell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tableCell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;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;That nesting is the entire difference. Plain text extraction never produces it — not because it's less accurate, but because table structure isn't something character recognition alone is designed to detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling up: processing 100 invoices
&lt;/h2&gt;

&lt;p&gt;A single curl call is enough to see the difference between output formats. It's not enough to run a real batch. Here's a simple Python script that walks a folder of invoices, sends each one through the API, and saves the results — the kind of script you'd actually point at 100 files:&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Batch-process a folder of invoices through the Nutrient Data Extraction API.

Usage:
    python batch_extract.py ./invoices --format json --out ./results
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="c1"&gt;# Note:
# The response field names below (for example "output" and
# "usage.data_extraction_credits") are illustrative.
# Verify the current response schema in the official API
# documentation before using this script in production.
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.nutrient.io/extraction/parse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;files&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;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;data&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;instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:{{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}}}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batch extract invoices with Nutrient&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Folder containing invoice PDFs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&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;markdown&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;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Folder to write results to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;input_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&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;pdf_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;total_credits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Note: field names below (output, usage.data_extraction_credits) should be
&lt;/span&gt;        &lt;span class="c1"&gt;# confirmed against the current API reference before running this in production.
&lt;/span&gt;        &lt;span class="n"&gt;out_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stem&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;credits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_extraction_credits&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;total_credits&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;credits&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;cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Simple pacing to stay under rate limits on large batches
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; succeeded, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total credits used: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_credits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed files:&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;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it: &lt;code&gt;python batch_extract.py ./invoices --format json --out ./results&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A few things worth calling out for a batch like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failures are tracked, not fatal.&lt;/strong&gt; One malformed PDF in a folder of 100 shouldn't kill the run — the script logs it and keeps going, then prints the failed filenames at the end so you can retry just those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit usage is summed across the batch&lt;/strong&gt;, so you know the actual cost of processing 100 invoices before you scale to 1,000.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;time.sleep(0.2)&lt;/code&gt; is a simple pacing guard, not a rate-limit implementation — for larger batches or production use, check the API docs for current rate limits and consider proper backoff instead of a fixed delay.&lt;/li&gt;
&lt;li&gt;Structure or Understand mode is usually the better starting point for real invoices, especially when you need tables, line items, and totals&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When each one is the wrong tool
&lt;/h2&gt;

&lt;p&gt;Text mode is the wrong tool when: you need to reconstruct a table, route a form field to the correct database column, or trust that "Total" and "$4,250" are actually linked. It will hand you the right characters in the wrong shape.&lt;/p&gt;

&lt;p&gt;Understand or Agentic mode is the wrong tool when: you're indexing millions of simple, clean digital pages for keyword search and don't need layout at all. Running the deepest mode on documents that Text mode would handle just as well is the most common way teams overpay for extraction they don't need.&lt;/p&gt;

&lt;p&gt;Match the mode to the job — not the other way around. If you need specific fields rather than the whole document, Extract with a schema (generated from a sample document via Studio's schema generator) is usually a better fit than parsing the full page and post-processing it yourself.&lt;/p&gt;

&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Plain OCR converts pixels to characters. Plain text extraction gets text out, but it was never meant to understand layout.&lt;/li&gt;
&lt;li&gt;Structure, Understand, and Agentic modes classify elements, preserve table structure, and determine reading order — a different job, not a better version of the same job.&lt;/li&gt;
&lt;li&gt;Picking the right mode per document (not defaulting to the most powerful one) is what keeps extraction both accurate and cost-efficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;❓ Is AI document extraction just "OCR with AI added"?&lt;/strong&gt;&lt;br&gt;
✅ Not exactly. OCR is the character-recognition layer. AI document extraction uses OCR or other recognition methods as part of a broader pipeline that also analyzes layout, detects tables, identifies fields, preserves reading order, and returns structured output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Do I need to run OCR separately before using Structure or Understand mode?&lt;/strong&gt;&lt;br&gt;
✅ No — both modes already include character recognition as part of the pipeline. You don't run OCR first and then request structure on top; you pick the mode that matches what the document needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Can Text mode handle scanned tables if I clean up the output myself?&lt;/strong&gt;&lt;br&gt;
✅ You can write custom logic to reconstruct a table from flat text, but it breaks the moment a vendor changes their layout. Structure mode is designed to reduce this brittleness by returning layout-aware elements, table structure, coordinates, and page context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Which mode should I default to if I'm not sure?&lt;/strong&gt;&lt;br&gt;
✅ Start with Structure mode for scanned or image-based documents. Move up to Understand or Agentic when tables, columns, handwriting, or complex layouts need deeper analysis&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Does a more advanced mode always mean better results?&lt;/strong&gt;&lt;br&gt;
✅ Not for every document. Understand and Agentic modes cost more and run slower than Structure — worth it for genuinely complex layouts, unnecessary overhead for a clean, well-structured scan.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nutrient Data Extraction API&lt;/strong&gt;&lt;br&gt;
Turn PDFs, scans, images, and Office files into structured JSON or clean Markdown — with the right mode for every document.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.nutrient.io/api/data-extraction-api/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=ocr-vs-ai-document-extraction" rel="noopener noreferrer"&gt;Get started free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ocr</category>
      <category>ai</category>
      <category>data</category>
      <category>automation</category>
    </item>
    <item>
      <title>From Messy PDFs to Structured JSON: An LLM-Ready Workflow</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:33:16 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/from-messy-pdfs-to-structured-json-an-llm-ready-workflow-11mo</link>
      <guid>https://dev.to/kevin_menesesgonzlez/from-messy-pdfs-to-structured-json-an-llm-ready-workflow-11mo</guid>
      <description>&lt;p&gt;Most AI pipelines don't fail because of the model.&lt;/p&gt;

&lt;p&gt;They fail because of the input.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building RAG pipelines,&lt;/li&gt;
&lt;li&gt;automating invoice or contract intake,&lt;/li&gt;
&lt;li&gt;or feeding documents into an LLM for Q&amp;amp;A,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the PDF is usually the weakest link in the chain.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A PDF is a layout format, not a data format. Generic text extraction often loses reading order, table structure, and source context.&lt;/li&gt;
&lt;li&gt;Nutrient Data Extraction API turns PDFs, scans, images, and Office files into structured output: structured JSON for schema-defined extraction and downstream systems, or Markdown for AI/search workflows.&lt;/li&gt;
&lt;li&gt;Use Studio to test documents first, compare processing modes, generate schemas, and inspect output before integrating with the API.&lt;/li&gt;
&lt;li&gt;The API supports four processing modes — Text, Structure, Understand, and Agentic — so teams can balance speed, cost, and extraction quality based on document complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The PDF was never built for this
&lt;/h2&gt;

&lt;p&gt;A PDF remembers how a page &lt;em&gt;looks&lt;/em&gt;. It doesn't remember what the content &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That distinction breaks most naive extraction attempts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text is stored in fragments, often out of reading order&lt;/li&gt;
&lt;li&gt;Tables are just aligned text — there's no actual "table" object&lt;/li&gt;
&lt;li&gt;Scanned pages have no text layer at all&lt;/li&gt;
&lt;li&gt;Two-column layouts get merged into a single unreadable stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers usually discover this too late — after a regex-based parser works fine on three test invoices and then falls apart on the fourth, because the vendor used a different template.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why text extraction alone isn't enough
&lt;/h2&gt;

&lt;p&gt;Getting text out of a PDF is easy. Getting text out &lt;em&gt;with the relationships preserved&lt;/em&gt; — which value belongs to which row, which heading owns which paragraph, which number came from which page — is the actual job.&lt;/p&gt;

&lt;p&gt;An LLM does not just need more text. It needs structured content that preserves context, order, and source grounding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nutrient Data Extraction API: Studio-first, API-next
&lt;/h2&gt;

&lt;p&gt;Nutrient Data Extraction API processes PDFs, scans, images, and Office files, and returns either structured JSON or clean Markdown, with source context such as confidence scores, page references, and coordinates.&lt;/p&gt;

&lt;p&gt;Before wiring extraction into an application, you can test documents in &lt;a href="https://dashboard.nutrient.io/data-extraction-api/studio/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=messy-pdfs-to-structured-json" rel="noopener noreferrer"&gt;Data Extraction API Studio&lt;/a&gt;. Upload a file, choose Parse or Extract, compare processing modes, inspect Markdown or JSON output, and review source context before moving to the API — 5,000 free credits, no credit card required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parse vs. Extract
&lt;/h2&gt;

&lt;p&gt;The API gives you two request paths, and picking the right one matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parse&lt;/strong&gt; returns the full document content as clean Markdown or structured JSON blocks — use it when you need the whole document, structured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; pulls specific fields you define in a schema — such as invoice number, total amount, or vendor name — use it when you know exactly which fields you need out of a document type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most RAG and search use cases run through Parse. Invoice, form, and structured-record workflows usually reach for Extract once the schema is defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown vs. spatial JSON
&lt;/h2&gt;

&lt;p&gt;Parse and Extract are about &lt;em&gt;which&lt;/em&gt; request you send. Markdown and spatial JSON are about &lt;em&gt;what format&lt;/em&gt; the content comes back in — and that choice is independent of Parse vs. Extract; both output formats are available either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Markdown&lt;/strong&gt; is clean, structured content — headings, lists, and tables rendered as Markdown syntax (including real &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; markup for tables). It's positioned as the go-to format for AI and search workflows: RAG ingestion, document Q&amp;amp;A, knowledge base indexing, content migration. It reads like a well-formatted document, not a data structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spatial JSON&lt;/strong&gt; is the layout-aware format — a typed list of elements, each with its type, its role (heading, body text, footer, and so on), its bounding box, its reading order, and a confidence score. It's the stronger choice when you need element types, bounds, coordinates, and confidence, or when a value has to be traceable back to an exact position on the page — table cell validation, form field mapping, audit trails.&lt;/p&gt;

&lt;p&gt;A rule of thumb: if the next step is "feed this to an LLM or a search index," reach for Markdown. If the next step is "validate this value, map it to a database column, or highlight it on the source page," reach for spatial JSON.&lt;/p&gt;

&lt;p&gt;They're separate output formats on the same Parse request — check the API docs for the current recommended pattern if a workflow genuinely needs both from one document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing modes: matching depth to the document
&lt;/h2&gt;

&lt;p&gt;Instead of a single one-size-fits-all pass, the API gives you four processing modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Born-digital documents where speed and cost matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scans and image-based documents that need OCR and layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Understand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex layouts, tables, handwriting, forms, and higher-quality structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agentic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visually complex or ambiguous documents where deeper reasoning is worth the extra cost — deeper visual reasoning and recovery for documents that need more advanced analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You pick the mode per document. A clean digital invoice doesn't need the same processing budget as a scanned, handwritten form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema generation for field extraction
&lt;/h2&gt;

&lt;p&gt;For Extract workflows, you don't have to write a schema from scratch. Schema generator can create a starting schema from a sample document — you then review and refine the fields, types, and instructions before running extraction through the API.&lt;/p&gt;

&lt;p&gt;This matters in practice: most teams don't know the exact field list a document type needs until they've looked at a handful of real samples. Starting from a generated schema and refining it is faster and more accurate than guessing upfront.&lt;/p&gt;

&lt;h2&gt;
  
  
  API example: parsing a document
&lt;/h2&gt;

&lt;p&gt;Here's a minimal Parse request that converts a PDF to Markdown:&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 https://api.nutrient.io/extraction/parse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$NUTRIENT_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@document.pdf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'instructions={"output":{"format":"markdown"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;"format":"markdown"&lt;/code&gt; for &lt;code&gt;"format":"json"&lt;/code&gt; when you need structured elements with coordinates and confidence instead of clean Markdown — for example, when you need to trace a value back to its exact position on the page, or preserve table cell structure precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reusable Python script
&lt;/h2&gt;

&lt;p&gt;The curl one-liner is fine for a quick test. For anything you'll run more than once, here's a small Python CLI wrapper around the same endpoint — it takes a PDF path, an output format, and an optional output file:&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Extract structured content from a PDF using the Nutrient extraction API.

Setup:
    pip install -r requirements.txt
    Copy .env.example to .env and add your Nutrient API key.

Usage:
    python extract_pdf.py path/to/file.pdf
    python extract_pdf.py path/to/file.pdf --format json
    python extract_pdf.py path/to/file.pdf --output result.md
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&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;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUTRIENT_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.nutrient.io/extraction/parse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;files&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;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;data&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;instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:{{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}}}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extract content from a PDF via Nutrient&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path to the PDF file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&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;markdown&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;json&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write the extracted content to this file instead of stdout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUTRIENT_API_KEY not found. Create a .env file next to this script &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;with the line: NUTRIENT_API_KEY=your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sys&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;File not found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;credits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_extraction_credits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[credits used: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;credits&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;cost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, remaining: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;credits&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;remainingCredits&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this next to a &lt;code&gt;.env&lt;/code&gt; file with &lt;code&gt;NUTRIENT_API_KEY=your_api_key&lt;/code&gt;, and you have a script you can point at any PDF: &lt;code&gt;python extract_pdf.py invoice.pdf --format json --output invoice.json&lt;/code&gt;. It's the same request the curl example makes — just wrapped in something you'll actually reuse.&lt;/p&gt;

&lt;p&gt;For schema-defined Extract requests, the exact request shape depends on your schema — check the &lt;a href="https://www.nutrient.io/api/data-extraction-api/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=messy-pdfs-to-structured-json" rel="noopener noreferrer"&gt;official API documentation&lt;/a&gt; for the current Extract endpoint and payload format, or generate a starting request directly from Studio once your schema is ready.&lt;/p&gt;

&lt;p&gt;A JSON response from Parse looks like this — element types, positions, and reading order, not a flat text dump:&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;"elements"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paragraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invoice #2024-0892"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"boundingBox"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&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;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"boundingBox"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;680&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;340&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;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tableCell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tableCell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;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;Compare that to plain OCR output: &lt;code&gt;"Invoice #2024-0892\nItem Amount\nWidget A $45.00"&lt;/code&gt; — with no indication of which value belongs to which column.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real response actually looks like
&lt;/h2&gt;

&lt;p&gt;The example above is simplified to make the shape easy to read. Here's what Parse actually returns for a real invoice — anonymized, but otherwise untouched — first as Markdown, then as structured JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Markdown output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Invoice&lt;/span&gt;

INV-2026-0001 Invoice number

July 3, 2026 Date of issue

&lt;span class="gu"&gt;## Medium&lt;/span&gt;

123 Main Street 94105 San Francisco California USA +1 555 010 2938

Date due July 17, 2026

&lt;span class="gu"&gt;## $175.00 USD due July 17, 2026&lt;/span&gt;

&lt;span class="gu"&gt;## Pay online&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Description&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Unit price&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Amount&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Content Marketing&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$175.00&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$175.00&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Subtotal&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$175.00&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Total&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$175.00&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Amount due&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$175.00 USD&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Medium&lt;/span&gt;

Bill to

Jamie Chen jamie.chen@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the table survives as real HTML &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; markup embedded in the Markdown — not flattened into a line of numbers. That's what makes Markdown output usable for RAG chunking without losing the row/column relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON output (trimmed — the real response includes every element, and the table's cell array is shortened here for readability):&lt;/strong&gt;&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;"elements"&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;"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;"ad35142f-5426-4995-94e3-56682a3a8c7c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paragraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SectionHeader"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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.939276&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;84.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;92.88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;170.97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.12&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;"page"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2200&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;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;"a0d1192d-3d59-4a4b-b10f-16df2233dbbb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paragraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-2026-0001 Invoice number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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.904187&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;82.81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;195.59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;416.19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;23.41&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;"page"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2200&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;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;"7184b67c-468d-42d6-9e00-f0dd10a2a4c3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paragraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123 Main Street&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;94105 San Francisco&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;California&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;USA&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;+1 555 010 2938"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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.680442&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;82.46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;394.16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;530.54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;174.84&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;"page"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2200&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;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;"8532e005-9b64-4a04-9745-f1a963a27f4d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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.928583&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rowCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"columnCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;80.54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;825.07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1537.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;246.88&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;"page"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2200&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;"cells"&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unit price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content Marketing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$175.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;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amount due"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"row"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$175.00 USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;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;"c38ff641-a643-4ad5-b9e2-4c370fc6a0b3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paragraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jamie Chen&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;jamie.chen@example.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;"confidence"&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.686578&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readingOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&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;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;695.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;394.81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;279.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;65.19&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;"page"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2200&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;span class="nl"&gt;"languageDetection"&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;"pages"&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;"pageNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"languages"&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;"eng"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"textDirection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lrtb"&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;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;Two things worth pointing out here that don't show up in a toy example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;role&lt;/code&gt; classifies elements&lt;/strong&gt;, not just types — &lt;code&gt;SectionHeader&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt;, and &lt;code&gt;Footer&lt;/code&gt; all come back as &lt;code&gt;paragraph&lt;/code&gt; type, but &lt;code&gt;role&lt;/code&gt; tells you which one is a heading versus body text versus a page footer. That's the field to key off if you're deciding what belongs in a RAG chunk versus what to drop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence varies by element&lt;/strong&gt;, and it's not always near 1.0 — the seller address block above scored 0.68, noticeably lower than the clean, high-contrast text elements around it. That's exactly the kind of value a validation threshold should catch before it moves downstream, not something to assume is correct because the rest of the document extracted cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The architecture: where extraction sits in the pipeline
&lt;/h2&gt;

&lt;p&gt;It helps to see this as a layer, not a single API call. A production document pipeline usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document intake (upload / email / scan / hosted URL)
        │
        ▼
Mode selection (Text / Structure / Understand / Agentic)
        │
        ▼
Nutrient Data Extraction API — Parse or Extract
   → structured JSON (elements, coordinates, confidence, reading order)
   → or Markdown (clean, LLM-ready content)
        │
        ▼
Validation layer
   → flag elements below a confidence threshold
   → route flagged elements to human review
        │
        ▼
Downstream systems
   → database / ERP (structured JSON)
   → RAG index / vector store (Markdown)
   → review queue (flagged elements + bounding boxes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part teams usually skip is the &lt;strong&gt;validation layer&lt;/strong&gt;. Extraction without a confidence check just moves the trust problem one step downstream — instead of a human misreading a PDF, you get an LLM confidently building on top of a weak extraction. Coordinates and confidence scores make that validation step practical: you set a threshold (say, anything under 85% confidence), route those specific elements to a review queue, and let everything else flow through automatically.&lt;/p&gt;

&lt;p&gt;Mode selection also isn't a one-time decision — it's a per-document routing rule. A born-digital invoice from your billing system doesn't need the same processing depth as a photographed, handwritten delivery note. Sending everything through the most expensive mode by default is the most common way teams overpay for extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and cons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source context — confidence scores, page references, and coordinates — comes back alongside the extracted content, not just plain text&lt;/li&gt;
&lt;li&gt;Four processing modes mean you're not paying for AI-heavy extraction on documents that don't need it&lt;/li&gt;
&lt;li&gt;Same API handles PDFs, scans, images, and Office files — no separate OCR pipeline to maintain&lt;/li&gt;
&lt;li&gt;Studio lets you test Parse and Extract, compare modes, and generate a schema before writing any integration code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files are not persistently stored after processing, which is good for compliance but means you handle retention or audit storage yourself if you need it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building document-heavy pipelines — RAG ingestion, invoice/contract automation, compliance workflows — where "we got some text out" isn't good enough and you need to trace values back to their source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical use cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Invoice and statement processing&lt;/strong&gt;&lt;br&gt;
Route invoices through Structure or Understand mode via Extract with a schema for line items and totals, and flag any amount under your confidence threshold for a human to check before it posts to your accounting system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Contract review pipelines&lt;/strong&gt;&lt;br&gt;
Use Parse to extract clause-level text with reading order preserved, so a downstream LLM step (clause classification, obligation extraction) works with clauses in the order they actually appear in the contract — not reshuffled by a naive text dump.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. RAG knowledge base ingestion&lt;/strong&gt;&lt;br&gt;
Use Markdown output from Parse directly as chunks for your vector store. Because headings, lists, and tables are preserved instead of flattened, retrieval quality improves — the model gets a coherent section, not fragments stitched together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Compliance and audit trails&lt;/strong&gt;&lt;br&gt;
For regulated workflows (financial statements, medical intake forms, legal filings), the combination of confidence score and page coordinate means extracted values can be traced back to a location in the source document — which is closer to what most audit processes actually require than "we have the data somewhere."&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the loop
&lt;/h2&gt;

&lt;p&gt;Structured elements with reading order, confidence, and bounding boxes are what turn "we extracted some text" into "we extracted data we can act on" — whether that means posting a line item to your ERP, indexing a chunk for RAG, or routing a flagged field to a human.&lt;/p&gt;

&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A PDF is a presentation format. Structure has to be extracted, not assumed.&lt;/li&gt;
&lt;li&gt;Source context — confidence scores and page coordinates — is what makes extracted data traceable back to the source, not just readable.&lt;/li&gt;
&lt;li&gt;Match the processing mode (Text / Structure / Understand / Agentic) and request path (Parse / Extract) to the document instead of defaulting to one approach for everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the modes actually perform
&lt;/h2&gt;

&lt;p&gt;The table above tells you when to use each mode. Here's what that difference looks like in numbers — internal benchmark scores (Build #200, July 13, 2026, commit &lt;code&gt;c24e99581f&lt;/code&gt;), all on a 0–1 scale where higher is better:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Overall&lt;/th&gt;
&lt;th&gt;NID&lt;/th&gt;
&lt;th&gt;NID-S&lt;/th&gt;
&lt;th&gt;TEDS&lt;/th&gt;
&lt;th&gt;TEDS-S&lt;/th&gt;
&lt;th&gt;MHS&lt;/th&gt;
&lt;th&gt;MHS-S&lt;/th&gt;
&lt;th&gt;Zone-F1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Understand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9321&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9590&lt;/td&gt;
&lt;td&gt;0.9567&lt;/td&gt;
&lt;td&gt;0.9365&lt;/td&gt;
&lt;td&gt;0.9444&lt;/td&gt;
&lt;td&gt;0.8671&lt;/td&gt;
&lt;td&gt;0.9078&lt;/td&gt;
&lt;td&gt;0.6892&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agentic (Vision)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9304&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9572&lt;/td&gt;
&lt;td&gt;0.9548&lt;/td&gt;
&lt;td&gt;0.9370&lt;/td&gt;
&lt;td&gt;0.9444&lt;/td&gt;
&lt;td&gt;0.8649&lt;/td&gt;
&lt;td&gt;0.9078&lt;/td&gt;
&lt;td&gt;0.7006&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structure&lt;/td&gt;
&lt;td&gt;0.8923&lt;/td&gt;
&lt;td&gt;0.9342&lt;/td&gt;
&lt;td&gt;0.9288&lt;/td&gt;
&lt;td&gt;0.7386&lt;/td&gt;
&lt;td&gt;0.7846&lt;/td&gt;
&lt;td&gt;0.8282&lt;/td&gt;
&lt;td&gt;0.8884&lt;/td&gt;
&lt;td&gt;0.4288&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;0.8887&lt;/td&gt;
&lt;td&gt;0.9263&lt;/td&gt;
&lt;td&gt;0.9291&lt;/td&gt;
&lt;td&gt;0.7394&lt;/td&gt;
&lt;td&gt;0.7902&lt;/td&gt;
&lt;td&gt;0.8239&lt;/td&gt;
&lt;td&gt;0.8862&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30bu01zktkhdwx0u31b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30bu01zktkhdwx0u31b4.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The -S suffix variants (NID-S, TEDS-S, MHS-S) are strict versions of each metric. Zone-F1 measures spatial layout accuracy and doesn't apply to Text mode, which doesn't preserve layout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What each metric actually measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NID (Normalized Information Distance)&lt;/strong&gt; — how much information survives from the original document. 0.95+ means almost nothing is lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TEDS (Tree Edit Distance Score)&lt;/strong&gt; — table structure accuracy specifically: how well row/column relationships in extracted tables match the source. This is the metric that separates Text/Structure from Understand/Agentic the most — TEDS is the hardest metric for complex documents, and scores below 0.80 are common outside these modes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MHS (Markdown Heading Score)&lt;/strong&gt; — whether the heading hierarchy (H1/H2/H3) gets reconstructed correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zone-F1&lt;/strong&gt; — spatial accuracy: whether bounding-box zones (text regions, tables, figures) are correctly detected and labeled. Higher-depth modes (Understand, Agentic) score noticeably better here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical read: Text and Structure hold up fine on NID (they capture the content), but their TEDS scores drop hard on anything with real tables — 0.74 vs 0.93+ for Understand and Agentic. If your documents are table-heavy, that gap is the one to design around, not overall accuracy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5hzw5wyy3dzbl0sv6p9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5hzw5wyy3dzbl0sv6p9.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see more details &lt;a href="https://github.com/pspdfkit/pdf-to-markdown" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I get structured JSON and Markdown from the same request?&lt;/strong&gt;&lt;br&gt;
✅ Choose the output format based on the use case — structured JSON when you need layout-aware elements with coordinates and confidence, or Markdown when you need clean content for RAG, search, or document Q&amp;amp;A. Check the API docs for the recommended request pattern if a workflow needs both.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Does this replace OCR, or run on top of it?&lt;/strong&gt;&lt;br&gt;
✅ It replaces the need for a separate OCR step. Structure and Understand modes handle scanned or image-based documents directly through the same API — you don't build or maintain a separate OCR pipeline before extraction.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;How do I know which processing mode to use for a given document?&lt;/strong&gt;&lt;br&gt;
✅ Start with Text mode for born-digital PDFs (fastest, cheapest). Use Structure mode once scanning or OCR is involved. Reach for Understand mode on complex layouts, handwriting, or documents needing OCR correction. Reserve Agentic mode for visually complex or ambiguous documents where deeper reasoning is worth the extra cost.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What happens to my documents after they're processed?&lt;/strong&gt;&lt;br&gt;
✅ Files are not persistently stored after processing. If you need retention for audit purposes, that's handled on your side, not the API's.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there a free way to test this before integrating it?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Data Extraction API Studio gives you 5,000 free credits, no credit card required, so you can upload your own documents, compare Parse and Extract, try all four modes, and generate a starting schema before writing any code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nutrient Data Extraction API&lt;/strong&gt;&lt;br&gt;
Turn messy PDFs into structured JSON or Markdown your LLM pipeline can trust — with source context on every request.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.nutrient.io/api/data-extraction-api/?utm_campaign=launch-data-extraction-api-2026q2&amp;amp;utm_source=dev-to&amp;amp;utm_medium=sponsoring&amp;amp;utm_content=messy-pdfs-to-structured-json" rel="noopener noreferrer"&gt;Get started free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ocr</category>
      <category>api</category>
      <category>pdf</category>
      <category>json</category>
    </item>
    <item>
      <title>Top 7 LLM Observability Tools Every AI Engineer Should Know (2026)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 15 Jul 2026 11:57:13 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/top-7-llm-observability-tools-every-ai-engineer-should-know-2026-3j7a</link>
      <guid>https://dev.to/kevin_menesesgonzlez/top-7-llm-observability-tools-every-ai-engineer-should-know-2026-3j7a</guid>
      <description>&lt;p&gt;Artificial intelligence has made it easier than ever to build applications powered by large language models. In just a few hours, you can create a chatbot, a RAG pipeline, or even an autonomous AI agent capable of calling APIs and interacting with external tools.&lt;/p&gt;

&lt;p&gt;Getting a demo to work is no longer the hard part.&lt;/p&gt;

&lt;p&gt;Running that same application in production — serving hundreds or thousands of users every day — is where the real engineering challenge begins.&lt;/p&gt;

&lt;p&gt;Once real traffic starts flowing, developers quickly encounter questions that traditional logging systems cannot answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why did response latency suddenly increase?&lt;/li&gt;
&lt;li&gt;Which prompts are consuming the most tokens?&lt;/li&gt;
&lt;li&gt;Which customers generate the highest API costs?&lt;/li&gt;
&lt;li&gt;What caused this hallucinated answer?&lt;/li&gt;
&lt;li&gt;Did the latest prompt update improve or reduce response quality?&lt;/li&gt;
&lt;li&gt;Which LLM performs best for this specific task?&lt;/li&gt;
&lt;li&gt;How can I debug an AI agent that executed twenty tool calls before failing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are exactly the problems that LLM observability platforms are designed to solve.&lt;/p&gt;

&lt;p&gt;Instead of treating AI models as mysterious black boxes, observability platforms allow engineering teams to inspect every interaction, measure performance, optimize costs, evaluate outputs, and continuously improve applications running in production.&lt;/p&gt;

&lt;p&gt;Whether you're building customer support assistants, financial research tools, coding agents, document-processing systems, or multi-agent workflows, observability has become a critical part of the modern AI stack.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore seven of the most popular LLM observability platforms available in 2026 and discuss where each one excels.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is LLM Observability?
&lt;/h2&gt;

&lt;p&gt;Traditional application monitoring focuses on metrics like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;HTTP requests&lt;/li&gt;
&lt;li&gt;Database performance&lt;/li&gt;
&lt;li&gt;Errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLM applications introduce an entirely new layer of complexity.&lt;/p&gt;

&lt;p&gt;Every request now contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompts&lt;/li&gt;
&lt;li&gt;Context windows&lt;/li&gt;
&lt;li&gt;Retrieved documents&lt;/li&gt;
&lt;li&gt;Model parameters&lt;/li&gt;
&lt;li&gt;Tool calls&lt;/li&gt;
&lt;li&gt;Structured outputs&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Cost information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional monitoring platforms simply weren't designed for this.&lt;/p&gt;

&lt;p&gt;LLM observability extends software monitoring by tracking everything that happens during an AI interaction.&lt;/p&gt;

&lt;p&gt;A typical observability platform records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt execution&lt;/li&gt;
&lt;li&gt;Model responses&lt;/li&gt;
&lt;li&gt;Token consumption&lt;/li&gt;
&lt;li&gt;Cost per request&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;User sessions&lt;/li&gt;
&lt;li&gt;Agent traces&lt;/li&gt;
&lt;li&gt;Tool execution&lt;/li&gt;
&lt;li&gt;Prompt versions&lt;/li&gt;
&lt;li&gt;Evaluation scores&lt;/li&gt;
&lt;li&gt;Errors and failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows developers to understand not only whether something failed, but why it failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is It Becoming Essential?
&lt;/h2&gt;

&lt;p&gt;Six months ago, many AI applications consisted of a single API call to GPT-4.&lt;/p&gt;

&lt;p&gt;Today's applications are very different.&lt;/p&gt;

&lt;p&gt;A single user request may involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector database retrieval&lt;/li&gt;
&lt;li&gt;Multiple prompt templates&lt;/li&gt;
&lt;li&gt;Several LLM calls&lt;/li&gt;
&lt;li&gt;External APIs&lt;/li&gt;
&lt;li&gt;MCP servers&lt;/li&gt;
&lt;li&gt;Python execution&lt;/li&gt;
&lt;li&gt;Memory retrieval&lt;/li&gt;
&lt;li&gt;Human approval&lt;/li&gt;
&lt;li&gt;Final answer generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without observability, debugging these workflows becomes almost impossible.&lt;/p&gt;

&lt;p&gt;As AI systems become more autonomous, engineering teams need visibility into every decision an agent makes.&lt;/p&gt;

&lt;p&gt;That's exactly where these platforms provide value.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Features Matter Most?
&lt;/h2&gt;

&lt;p&gt;Before comparing platforms, it's worth understanding the capabilities that differentiate them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracing
&lt;/h3&gt;

&lt;p&gt;Tracing records every step an AI application performs.&lt;/p&gt;

&lt;p&gt;Instead of seeing only the final response, developers can inspect the complete execution path.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question
↓
Retriever
↓
Prompt Template
↓
Claude
↓
Weather API
↓
Second Prompt
↓
Final Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dramatically simplifies debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Analytics
&lt;/h3&gt;

&lt;p&gt;LLM costs can grow surprisingly fast.&lt;/p&gt;

&lt;p&gt;Good observability platforms answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which user generated the highest cost?&lt;/li&gt;
&lt;li&gt;Which prompt consumes the most tokens?&lt;/li&gt;
&lt;li&gt;Which model is most cost-efficient?&lt;/li&gt;
&lt;li&gt;How much does each feature cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This information often leads to significant savings simply by optimizing prompts or routing requests to more appropriate models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Versioning
&lt;/h3&gt;

&lt;p&gt;Prompts evolve continuously.&lt;/p&gt;

&lt;p&gt;Without versioning, it's difficult to know which change introduced a regression.&lt;/p&gt;

&lt;p&gt;Modern platforms allow teams to compare prompt versions and roll back unsuccessful updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluation
&lt;/h3&gt;

&lt;p&gt;One of the biggest challenges in AI engineering is measuring quality.&lt;/p&gt;

&lt;p&gt;Unlike traditional software, LLM outputs aren't simply "correct" or "incorrect."&lt;/p&gt;

&lt;p&gt;Evaluation systems can automatically score responses based on criteria such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy&lt;/li&gt;
&lt;li&gt;Relevance&lt;/li&gt;
&lt;li&gt;Faithfulness&lt;/li&gt;
&lt;li&gt;Hallucination rate&lt;/li&gt;
&lt;li&gt;Toxicity&lt;/li&gt;
&lt;li&gt;Response quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some platforms even use another LLM as an automated judge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Debugging
&lt;/h3&gt;

&lt;p&gt;Imagine a customer reports:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your AI gave me the wrong answer yesterday."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without observability, reproducing that interaction is extremely difficult.&lt;/p&gt;

&lt;p&gt;With tracing enabled, engineers can inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact prompt&lt;/li&gt;
&lt;li&gt;Retrieved documents&lt;/li&gt;
&lt;li&gt;Model parameters&lt;/li&gt;
&lt;li&gt;Tool calls&lt;/li&gt;
&lt;li&gt;Response time&lt;/li&gt;
&lt;li&gt;Final output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This dramatically reduces debugging time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation Criteria
&lt;/h2&gt;

&lt;p&gt;For this comparison, each platform was evaluated across the areas above: tracing depth, cost analytics, prompt versioning, evaluation capabilities, and production debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Langfuse
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Open-source observability with powerful tracing and evaluation features.&lt;/p&gt;

&lt;p&gt;Langfuse has quickly become one of the most popular observability platforms for AI applications.&lt;/p&gt;

&lt;p&gt;Unlike traditional monitoring software, it was built specifically around LLM workflows.&lt;/p&gt;

&lt;p&gt;Its biggest strength is that it combines enterprise-level capabilities with an open-source foundation.&lt;/p&gt;

&lt;p&gt;For many engineering teams, this provides the best balance between flexibility and production readiness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end tracing&lt;/li&gt;
&lt;li&gt;Prompt management&lt;/li&gt;
&lt;li&gt;Dataset creation&lt;/li&gt;
&lt;li&gt;Evaluations&lt;/li&gt;
&lt;li&gt;Cost analytics&lt;/li&gt;
&lt;li&gt;Session tracking&lt;/li&gt;
&lt;li&gt;User analytics&lt;/li&gt;
&lt;li&gt;Self-hosted deployment&lt;/li&gt;
&lt;li&gt;OpenTelemetry support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One particularly useful capability is visualizing complete execution graphs for AI agents.&lt;/p&gt;

&lt;p&gt;Instead of reviewing logs line by line, developers can inspect the entire reasoning pipeline.&lt;/p&gt;

&lt;p&gt;This makes debugging dramatically faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Excellent developer experience&lt;/li&gt;
&lt;li&gt;✔ Open source&lt;/li&gt;
&lt;li&gt;✔ Self-hosting available&lt;/li&gt;
&lt;li&gt;✔ Strong documentation&lt;/li&gt;
&lt;li&gt;✔ Supports most modern AI frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise features require paid plans&lt;/li&gt;
&lt;li&gt;Can feel overwhelming for very small projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production AI applications&lt;/li&gt;
&lt;li&gt;RAG systems&lt;/li&gt;
&lt;li&gt;AI agents&lt;/li&gt;
&lt;li&gt;Multi-agent workflows&lt;/li&gt;
&lt;li&gt;Internal enterprise assistants&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. LangSmith
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams already building with LangChain and LangGraph.&lt;/p&gt;

&lt;p&gt;LangSmith is the observability platform developed by the LangChain team.&lt;/p&gt;

&lt;p&gt;Rather than acting as a generic monitoring solution, it's deeply integrated into the LangChain ecosystem.&lt;/p&gt;

&lt;p&gt;If your application uses LangGraph agents, LangSmith feels almost like a native debugging interface.&lt;/p&gt;

&lt;p&gt;Developers can replay executions, inspect intermediate reasoning steps, compare prompt versions, and evaluate changes over time.&lt;/p&gt;

&lt;p&gt;For complex AI agents, this level of visibility becomes invaluable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent tracing&lt;/li&gt;
&lt;li&gt;Prompt versioning&lt;/li&gt;
&lt;li&gt;Dataset management&lt;/li&gt;
&lt;li&gt;Human feedback&lt;/li&gt;
&lt;li&gt;Automated evaluations&lt;/li&gt;
&lt;li&gt;Experiment tracking&lt;/li&gt;
&lt;li&gt;Production monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of LangSmith's strongest capabilities is debugging multi-agent systems where dozens of individual reasoning steps occur before producing a final answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Best integration with LangGraph&lt;/li&gt;
&lt;li&gt;✔ Excellent UI&lt;/li&gt;
&lt;li&gt;✔ Powerful evaluation system&lt;/li&gt;
&lt;li&gt;✔ Enterprise-ready&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivers the most value when using the LangChain ecosystem&lt;/li&gt;
&lt;li&gt;Less framework-agnostic than some competitors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LangGraph agents&lt;/li&gt;
&lt;li&gt;Enterprise AI assistants&lt;/li&gt;
&lt;li&gt;Coding agents&lt;/li&gt;
&lt;li&gt;Research agents&lt;/li&gt;
&lt;li&gt;Production agent orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Portkey
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Organizations that need both an AI Gateway and observability platform.&lt;/p&gt;

&lt;p&gt;Portkey takes a different approach.&lt;/p&gt;

&lt;p&gt;Instead of focusing only on monitoring, it sits between your application and the LLM providers.&lt;/p&gt;

&lt;p&gt;Think of it as an API gateway designed specifically for AI.&lt;/p&gt;

&lt;p&gt;Every request flows through Portkey before reaching OpenAI, Anthropic, Gemini, Mistral, or another provider.&lt;/p&gt;

&lt;p&gt;This architecture unlocks capabilities beyond observability.&lt;/p&gt;

&lt;p&gt;Developers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route traffic between providers&lt;/li&gt;
&lt;li&gt;Retry failed requests&lt;/li&gt;
&lt;li&gt;Cache responses&lt;/li&gt;
&lt;li&gt;Apply guardrails&lt;/li&gt;
&lt;li&gt;Monitor costs&lt;/li&gt;
&lt;li&gt;Analyze latency&lt;/li&gt;
&lt;li&gt;Centralize logging&lt;/li&gt;
&lt;li&gt;Switch providers without changing application code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For larger organizations managing multiple models, this architecture can significantly simplify infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Multi-provider routing&lt;/li&gt;
&lt;li&gt;✔ Built-in observability&lt;/li&gt;
&lt;li&gt;✔ AI gateway capabilities&lt;/li&gt;
&lt;li&gt;✔ Cost optimization&lt;/li&gt;
&lt;li&gt;✔ Strong enterprise focus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More infrastructure to configure&lt;/li&gt;
&lt;li&gt;May be unnecessary for very small applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-model applications&lt;/li&gt;
&lt;li&gt;Enterprise AI platforms&lt;/li&gt;
&lt;li&gt;SaaS products&lt;/li&gt;
&lt;li&gt;High-volume API workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Braintrust
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Evaluating AI applications before they reach production.&lt;/p&gt;

&lt;p&gt;While many observability platforms focus on monitoring requests and debugging failures, Braintrust is built around a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do we know our AI application is actually getting better?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This makes Braintrust one of the strongest platforms for AI evaluation.&lt;/p&gt;

&lt;p&gt;Instead of simply collecting traces, it helps engineering teams measure whether changes to prompts, models, or workflows improve response quality.&lt;/p&gt;

&lt;p&gt;For companies shipping AI products every week, this capability is extremely valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt evaluation&lt;/li&gt;
&lt;li&gt;LLM-as-a-Judge&lt;/li&gt;
&lt;li&gt;Human evaluations&lt;/li&gt;
&lt;li&gt;Regression testing&lt;/li&gt;
&lt;li&gt;Experiment tracking&lt;/li&gt;
&lt;li&gt;Benchmark datasets&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Braintrust allows developers to compare prompt versions before deploying them.&lt;/p&gt;

&lt;p&gt;For example, after changing a system prompt, you can automatically test it against hundreds of predefined examples and compare the results with the previous version.&lt;/p&gt;

&lt;p&gt;This prevents unexpected regressions from reaching production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Excellent evaluation workflow&lt;/li&gt;
&lt;li&gt;✔ Designed for enterprise AI teams&lt;/li&gt;
&lt;li&gt;✔ Strong experiment management&lt;/li&gt;
&lt;li&gt;✔ Easy A/B testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less focused on operational monitoring&lt;/li&gt;
&lt;li&gt;Better suited for mature AI products than small prototypes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI copilots&lt;/li&gt;
&lt;li&gt;Customer support assistants&lt;/li&gt;
&lt;li&gt;Enterprise chatbots&lt;/li&gt;
&lt;li&gt;Document understanding&lt;/li&gt;
&lt;li&gt;AI products with continuous releases
## 4. Braintrust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Evaluating AI applications before they reach production.&lt;/p&gt;

&lt;p&gt;While many observability platforms focus on monitoring requests and debugging failures, Braintrust is built around a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do we know our AI application is actually getting better?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This makes Braintrust one of the strongest platforms for AI evaluation.&lt;/p&gt;

&lt;p&gt;Instead of simply collecting traces, it helps engineering teams measure whether changes to prompts, models, or workflows improve response quality.&lt;/p&gt;

&lt;p&gt;For companies shipping AI products every week, this capability is extremely valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt evaluation&lt;/li&gt;
&lt;li&gt;LLM-as-a-Judge&lt;/li&gt;
&lt;li&gt;Human evaluations&lt;/li&gt;
&lt;li&gt;Regression testing&lt;/li&gt;
&lt;li&gt;Experiment tracking&lt;/li&gt;
&lt;li&gt;Benchmark datasets&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Braintrust allows developers to compare prompt versions before deploying them.&lt;/p&gt;

&lt;p&gt;For example, after changing a system prompt, you can automatically test it against hundreds of predefined examples and compare the results with the previous version.&lt;/p&gt;

&lt;p&gt;This prevents unexpected regressions from reaching production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Excellent evaluation workflow&lt;/li&gt;
&lt;li&gt;✔ Designed for enterprise AI teams&lt;/li&gt;
&lt;li&gt;✔ Strong experiment management&lt;/li&gt;
&lt;li&gt;✔ Easy A/B testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less focused on operational monitoring&lt;/li&gt;
&lt;li&gt;Better suited for mature AI products than small prototypes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI copilots&lt;/li&gt;
&lt;li&gt;Customer support assistants&lt;/li&gt;
&lt;li&gt;Enterprise chatbots&lt;/li&gt;
&lt;li&gt;Document understanding&lt;/li&gt;
&lt;li&gt;AI products with continuous releases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Arize Phoenix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Advanced AI and ML observability.&lt;/p&gt;

&lt;p&gt;Arize AI has been a leader in machine learning observability for several years.&lt;/p&gt;

&lt;p&gt;As generative AI adoption accelerated, the company introduced Phoenix, an open-source platform focused on LLM applications.&lt;/p&gt;

&lt;p&gt;Phoenix combines traditional ML monitoring with AI-specific capabilities such as prompt tracing and hallucination analysis.&lt;/p&gt;

&lt;p&gt;For organizations already operating ML pipelines, this makes Arize a natural evolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end tracing&lt;/li&gt;
&lt;li&gt;Hallucination analysis&lt;/li&gt;
&lt;li&gt;Root cause investigation&lt;/li&gt;
&lt;li&gt;Embedding visualization&lt;/li&gt;
&lt;li&gt;Retrieval quality analysis&lt;/li&gt;
&lt;li&gt;Prompt inspection&lt;/li&gt;
&lt;li&gt;OpenTelemetry support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One particularly interesting capability is analyzing retrieval quality in RAG systems.&lt;/p&gt;

&lt;p&gt;Instead of simply inspecting prompts, Phoenix helps identify whether poor answers were caused by weak retrieval rather than the language model itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Excellent for RAG systems&lt;/li&gt;
&lt;li&gt;✔ Strong open-source offering&lt;/li&gt;
&lt;li&gt;✔ Built by an experienced ML company&lt;/li&gt;
&lt;li&gt;✔ Enterprise-grade analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More advanced than many teams require&lt;/li&gt;
&lt;li&gt;Learning curve is steeper&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAG applications&lt;/li&gt;
&lt;li&gt;Enterprise search&lt;/li&gt;
&lt;li&gt;AI knowledge bases&lt;/li&gt;
&lt;li&gt;Internal copilots&lt;/li&gt;
&lt;li&gt;Large production systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Humanloop
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Prompt management and collaborative AI development.&lt;/p&gt;

&lt;p&gt;Humanloop sits somewhere between an observability platform and a prompt engineering workspace.&lt;/p&gt;

&lt;p&gt;Its philosophy is that AI applications should be developed in the same structured way as software.&lt;/p&gt;

&lt;p&gt;Instead of editing prompts directly inside source code, teams can version, test, review, and evaluate prompts collaboratively.&lt;/p&gt;

&lt;p&gt;For product teams working closely with engineers, this approach can significantly speed up iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt management&lt;/li&gt;
&lt;li&gt;Version control&lt;/li&gt;
&lt;li&gt;Prompt playground&lt;/li&gt;
&lt;li&gt;Automated evaluations&lt;/li&gt;
&lt;li&gt;Human feedback&lt;/li&gt;
&lt;li&gt;Experiment tracking&lt;/li&gt;
&lt;li&gt;Team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Humanloop also makes it easier for non-engineering stakeholders to participate in prompt development without modifying application code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Excellent prompt workflow&lt;/li&gt;
&lt;li&gt;✔ Clean interface&lt;/li&gt;
&lt;li&gt;✔ Strong collaboration features&lt;/li&gt;
&lt;li&gt;✔ Easy experimentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less focused on infrastructure monitoring&lt;/li&gt;
&lt;li&gt;More valuable for larger teams than solo developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product teams&lt;/li&gt;
&lt;li&gt;AI startups&lt;/li&gt;
&lt;li&gt;Internal copilots&lt;/li&gt;
&lt;li&gt;Prompt engineering&lt;/li&gt;
&lt;li&gt;Customer support AI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Helicone
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Fast and simple LLM observability.&lt;/p&gt;

&lt;p&gt;Helicone has one of the simplest onboarding experiences in the industry.&lt;/p&gt;

&lt;p&gt;In many cases, developers only need to change the API endpoint used by their application.&lt;/p&gt;

&lt;p&gt;Immediately afterwards they gain access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request logs&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Costs&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;User analytics&lt;/li&gt;
&lt;li&gt;Model comparisons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This simplicity makes Helicone particularly attractive for startups and individual developers.&lt;/p&gt;

&lt;p&gt;Instead of spending hours configuring infrastructure, teams can begin monitoring their AI applications almost immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API proxy&lt;/li&gt;
&lt;li&gt;Cost analytics&lt;/li&gt;
&lt;li&gt;Token tracking&lt;/li&gt;
&lt;li&gt;Latency monitoring&lt;/li&gt;
&lt;li&gt;User analytics&lt;/li&gt;
&lt;li&gt;Request history&lt;/li&gt;
&lt;li&gt;Prompt inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Extremely easy to deploy&lt;/li&gt;
&lt;li&gt;✔ Very developer friendly&lt;/li&gt;
&lt;li&gt;✔ Excellent analytics&lt;/li&gt;
&lt;li&gt;✔ Minimal setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller feature set than enterprise platforms&lt;/li&gt;
&lt;li&gt;Less comprehensive evaluation tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MVPs&lt;/li&gt;
&lt;li&gt;SaaS startups&lt;/li&gt;
&lt;li&gt;Internal tools&lt;/li&gt;
&lt;li&gt;Developer projects&lt;/li&gt;
&lt;li&gt;AI prototypes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which Platform Should You Choose?
&lt;/h2&gt;

&lt;p&gt;The answer depends entirely on your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you're building your first AI application
&lt;/h3&gt;

&lt;p&gt;Start with Helicone.&lt;/p&gt;

&lt;p&gt;It provides immediate visibility into costs, latency, and requests without requiring significant infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you prefer open-source software
&lt;/h3&gt;

&lt;p&gt;Langfuse is currently one of the strongest choices.&lt;/p&gt;

&lt;p&gt;It combines production-ready observability with the flexibility of self-hosting.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you're building AI agents with LangGraph
&lt;/h3&gt;

&lt;p&gt;Choose LangSmith.&lt;/p&gt;

&lt;p&gt;Its deep integration with the LangChain ecosystem makes debugging significantly easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you use multiple LLM providers
&lt;/h3&gt;

&lt;p&gt;Portkey is an excellent choice.&lt;/p&gt;

&lt;p&gt;Its gateway architecture simplifies routing, failover, and centralized monitoring across different models.&lt;/p&gt;

&lt;h3&gt;
  
  
  If evaluation is your biggest challenge
&lt;/h3&gt;

&lt;p&gt;Choose Braintrust or Humanloop.&lt;/p&gt;

&lt;p&gt;Both platforms excel at measuring response quality and comparing prompt versions before deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you're operating enterprise-scale AI systems
&lt;/h3&gt;

&lt;p&gt;Arize Phoenix provides some of the most advanced observability capabilities available today, particularly for RAG applications and production environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Comparison Table
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Open Source&lt;/th&gt;
&lt;th&gt;Tracing&lt;/th&gt;
&lt;th&gt;Evaluations&lt;/th&gt;
&lt;th&gt;Prompt Mgmt&lt;/th&gt;
&lt;th&gt;AI Gateway&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Open-source production AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;LangGraph &amp;amp; AI Agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portkey&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Multi-LLM Infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Braintrust&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;AI Evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arize Phoenix&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Enterprise AI &amp;amp; RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humanloop&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Prompt Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;⭐⭐☆☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐☆☆☆&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆&lt;/td&gt;
&lt;td&gt;Startups &amp;amp; MVPs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  FAQs
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is LLM observability?
&lt;/h2&gt;

&lt;p&gt;It is the practice of monitoring prompts, traces, token usage, costs, latency and evaluations for production AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which platform should I choose?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Langfuse → Open source&lt;/li&gt;
&lt;li&gt;LangSmith → LangGraph&lt;/li&gt;
&lt;li&gt;Portkey → Multi-LLM&lt;/li&gt;
&lt;li&gt;Braintrust → Evaluation&lt;/li&gt;
&lt;li&gt;Arize → Enterprise&lt;/li&gt;
&lt;li&gt;Humanloop → Prompt engineering&lt;/li&gt;
&lt;li&gt;Helicone → MVPs&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Observability is becoming as important for AI applications as logging is for traditional software. The right platform helps reduce costs, improve reliability and accelerate debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Work Together
&lt;/h2&gt;

&lt;p&gt;Looking for high-quality technical content for your AI, API, or developer tool?&lt;/p&gt;

&lt;p&gt;📧 &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💼 &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/kevin-meneses-gonzalez/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌐 &lt;a href="https://kevinmeneses.com" rel="noopener noreferrer"&gt;https://kevinmeneses.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm always open to new collaborations, sponsored content and developer marketing partnerships.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>analytics</category>
    </item>
    <item>
      <title>5 Strapi Alternatives Developers Are Actually Switching To in 2026</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 14 Jul 2026 16:30:48 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/5-strapi-alternatives-developers-are-actually-switching-to-in-2026-54jj</link>
      <guid>https://dev.to/kevin_menesesgonzlez/5-strapi-alternatives-developers-are-actually-switching-to-in-2026-54jj</guid>
      <description>&lt;p&gt;For years, Strapi was the answer.&lt;/p&gt;

&lt;p&gt;Need a headless CMS? Open source, self-hosted, developer-first. Done.&lt;/p&gt;

&lt;p&gt;That's not true anymore. In 2026, developers are switching away from &lt;a href="https://strapi.io" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; at a pace that's hard to ignore — not because it got worse, but because the alternatives got dramatically better.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;migrating a content-heavy Next.js app,&lt;/li&gt;
&lt;li&gt;choosing infrastructure for a new product,&lt;/li&gt;
&lt;li&gt;or just tired of patching Node dependencies every quarter,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost Nobody Puts in the Pricing Table
&lt;/h2&gt;

&lt;p&gt;Strapi's pitch has always been simple: it's free, it's open source, you own your data.&lt;/p&gt;

&lt;p&gt;All true. None of it accounts for what happens after deployment.&lt;/p&gt;

&lt;p&gt;Someone has to manage the server. Apply security patches. Handle database migrations when a plugin update breaks your schema. Babysit uptime at 3am when the content API goes down mid-launch.&lt;/p&gt;

&lt;p&gt;That someone is usually a developer who'd rather be shipping features.&lt;/p&gt;

&lt;p&gt;Teams call this "Type 1 fun" — technically satisfying, but a distraction from the product you're actually trying to build. Strapi Cloud exists specifically to remove this burden.&lt;/p&gt;

&lt;p&gt;It also adds a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Isn't Strapi. It's the Definition of "Headless."
&lt;/h2&gt;

&lt;p&gt;For a long time, "headless CMS" meant one thing: a content repository with an API on top.&lt;/p&gt;

&lt;p&gt;That bar is too low for 2026.&lt;/p&gt;

&lt;p&gt;Content today needs to move across web, mobile, and increasingly AI-driven interfaces — chatbots, agents, voice assistants. A CMS that only stores text and serves it over REST doesn't cut it when your product needs structured, machine-readable content that an LLM can reason over.&lt;/p&gt;

&lt;p&gt;The platforms winning in 2026 aren't just "headless." They handle content operations — versioning, AI-assisted authoring, translation pipelines, and increasingly, native support for AI agents and MCP.&lt;/p&gt;

&lt;p&gt;That's the real reason developers are re-evaluating their stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look for Before You Switch
&lt;/h2&gt;

&lt;p&gt;Before comparing tools, three questions actually matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework fit.&lt;/strong&gt; Are you on Next.js, Nuxt, Astro, SvelteKit? Most CMS platforms support all of them via REST, but GraphQL support narrows the field fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;True cost at scale.&lt;/strong&gt; Free tiers are easy to compare. What you'll pay at 12–18 months of real usage — seats, API limits, bandwidth — is the number that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data portability.&lt;/strong&gt; Can you get your content out in a standard format if you ever need to migrate again? Some platforms make this trivial. Others make it a project.&lt;/p&gt;

&lt;p&gt;With that in mind, here are the five alternatives worth your evaluation time.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;a href="https://payloadcms.com" rel="noopener noreferrer"&gt;Payload&lt;/a&gt; — Best for Next.js and TypeScript-First Teams
&lt;/h2&gt;

&lt;p&gt;Payload is the CMS that made the biggest jump in 2026. Version 3.0 cut its dependency count from 88 to 27, and its local API means content queries run in-process — no network round trip, no rate limits to worry about.&lt;/p&gt;

&lt;p&gt;The schema-as-code approach feels familiar if you've used Sanity, but the TypeScript integration goes further: content types generate full types automatically, so there's no &lt;code&gt;any&lt;/code&gt; silently leaking through your codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local API eliminates network latency for server-side queries&lt;/li&gt;
&lt;li&gt;Auto-generated TypeScript types from your schema&lt;/li&gt;
&lt;li&gt;Now framework-agnostic — works with Remix, Astro, SvelteKit, not just Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Younger ecosystem than Strapi's — fewer plugins, fewer Stack Overflow answers&lt;/li&gt;
&lt;li&gt;Smaller community means you'll solve more edge cases yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams deeply committed to Next.js and TypeScript who want to own their CMS implementation completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;a href="https://www.sanity.io" rel="noopener noreferrer"&gt;Sanity&lt;/a&gt; — Best for Content Operations and Multilingual Projects
&lt;/h2&gt;

&lt;p&gt;Sanity has evolved past the traditional definition of headless CMS. It's positioning itself as a full content operating system — structured content that plugs directly into AI and automation workflows, not just a delivery API.&lt;/p&gt;

&lt;p&gt;Teams running large multilingual content pipelines (10+ languages, document-level i18n) consistently point to Sanity's structured content tooling as the deciding factor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strongest developer experience for structured, queryable content (GROQ)&lt;/li&gt;
&lt;li&gt;Excellent Next.js integration with Visual Editing and live preview&lt;/li&gt;
&lt;li&gt;Handles document-level internationalization cleanly at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Steeper learning curve for teams new to structured content modeling&lt;/li&gt;
&lt;li&gt;Pricing can climb fast once you exceed generous free-tier limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Engineering-led teams running multilingual content operations who want AI-ready structured data, not just a content store.&lt;/p&gt;

&lt;blockquote&gt;
&lt;strong&gt;Looking for technical content that actually explains your product?&lt;/strong&gt;&lt;br&gt;
I write developer-first articles and tutorials for API and infrastructure companies — the kind engineers bookmark instead of skip.&lt;br&gt;
→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Let's talk on LinkedIn&lt;/a&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. &lt;a href="https://directus.io" rel="noopener noreferrer"&gt;Directus&lt;/a&gt; — Best for Wrapping an Existing Database
&lt;/h2&gt;

&lt;p&gt;Directus takes a different approach entirely: instead of designing a new database for your content, it sits on top of the one you already have.&lt;/p&gt;

&lt;p&gt;That single decision makes it the strongest pick for teams with legacy data models who don't want to migrate everything into a new schema just to get a CMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds a CMS layer to an existing SQL database without disrupting the schema&lt;/li&gt;
&lt;li&gt;AI features aren't locked behind an enterprise tier — bring your own API key&lt;/li&gt;
&lt;li&gt;Generous free tier compared to most open-source competitors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less opinionated structure means more setup decisions land on you&lt;/li&gt;
&lt;li&gt;Custom AI logic still requires building your own plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that already have a database and need a CMS layer without redesigning their data model.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;a href="https://hygraph.com" rel="noopener noreferrer"&gt;Hygraph&lt;/a&gt; — Best for GraphQL-Native Architectures
&lt;/h2&gt;

&lt;p&gt;Hygraph (formerly GraphCMS) is built entirely around GraphQL, not bolted onto REST as an afterthought. For teams working with Next.js, React, or Svelte where efficient data fetching matters, this changes the developer experience meaningfully.&lt;/p&gt;

&lt;p&gt;Its standout feature is content federation — pulling data from multiple CMS instances, APIs, and databases into a single GraphQL layer. That's a specific use case, but it's one most competitors don't touch at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GraphQL-first design produces cleaner queries and more predictable data shapes&lt;/li&gt;
&lt;li&gt;Content federation across multiple sources in one layer&lt;/li&gt;
&lt;li&gt;Strong role-based permissions for larger teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overkill if your project doesn't need GraphQL or multi-source federation&lt;/li&gt;
&lt;li&gt;Smaller plugin ecosystem than Strapi or Sanity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Composable architectures pulling content from multiple sources into a single, unified API.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;a href="https://nomacms.com" rel="noopener noreferrer"&gt;NomaCMS&lt;/a&gt; — Best for AI-Native, Zero-Infrastructure Teams
&lt;/h2&gt;

&lt;p&gt;NomaCMS is the newest name on this list, and the one built specifically for the AI-agent era. It's a fully managed, AI-native headless CMS — REST API, JavaScript SDK, and built-in AI tools for writing, translation, and editing, with content and assets served over a global CDN.&lt;/p&gt;

&lt;p&gt;You run no CMS server at all. It also ships with official MCP support, meaning tools like Claude Code and Cursor can interact with your content model directly — a detail that matters if your workflow already leans on AI agents for content operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero server management — content, assets, and CDN handled entirely by the platform&lt;/li&gt;
&lt;li&gt;Native MCP support for AI coding assistants&lt;/li&gt;
&lt;li&gt;Fast to start: 7-day free trial, plans from $15/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Newer platform — smaller track record than established players&lt;/li&gt;
&lt;li&gt;Less customization depth than a self-hosted, code-first CMS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Small teams and solo developers who want Strapi-like structured content without running Node infrastructure, especially if AI tooling is already part of the workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Look at Consuming a Modern CMS API
&lt;/h2&gt;

&lt;p&gt;Regardless of which platform you pick, the integration pattern looks similar. Here's a minimal example fetching structured content over REST:&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="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example-cms.com/v1/content/articles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&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;Authorization&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;Bearer YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="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="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;publishedAt&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Building a Faster Checkout Flow - 2026-06-02
Why We Moved Off Strapi - 2026-05-14
Structured Content for AI Agents - 2026-04-28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a content sync pipeline into your own database&lt;/li&gt;
&lt;li&gt;an AI agent that queries your CMS directly via MCP&lt;/li&gt;
&lt;li&gt;automated translation workflows triggered on publish&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Strapi still works — but self-hosting only pays off when it's a genuine requirement, not a cost-saving assumption.&lt;/li&gt;
&lt;li&gt;The best pick depends on one variable more than any other: do you need code-first flexibility (Payload, Directus) or managed content operations (Sanity, NomaCMS)?&lt;/li&gt;
&lt;li&gt;AI-native support — MCP, agent access, structured data for LLMs — is quickly becoming a baseline expectation, not a bonus feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Is Strapi still a good choice in 2026?&lt;/strong&gt;&lt;br&gt;
✅ Yes, if self-hosting and full data ownership are non-negotiable requirements. For teams without strong DevOps capacity, the operational overhead often outweighs the savings from a free license.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the best headless CMS for a Next.js project?&lt;/strong&gt;&lt;br&gt;
✅ Payload and Sanity both offer deep Next.js integrations. Payload edges ahead for teams that want a TypeScript-first, code-driven workflow with no external CMS server to manage.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Do any of these support AI agents directly?&lt;/strong&gt;&lt;br&gt;
✅ NomaCMS ships with native MCP support, letting tools like Claude Code query and manage content directly. Directus and Payload support AI through custom plugins using your own API keys.&lt;/p&gt;




&lt;p&gt;The CMS market spent a decade converging on Strapi as the default.&lt;/p&gt;

&lt;p&gt;That convergence is over. Pick based on what your team actually needs to maintain — not which platform has the most GitHub stars.&lt;/p&gt;

&lt;p&gt;If you're building something and want a second opinion on your content infrastructure, or want your product explained through content developers actually read, you know where to find me: &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;kevinmeneses.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cms</category>
      <category>wordpress</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best Stock Market API for Python Developers (2026)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:17:45 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/best-stock-market-api-for-python-developers-2026-11h2</link>
      <guid>https://dev.to/kevin_menesesgonzlez/best-stock-market-api-for-python-developers-2026-11h2</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Scraping and unofficial wrappers like &lt;code&gt;yfinance&lt;/code&gt; break in production. For Python projects that need historical + real-time stock data without stitching together multiple providers, EODHD offers the widest coverage (150,000+ tickers, 70+ exchanges) under one API key. Massive is a strong alternative for low-latency US real-time feeds, Alpha Vantage works for prototyping, and &lt;code&gt;yfinance&lt;/code&gt; should stay limited to personal scripts.&lt;/p&gt;

&lt;p&gt;Many developers believe you need to pay hundreds of dollars a month for reliable stock market data in Python. That's not true.&lt;/p&gt;

&lt;p&gt;The real problem isn't price. It's picking the wrong API before you know what production actually demands.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a stock screener,&lt;/li&gt;
&lt;li&gt;backtesting a trading strategy,&lt;/li&gt;
&lt;li&gt;or adding market data to a fintech app,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this decision will follow you for months. Get it wrong and you rebuild the whole data layer later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scraping Works Until It Doesn't
&lt;/h2&gt;

&lt;p&gt;Most Python developers start the same way. &lt;code&gt;yfinance&lt;/code&gt;, a scraping script, maybe an unofficial endpoint someone shared on GitHub.&lt;/p&gt;

&lt;p&gt;It works. In local testing.&lt;/p&gt;

&lt;p&gt;Then it hits production.&lt;/p&gt;

&lt;p&gt;Rate limits appear out of nowhere. Endpoints change without warning. A field that returned a float last month now returns a string. Your script that ran perfectly on Tuesday throws a &lt;code&gt;KeyError&lt;/code&gt; on Wednesday.&lt;/p&gt;

&lt;p&gt;Developers often discover this too late — after building an entire pipeline around a source that was never meant to be an API in the first place.&lt;/p&gt;

&lt;p&gt;The symptoms are always the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silent failures during market hours&lt;/li&gt;
&lt;li&gt;Historical data with random gaps&lt;/li&gt;
&lt;li&gt;No SLA, no support, no changelog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a coding problem. It's an infrastructure problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Is Infrastructure, Not Data
&lt;/h2&gt;

&lt;p&gt;Stock data itself isn't scarce. Every exchange publishes it.&lt;/p&gt;

&lt;p&gt;The real problem is structure: getting that data through a stable, documented, rate-limit-transparent REST API instead of a scraper held together with &lt;code&gt;try/except&lt;/code&gt; blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Actually Look For
&lt;/h2&gt;

&lt;p&gt;Before picking any stock market API for a Python project, check for four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real REST API&lt;/strong&gt; — documented endpoints, not reverse-engineered JSON from a webpage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear rate limits&lt;/strong&gt; — published numbers, not "fair use" vagueness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both historical and real-time coverage&lt;/strong&gt; — screeners need history, alerts need live data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean JSON responses&lt;/strong&gt; — no HTML parsing, no regex, no fragile scraping logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After testing multiple providers for backtesting and screener projects, I consistently reach for &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=best-stock-market-api-python-developers&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD&lt;/a&gt; for this type of work. Here's why.&lt;/p&gt;

&lt;p&gt;EODHD covers over 150,000 tickers across 70+ exchanges, with end-of-day, intraday, and real-time endpoints under one API key. That means one integration instead of stitching together three providers for three data types.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting Stock Data in Python: A Working Example
&lt;/h2&gt;

&lt;p&gt;Let's pull historical daily prices for a single ticker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests pandas
&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&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;json&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;period&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;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;open&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;high&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;low&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;close&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;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;        &lt;span class="k"&gt;date&lt;/span&gt;    &lt;span class="k"&gt;open&lt;/span&gt;    &lt;span class="k"&gt;high&lt;/span&gt;     &lt;span class="k"&gt;low&lt;/span&gt;   &lt;span class="k"&gt;close&lt;/span&gt;    &lt;span class="k"&gt;volume&lt;/span&gt;
&lt;span class="mf"&gt;495&lt;/span&gt;  &lt;span class="ld"&gt;2026-06-24&lt;/span&gt;  &lt;span class="mf"&gt;198.31&lt;/span&gt;  &lt;span class="mf"&gt;200.12&lt;/span&gt;  &lt;span class="mf"&gt;197.85&lt;/span&gt;  &lt;span class="mf"&gt;199.40&lt;/span&gt;  &lt;span class="mf"&gt;48213500&lt;/span&gt;
&lt;span class="mf"&gt;496&lt;/span&gt;  &lt;span class="ld"&gt;2026-06-25&lt;/span&gt;  &lt;span class="mf"&gt;199.50&lt;/span&gt;  &lt;span class="mf"&gt;201.03&lt;/span&gt;  &lt;span class="mf"&gt;198.90&lt;/span&gt;  &lt;span class="mf"&gt;200.77&lt;/span&gt;  &lt;span class="mf"&gt;39871200&lt;/span&gt;
&lt;span class="mf"&gt;497&lt;/span&gt;  &lt;span class="ld"&gt;2026-06-26&lt;/span&gt;  &lt;span class="mf"&gt;200.90&lt;/span&gt;  &lt;span class="mf"&gt;202.44&lt;/span&gt;  &lt;span class="mf"&gt;199.75&lt;/span&gt;  &lt;span class="mf"&gt;201.15&lt;/span&gt;  &lt;span class="mf"&gt;41205300&lt;/span&gt;
&lt;span class="mf"&gt;498&lt;/span&gt;  &lt;span class="ld"&gt;2026-06-29&lt;/span&gt;  &lt;span class="mf"&gt;201.20&lt;/span&gt;  &lt;span class="mf"&gt;203.01&lt;/span&gt;  &lt;span class="mf"&gt;200.44&lt;/span&gt;  &lt;span class="mf"&gt;202.63&lt;/span&gt;  &lt;span class="mf"&gt;36994800&lt;/span&gt;
&lt;span class="mf"&gt;499&lt;/span&gt;  &lt;span class="ld"&gt;2026-06-30&lt;/span&gt;  &lt;span class="mf"&gt;202.70&lt;/span&gt;  &lt;span class="mf"&gt;204.15&lt;/span&gt;  &lt;span class="mf"&gt;201.98&lt;/span&gt;  &lt;span class="mf"&gt;203.29&lt;/span&gt;  &lt;span class="mf"&gt;44012700&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stock screener that filters by volume or volatility&lt;/li&gt;
&lt;li&gt;a backtesting engine for a trading strategy&lt;/li&gt;
&lt;li&gt;a real-time alert system on top of the same API key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No scraper to maintain. No parsing HTML. Just a request and a DataFrame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the Top Stock Market APIs for Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. EODHD — Broad coverage, one API for everything
&lt;/h3&gt;

&lt;p&gt;Covers historical, real-time, and fundamental data across global exchanges through a single REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;150,000+ tickers across 70+ exchanges&lt;/li&gt;
&lt;li&gt;Historical, real-time, and fundamentals in one API key&lt;/li&gt;
&lt;li&gt;Free tier available for testing before committing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time data has a short delay on the lowest-tier plans&lt;/li&gt;
&lt;li&gt;Some fundamental endpoints require a paid plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who need historical + real-time + fundamentals without juggling three providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Alpha Vantage — Good for prototyping
&lt;/h3&gt;

&lt;p&gt;A free-tier-first API popular for quick prototypes and learning projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generous free tier for low-volume testing&lt;/li&gt;
&lt;li&gt;Well-documented technical indicator endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate limits are restrictive (5 calls/minute on the free tier)&lt;/li&gt;
&lt;li&gt;Real-time data requires a premium plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; early-stage prototypes and academic projects, not production systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Massive — Strong for US real-time data
&lt;/h3&gt;

&lt;p&gt;Focused heavily on US equities and options with fast real-time feeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-latency real-time data for US markets&lt;/li&gt;
&lt;li&gt;WebSocket support for live streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited international exchange coverage&lt;/li&gt;
&lt;li&gt;Pricier at the tiers where real-time data actually becomes usable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; US-focused trading applications that need speed over breadth (formerly Polygon.io, now rebranded as Massive).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. yfinance — Fine for personal projects, risky for production
&lt;/h3&gt;

&lt;p&gt;An unofficial wrapper around Yahoo Finance's internal endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free and instantly usable&lt;/li&gt;
&lt;li&gt;No API key required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not an official API — Yahoo can change the underlying structure anytime&lt;/li&gt;
&lt;li&gt;No SLA, no support, frequent silent breakages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; personal scripts and one-off analysis, never production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  How These Fit Together
&lt;/h2&gt;

&lt;p&gt;If you're testing an idea over a weekend, &lt;code&gt;yfinance&lt;/code&gt; is fine.&lt;/p&gt;

&lt;p&gt;The moment that idea becomes a screener, a dashboard, or anything a client depends on, move to a documented REST API.&lt;/p&gt;

&lt;p&gt;EODHD covers the widest range of use cases — historical, real-time, and fundamentals — without forcing you to combine multiple providers just to cover the basics.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A stock market API is infrastructure, not just a data source — treat the decision like one&lt;/li&gt;
&lt;li&gt;Documented REST endpoints beat scraping every time production matters&lt;/li&gt;
&lt;li&gt;Free tiers are enough to validate the integration before paying for anything&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ Is there a free stock market API for Python?&lt;br&gt;
✅ Yes. EODHD, Alpha Vantage, and yfinance all offer free access. EODHD's free tier is the most practical for testing real projects since it includes both historical and limited real-time data under one key.&lt;/p&gt;

&lt;p&gt;❓ What's the best stock API for real-time data?&lt;br&gt;
✅ For US-only real-time feeds, Massive is strong. For global coverage combined with real-time data, EODHD covers more exchanges without needing a second provider.&lt;/p&gt;

&lt;p&gt;❓ Can I use yfinance in a production app?&lt;br&gt;
✅ Not recommended. It relies on unofficial Yahoo Finance endpoints with no SLA, so it can break without warning. Use it for prototypes only.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=best-stock-market-api-python-developers&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get started with EODHD&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Historical + real-time data across 70+ exchanges&lt;/li&gt;
&lt;li&gt;A free tier to test before committing&lt;/li&gt;
&lt;li&gt;Fundamentals and technical indicators under the same key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real question isn't which API has the most features. It's which one you can still trust in six months.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>stocks</category>
      <category>python</category>
      <category>api</category>
      <category>data</category>
    </item>
    <item>
      <title>I Let Claude Screen 3,000 Stocks Using the EODHD API — Here's What Made the Cut</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Fri, 10 Jul 2026 07:10:00 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/i-let-claude-screen-3000-stocks-using-the-eodhd-api-heres-what-made-the-cut-2eap</link>
      <guid>https://dev.to/kevin_menesesgonzlez/i-let-claude-screen-3000-stocks-using-the-eodhd-api-heres-what-made-the-cut-2eap</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built a 4-stage screening funnel on top of the EODHD Screener API, starting from a universe of 3,000+ US-listed stocks with a market cap above $150M. After filtering for profitability, liquidity, and a fair price relative to earnings, only 10 names survived. No hype, no "buy this now." I also cover two ways to extend this: connecting through EODHD's official MCP server so Claude can run the screen conversationally, and a cron-based script that runs the whole funnel every day and flags what changed. Full list, code, and setup below.&lt;/p&gt;




&lt;p&gt;Most "AI picks stocks" content is theater.&lt;/p&gt;

&lt;p&gt;Someone asks ChatGPT to "recommend 5 undervalued stocks," the model pulls from stale training data, and the article calls it analysis. No live data. No verifiable filter. No way for the reader to reproduce a single number.&lt;/p&gt;

&lt;p&gt;I wanted to do the opposite.&lt;/p&gt;

&lt;p&gt;So I built a real screening pipeline: Claude connected directly to the EODHD Stock Screener API, ran a live query against thousands of tickers, and narrowed them down using nothing but hard numeric filters. Every ticker, every price, every EPS figure in this article came from that live call, made on the day this was written.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a quant screening tool,&lt;/li&gt;
&lt;li&gt;evaluating financial data APIs for an AI agent,&lt;/li&gt;
&lt;li&gt;or just tired of "AI stock picks" content with zero data behind it,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Most Stock Screeners
&lt;/h2&gt;

&lt;p&gt;Free screeners on the big finance sites give you a handful of filters: market cap, sector, maybe a P/E range. Fine for a Sunday afternoon. Useless if you're trying to build something programmatic.&lt;/p&gt;

&lt;p&gt;Paid terminals solve the flexibility problem but cost more than most independent investors are willing to spend just to test an idea.&lt;/p&gt;

&lt;p&gt;And "AI-powered" stock tools usually skip the data problem entirely. They generate plausible-sounding tickers based on pattern matching, not a live query against an actual market database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real bottleneck isn't intelligence. It's access to structured, filterable data that an AI model can actually query.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving Claude a Real Data Source
&lt;/h2&gt;

&lt;p&gt;EODHD's Screener API exposes exactly what a filtering pipeline needs: market capitalization, EPS, dividend yield, trading volume, sector, and price, across every ticker on the exchange, in one request.&lt;/p&gt;

&lt;p&gt;Once Claude can call that endpoint directly, "screen the market" stops being a metaphor. It becomes a real, auditable sequence of API calls.&lt;/p&gt;

&lt;p&gt;Here's the funnel I ran:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stage 1 — Universe:&lt;/strong&gt; US-listed common stocks with market cap &amp;gt; $150M&lt;br&gt;
&lt;strong&gt;Stage 2 — Quality filter:&lt;/strong&gt; profitable (positive EPS), liquid (200K+ average daily volume), priced above $5&lt;br&gt;
&lt;strong&gt;Stage 3 — Value zone:&lt;/strong&gt; market cap between $1B and $50B, dividend-paying, still liquid&lt;br&gt;
&lt;strong&gt;Stage 4 — Ranking:&lt;/strong&gt; sorted by earnings yield (EPS ÷ price) — the cheapest stocks relative to what they actually earn&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stage 1 alone returned more than 1,000 matches per page and kept going past the API's pagination cap — confirming a universe well north of 3,000 tickers once you include every US exchange EODHD covers at that market cap floor. From there, each stage cuts the field down hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Screener
&lt;/h2&gt;

&lt;p&gt;The setup is a single authenticated GET request per stage. No scraping, no manual downloads.&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;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/screener&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization.desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&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="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Stage 2 + 3 combined: profitable, liquid, mid/large-cap, dividend-paying
&lt;/span&gt;&lt;span class="n"&gt;filters&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;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,1000000000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,50000000000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_share&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,0],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avgvol_1d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,300000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,5],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dividend_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,0]]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_share&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;implied_pe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;top10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top10&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sector&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;adjusted_close&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;earnings_share&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;earnings_yield&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;dividend_yield&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;market_capitalization&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;That's the whole pipeline. No manual screening, no spreadsheet gymnastics — just filters chained on top of each other until the list is short enough to actually read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Made the Cut
&lt;/h2&gt;

&lt;p&gt;Out of the 3,000+ stock universe, these 10 names survived every filter: profitable, liquid, mid-to-large cap, and cheapest relative to their own earnings on the day of the screen.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ticker&lt;/th&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Sector&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;EPS&lt;/th&gt;
&lt;th&gt;Earnings Yield&lt;/th&gt;
&lt;th&gt;Implied P/E&lt;/th&gt;
&lt;th&gt;Dividend Yield&lt;/th&gt;
&lt;th&gt;Market Cap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JBS&lt;/td&gt;
&lt;td&gt;JBS N.V.&lt;/td&gt;
&lt;td&gt;Consumer Defensive (Packaged Foods)&lt;/td&gt;
&lt;td&gt;$11.82&lt;/td&gt;
&lt;td&gt;$1.62&lt;/td&gt;
&lt;td&gt;13.7%&lt;/td&gt;
&lt;td&gt;~7.3x&lt;/td&gt;
&lt;td&gt;8.2%&lt;/td&gt;
&lt;td&gt;$40.1B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EIX&lt;/td&gt;
&lt;td&gt;Edison International&lt;/td&gt;
&lt;td&gt;Utilities (Regulated Electric)&lt;/td&gt;
&lt;td&gt;$74.78&lt;/td&gt;
&lt;td&gt;$9.20&lt;/td&gt;
&lt;td&gt;12.3%&lt;/td&gt;
&lt;td&gt;~8.1x&lt;/td&gt;
&lt;td&gt;4.5%&lt;/td&gt;
&lt;td&gt;$28.8B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GFI&lt;/td&gt;
&lt;td&gt;Gold Fields Ltd ADR&lt;/td&gt;
&lt;td&gt;Basic Materials (Gold)&lt;/td&gt;
&lt;td&gt;$32.88&lt;/td&gt;
&lt;td&gt;$3.94&lt;/td&gt;
&lt;td&gt;12.0%&lt;/td&gt;
&lt;td&gt;~8.3x&lt;/td&gt;
&lt;td&gt;6.9%&lt;/td&gt;
&lt;td&gt;$30.0B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PYPL&lt;/td&gt;
&lt;td&gt;PayPal Holdings&lt;/td&gt;
&lt;td&gt;Financial Services (Credit Services)&lt;/td&gt;
&lt;td&gt;$44.53&lt;/td&gt;
&lt;td&gt;$5.33&lt;/td&gt;
&lt;td&gt;12.0%&lt;/td&gt;
&lt;td&gt;~8.4x&lt;/td&gt;
&lt;td&gt;0.9%&lt;/td&gt;
&lt;td&gt;$40.3B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VICI&lt;/td&gt;
&lt;td&gt;VICI Properties&lt;/td&gt;
&lt;td&gt;Real Estate (REIT – Gaming)&lt;/td&gt;
&lt;td&gt;$26.09&lt;/td&gt;
&lt;td&gt;$2.92&lt;/td&gt;
&lt;td&gt;11.2%&lt;/td&gt;
&lt;td&gt;~8.9x&lt;/td&gt;
&lt;td&gt;6.7%&lt;/td&gt;
&lt;td&gt;$29.5B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUK&lt;/td&gt;
&lt;td&gt;Prudential PLC ADR&lt;/td&gt;
&lt;td&gt;Financial Services (Life Insurance)&lt;/td&gt;
&lt;td&gt;$27.36&lt;/td&gt;
&lt;td&gt;$3.02&lt;/td&gt;
&lt;td&gt;11.0%&lt;/td&gt;
&lt;td&gt;~9.1x&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;td&gt;$34.5B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIG&lt;/td&gt;
&lt;td&gt;Hartford Financial&lt;/td&gt;
&lt;td&gt;Financial Services (Insurance)&lt;/td&gt;
&lt;td&gt;$138.74&lt;/td&gt;
&lt;td&gt;$14.12&lt;/td&gt;
&lt;td&gt;10.2%&lt;/td&gt;
&lt;td&gt;~9.8x&lt;/td&gt;
&lt;td&gt;1.6%&lt;/td&gt;
&lt;td&gt;$38.0B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EQT&lt;/td&gt;
&lt;td&gt;EQT Corporation&lt;/td&gt;
&lt;td&gt;Energy (Oil &amp;amp; Gas E&amp;amp;P)&lt;/td&gt;
&lt;td&gt;$51.16&lt;/td&gt;
&lt;td&gt;$5.21&lt;/td&gt;
&lt;td&gt;10.2%&lt;/td&gt;
&lt;td&gt;~9.8x&lt;/td&gt;
&lt;td&gt;1.3%&lt;/td&gt;
&lt;td&gt;$32.0B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC&lt;/td&gt;
&lt;td&gt;Ecopetrol SA ADR&lt;/td&gt;
&lt;td&gt;Energy (Oil &amp;amp; Gas Integrated)&lt;/td&gt;
&lt;td&gt;$15.13&lt;/td&gt;
&lt;td&gt;$1.40&lt;/td&gt;
&lt;td&gt;9.3%&lt;/td&gt;
&lt;td&gt;~10.8x&lt;/td&gt;
&lt;td&gt;4.4%&lt;/td&gt;
&lt;td&gt;$30.2B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KB&lt;/td&gt;
&lt;td&gt;KB Financial Group&lt;/td&gt;
&lt;td&gt;Financial Services (Banks – Regional)&lt;/td&gt;
&lt;td&gt;$116.74&lt;/td&gt;
&lt;td&gt;$10.40&lt;/td&gt;
&lt;td&gt;8.9%&lt;/td&gt;
&lt;td&gt;~11.2x&lt;/td&gt;
&lt;td&gt;2.7%&lt;/td&gt;
&lt;td&gt;$41.0B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things jump out.&lt;/p&gt;

&lt;p&gt;No mega-cap tech. Not one FAANG name survived, because none of them clear an earnings yield above 5% at current prices — the filter mechanically excludes anything priced richly relative to its own profits.&lt;/p&gt;

&lt;p&gt;Heavy tilt toward energy, financials, and insurance. That's not a bias I introduced. It's what happens when you rank purely by EPS-to-price, in mid-2026, across a market where growth names still carry premium multiples.&lt;/p&gt;

&lt;p&gt;Real geographic spread. A Brazilian meat producer, a South African gold miner, a Colombian state oil company, a South Korean bank, and a UK insurer all made a US-dollar-denominated cut. The screener doesn't care about domicile — it cares about the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;None of this is a buy recommendation.&lt;/strong&gt; Cheap relative to earnings doesn't mean safe, and several of these names (EIX, in particular, given wildfire litigation exposure; JBS, given historical governance concerns) carry known company-specific risk that a pure numeric filter can't see. This is a starting list for further research, not a portfolio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skipping the Code: EODHD's Official MCP Server
&lt;/h2&gt;

&lt;p&gt;Everything above ran through raw HTTP calls, which is the right approach when you're building something you'll productionize. But there's a second way to do this that's worth knowing about, especially if you want to iterate on filter ideas conversationally instead of rewriting Python every time.&lt;/p&gt;

&lt;p&gt;EODHD ships an official &lt;strong&gt;Model Context Protocol (MCP) server&lt;/strong&gt; — a standard that lets AI clients like Claude Desktop, Claude Code, and ChatGPT call external tools directly, without you writing the request-handling code yourself. Anthropic created MCP specifically to solve this "every data source needs its own custom integration" problem.&lt;/p&gt;

&lt;p&gt;EODHD's server exposes &lt;strong&gt;70+ read-only tools&lt;/strong&gt; covering the same ground as the REST API: the screener, fundamentals, historical and intraday prices, news and sentiment, technical indicators, macro indicators, US options, and more. It comes in two flavors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v2 (OAuth)&lt;/strong&gt; — the simpler setup for Claude Desktop. You paste the server URL into Settings → Extensions, authorize through a consent flow, and you're done. No API key handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1 (API key)&lt;/strong&gt; — for clients like ChatGPT or Claude Code that expect the key passed directly, or if you'd rather manage auth yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it's connected, you don't write a &lt;code&gt;requests.get()&lt;/code&gt; call at all. You just ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Screen US stocks with market cap between 1B and 50B, positive EPS, dividend yield above 0, and rank them by earnings yield. Show me the top 10."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude translates that into the same &lt;code&gt;stock_screener&lt;/code&gt; tool call I used to build this article, executes it against live EODHD data, and returns the table. This is exactly how I pulled the numbers above — no manual API scripting for the exploratory stage, just a direct conversation with the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this matters in practice:&lt;/strong&gt; the raw API approach is better once you know exactly what you're building — a cron job, a dashboard, a backtest. The MCP approach is better while you're still deciding what the funnel should even look like, since you can adjust a filter, re-run, and see the new output in seconds without touching code.&lt;/p&gt;

&lt;p&gt;Both hit the same underlying EODHD infrastructure and consume the same API quota, so there's no data-quality tradeoff — it's purely a question of whether you want a repeatable script or a fast conversational loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Here You Can Build
&lt;/h2&gt;

&lt;p&gt;This exact funnel took four API calls and about 30 lines of code. From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated weekly screener reports delivered to Slack or email&lt;/li&gt;
&lt;li&gt;a Streamlit dashboard where the filters are sliders instead of hardcoded values&lt;/li&gt;
&lt;li&gt;a backtesting loop that checks whether "cheap earnings yield + dividend-paying + mid-cap" actually outperforms over rolling 12-month windows&lt;/li&gt;
&lt;li&gt;a signal layer on top using EODHD's &lt;code&gt;wallstreet_lo&lt;/code&gt; / &lt;code&gt;wallstreet_hi&lt;/code&gt; signals to cross-check against analyst targets&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get real-time and historical stock data for your own screeners&lt;/strong&gt;&lt;br&gt;
EODHD gives you programmatic access to fundamentals, screener queries, and live pricing across global exchanges — the same endpoints used for every number in this article.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=claude-screened-3000-stocks-eodhd-api&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Start with EODHD's API&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Turning This Into a Daily Job
&lt;/h2&gt;

&lt;p&gt;A one-time screen is a snapshot. The actual value shows up when you run it every trading day and track what changes — which names drop out because they got expensive, and which new ones enter because they got cheap or a fresh earnings report shifted the EPS.&lt;/p&gt;

&lt;p&gt;Here's a script that runs the full 4-stage funnel, saves the day's result, diffs it against yesterday's, and writes a short summary. Point it at cron and forget about it.&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;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/screener&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;OUTPUT_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;screener_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&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;FILTERS&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;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,1000000000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,50000000000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_share&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,0],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avgvol_1d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,300000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,5],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dividend_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,0]]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_screen&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sort&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;market_capitalization.desc&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;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&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="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_share&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;earnings_yield&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&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;def&lt;/span&gt; &lt;span class="nf"&gt;save_and_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_df&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;today_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;today_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OUTPUT_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;today_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;today_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;First run — no previous data to compare against.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;yesterday_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;today_codes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;yesterday_codes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yesterday_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;new_entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today_codes&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yesterday_codes&lt;/span&gt;
    &lt;span class="n"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yesterday_codes&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;today_codes&lt;/span&gt;

    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;New in today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s top 10: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_entries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dropped out: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;new_entries&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No changes in the top 10 today.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_screen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;save_and_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it with a one-line crontab entry to run every weekday after market close:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 22 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5 /usr/bin/python3 /path/to/daily_screener.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /path/to/screener.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the &lt;code&gt;summary&lt;/code&gt; string is trivial to route anywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Post it to a Slack channel with a webhook (&lt;code&gt;requests.post(SLACK_WEBHOOK_URL, json={"text": summary})&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Email it to yourself using the same pattern as any transactional email script&lt;/li&gt;
&lt;li&gt;Append each day's CSV to a running Google Sheet so you get a queryable history instead of a folder of files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part isn't the top 10 on any single day. It's watching &lt;em&gt;turnover&lt;/em&gt; — a name entering the list usually means its price dropped or its earnings just improved enough to make the yield attractive. A name leaving usually means the opposite. That turnover signal is something a one-off screenshot can never give you.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A 4-stage numeric funnel turned a 3,000+ stock universe into 10 names in under 30 lines of Python.&lt;/li&gt;
&lt;li&gt;Earnings yield (EPS ÷ price) is a simple, brutally objective ranking metric — no sentiment, no narrative, no model hallucination.&lt;/li&gt;
&lt;li&gt;The filter mechanically excludes expensive growth names and surfaces cheap, profitable, dividend-paying companies across sectors and countries. That's the point of a rules-based screen: it doesn't know what's "supposed" to be popular.&lt;/li&gt;
&lt;li&gt;EODHD's MCP server lets you skip the API scripting entirely while you're still iterating on filter logic — same data, conversational interface.&lt;/li&gt;
&lt;li&gt;The real value isn't a single day's list. It's a cron job that runs the funnel daily and tells you what entered or left the top 10, because turnover is the actual signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Can Claude actually query live financial data, or is this just training data?&lt;/strong&gt;&lt;br&gt;
✅ Claude called the EODHD Screener API directly for this article, meaning every number reflects the market on the day of the query, not a training snapshot. That distinction matters — most "AI stock pick" content is quietly using stale, memorized data.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is earnings yield a reliable way to find undervalued stocks?&lt;/strong&gt;&lt;br&gt;
✅ It's a useful, transparent starting filter, not a complete valuation model. A high earnings yield can mean genuine value or it can mean the market is pricing in a real risk (regulatory, legal, competitive) that the number doesn't capture. Always follow up with qualitative research before acting.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need a paid EODHD plan to run this screener?&lt;/strong&gt;&lt;br&gt;
✅ The Screener API is available on EODHD's All-In-One and All World Extended plans. Each screener request consumes 5 API calls, so a 4-stage funnel like this one costs 20 calls total — trivial even on a modest plan.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the difference between calling the EODHD API directly and using their MCP server?&lt;/strong&gt;&lt;br&gt;
✅ Same underlying data and the same API quota either way. Direct API calls make sense once you're building a repeatable script, like the daily cron job above. The MCP server makes sense while you're still shaping the filter logic, since you can adjust it in plain language and see results immediately without touching code.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;How do I get alerted automatically instead of checking the screener manually?&lt;/strong&gt;&lt;br&gt;
✅ Run the funnel on a schedule (cron, Task Scheduler, or a serverless function), save each day's output, and diff it against the previous run. Route the diff to Slack, email, or a spreadsheet — the script in this article does exactly that in under 80 lines.&lt;/p&gt;




&lt;p&gt;The market doesn't reward the investor with the best opinions.&lt;/p&gt;

&lt;p&gt;It rewards the one with the best process for filtering out noise.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>python</category>
      <category>mcp</category>
      <category>stocks</category>
    </item>
    <item>
      <title>7 Best WhatsApp APIs for Developers in 2026 (Compared)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:02:19 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/7-best-whatsapp-apis-for-developers-in-2026-compared-f6</link>
      <guid>https://dev.to/kevin_menesesgonzlez/7-best-whatsapp-apis-for-developers-in-2026-compared-f6</guid>
      <description>&lt;p&gt;Most developers reach for Twilio or Meta's official Cloud API the second WhatsApp comes up.&lt;/p&gt;

&lt;p&gt;That's usually the wrong first move.&lt;/p&gt;

&lt;p&gt;The official API wants Business verification, template approval, and per-message billing before you've sent a single test message. Twilio piles its own markup on top. If you're building an AI agent or a support bot that needs to talk to customers this week, you're looking at weeks of paperwork before you write a line of actual logic.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prototyping a WhatsApp AI agent,&lt;/li&gt;
&lt;li&gt;shipping a chatbot fast,&lt;/li&gt;
&lt;li&gt;or running outbound automation and want to skip Meta's approval process,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "official" isn't always the right call
&lt;/h2&gt;

&lt;p&gt;The official WhatsApp Business Platform makes sense if you need the green checkmark, or you're in a regulated industry where Meta's compliance guarantees actually matter.&lt;/p&gt;

&lt;p&gt;But getting there is slow.&lt;/p&gt;

&lt;p&gt;Business Manager verification. App Review. Template approval that can sit for days. Per-message fees that swing wildly by country and category — marketing messages cost ten times more than utility ones in some markets.&lt;/p&gt;

&lt;p&gt;A lot of developers don't need any of that. They just need WhatsApp wired into a backend, an LLM, or an automation pipeline, and that's where unofficial APIs come in. They connect through WhatsApp Web's protocol instead of Meta's Business Platform. You lose some compliance guarantees, you gain instant setup and flat pricing you can actually predict.&lt;/p&gt;

&lt;p&gt;I've seen both sides of this go wrong, and not in the way people expect.&lt;/p&gt;

&lt;p&gt;A developer picks an unofficial API to ship an MVP fast. It works, customers are happy, volume grows — and then the number gets flagged out of nowhere, because nobody warmed it up or thought about WhatsApp's spam detection. Or it goes the other direction: a team picks the official Cloud API for what's basically an internal notification bot, and burns two weeks on Business Manager and template approval for a use case that never needed that level of compliance in the first place.&lt;/p&gt;

&lt;p&gt;Same root mistake both times. Nobody matched the API to their actual message volume and risk tolerance before committing.&lt;/p&gt;

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

&lt;p&gt;This is a rundown of the best WhatsApp APIs for developers right now — official and unofficial, with real pricing, so you can pick the right one for an AI agent, a chatbot, or whatever automation you're building instead of guessing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best official WhatsApp API for AI agents:&lt;/strong&gt; Zernio. No markup on Meta's fees, numbers from $2/month, one API call to get live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best flat-rate unofficial option:&lt;/strong&gt; WAAPI at $10/mo or Whapi.Cloud at $29/mo — no per-message charges, you know your bill in advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best free option if you want to self-host:&lt;/strong&gt; CodeChat. Open source, the only ongoing cost is your own server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Zernio — the official API minus the paperwork
&lt;/h2&gt;

&lt;p&gt;Zernio runs on the real WhatsApp Business Platform, but somebody on their team clearly got tired of Meta's onboarding and decided to fix it.&lt;/p&gt;

&lt;p&gt;Instead of creating a Meta app, passing App Review, and wiring up webhooks by hand, Zernio uses Meta's Embedded Signup flow. You connect a number from a dashboard in one click. No Business Manager rabbit hole.&lt;/p&gt;

&lt;p&gt;What you actually get:&lt;/p&gt;

&lt;p&gt;WhatsApp numbers in 53 countries, provisioned through the API, starting at $2/month. Zero markup on Meta's per-message fees — you pay what Meta charges, not the 3–5x tax Twilio adds on top. Template CRUD with automatic category tracking, so you don't get blindsided by a template silently getting reclassified into a more expensive tier. Webhooks come back in the same JSON format across WhatsApp, Instagram, and Telegram, which matters if your agent talks to more than one channel.&lt;/p&gt;

&lt;p&gt;There's also a hosted MCP server, which means an AI agent (Claude, your own backend, whatever) can manage WhatsApp conversations through plain tool calls instead of you writing glue code for every action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Official API, so you're not gambling on an unofficial protocol getting your number banned&lt;/li&gt;
&lt;li&gt;Platform fee scales down at low volume, first two connected accounts are free&lt;/li&gt;
&lt;li&gt;WhatsApp Calling API support, useful if you want to route voice to an agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You still pay Meta's per-message fees on top, Zernio removes the markup, not the underlying cost&lt;/li&gt;
&lt;li&gt;No visual dashboard built for non-technical teammates, this is built for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers building AI agents or SaaS products who want the official API without losing a month to Meta's setup.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://zernio.com" rel="noopener noreferrer"&gt;Skip the Meta developer portal entirely&lt;/a&gt;&lt;/strong&gt; — Connect a WhatsApp number and start sending messages in one API call, official Business Platform, zero markup.&lt;br&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  2. UltraMsg — fastest unofficial setup
&lt;/h2&gt;

&lt;p&gt;UltraMsg connects through WhatsApp Web's protocol. Scan a QR code, grab an instance ID and token, you're sending messages in about five minutes.&lt;/p&gt;

&lt;p&gt;Pricing is $39/month per instance, or $390 if you pay yearly.&lt;/p&gt;

&lt;p&gt;The upside is flexibility. Any language that can make an HTTP request can talk to it, and there's no hard message cap from UltraMsg's side (WhatsApp's own anti-spam behavior still applies, that part never goes away). The downside is the same one every unofficial API shares: your account lives or dies on WhatsApp Web session stability, not Meta's guarantees. And if you're running more than one number, the per-instance pricing adds up fast.&lt;/p&gt;

&lt;p&gt;Good fit for a solo developer or small business that wants a working integration today and has no interest in touching Meta's portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Green API — the cheapest way to test an idea
&lt;/h2&gt;

&lt;p&gt;Green API splits into two plans: Developer, which is free but capped, and Business, which runs around $8/month per instance for unlimited messaging.&lt;/p&gt;

&lt;p&gt;What I like here is that the free tier is actually usable. It's not a three-day trial dressed up as a free plan — you can build and test a real MVP on it before paying anything. There's also a daily-billing "Partner" option if your workload is seasonal.&lt;/p&gt;

&lt;p&gt;The tradeoffs: the free plan only handles a handful of individual chats per month, so it's testing-only, not production. And the documentation leans toward Russian-market use cases, though the English docs cover what you need.&lt;/p&gt;

&lt;p&gt;Best for validating an idea before you commit to a paid plan anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. WAAPI — one flat price, no surprises
&lt;/h2&gt;

&lt;p&gt;WAAPI charges $10/month per instance, flat. Unlimited messages, every feature included, no tier you have to upgrade into.&lt;/p&gt;

&lt;p&gt;That simplicity is the whole pitch. You're not parsing a pricing page trying to figure out which plan unlocks webhooks. Everything's already there.&lt;/p&gt;

&lt;p&gt;It's a smaller company than UltraMsg or Green API, so there's less of a track record to lean on, and the same unofficial-protocol ban risk applies as everywhere else on this list. But if predictable billing matters more to you than brand history, it's hard to beat.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Whapi.Cloud — built for groups and channels
&lt;/h2&gt;

&lt;p&gt;Whapi.Cloud does something most of the others don't bother with well: WhatsApp Groups, Channels, and Status updates. If your agent needs to post to a channel rather than just DM individual users, this is the one that actually handles it properly.&lt;/p&gt;

&lt;p&gt;There's a free sandbox (150 messages a day, 5 active conversations a month) and then $29/month per connected number once you outgrow it.&lt;/p&gt;

&lt;p&gt;It plugs natively into n8n, Make, and Zapier, which is nice if your team leans no-code. The sandbox is also a genuine free-forever tier, not a countdown timer. The catch is that $29/month is the second-highest flat rate on this list, and the sandbox limits make it useless past the prototype stage.&lt;/p&gt;

&lt;p&gt;Best for anyone building Groups or Channels automation specifically, not plain one-to-one messaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. CodeChat — free, open source, your server
&lt;/h2&gt;

&lt;p&gt;CodeChat is for developers who'd rather run their own infrastructure than pay anyone monthly. It's built on the Baileys library, the same WebSocket engine a lot of commercial APIs use under the hood, and ships as a self-hosted REST API.&lt;/p&gt;

&lt;p&gt;Cost: nothing, beyond whatever VPS you run it on.&lt;/p&gt;

&lt;p&gt;The appeal is obvious if you've ever felt locked into a vendor. Full control over your data, no recurring API bill, and the codebase is battle-tested enough that it later became the foundation for Evolution API. The cost is that you're now the one responsible for uptime, scaling, and keeping the WhatsApp Web layer updated whenever WhatsApp changes something underneath you. There's no support line to call, just community docs.&lt;/p&gt;

&lt;p&gt;If you're comfortable with Docker and don't mind owning the ops side, this is the only option on the list with zero recurring cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Wassenger — built for teams, not just developers
&lt;/h2&gt;

&lt;p&gt;Wassenger is the odd one out here. It's not just an API, it's a full platform: shared team inbox, AI-drafted replies through Claude or ChatGPT, campaign tools.&lt;/p&gt;

&lt;p&gt;Pricing runs €39.90 for Professional, €69.90 for Business, €99.90 for Enterprise, all on the official WABA.&lt;/p&gt;

&lt;p&gt;If you need a human reviewing AI-drafted replies before they go out, the draft-and-approve workflow is genuinely useful. It also means you're not betting on an unofficial protocol. But it's the most expensive entry point on this list, and if all you need is raw API access for a backend integration, you're paying for a team inbox you'll never open.&lt;/p&gt;

&lt;p&gt;Best for teams that want a shared inbox and AI-assisted replies bundled with their API access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Starting price&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zernio&lt;/td&gt;
&lt;td&gt;Official&lt;/td&gt;
&lt;td&gt;$2/month&lt;/td&gt;
&lt;td&gt;AI agents, SaaS products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UltraMsg&lt;/td&gt;
&lt;td&gt;Unofficial&lt;/td&gt;
&lt;td&gt;$39/month&lt;/td&gt;
&lt;td&gt;Fast solo setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green API&lt;/td&gt;
&lt;td&gt;Unofficial&lt;/td&gt;
&lt;td&gt;Free (capped) / $8/mo&lt;/td&gt;
&lt;td&gt;Testing an idea&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAAPI&lt;/td&gt;
&lt;td&gt;Unofficial&lt;/td&gt;
&lt;td&gt;$10/month flat&lt;/td&gt;
&lt;td&gt;Predictable billing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whapi.Cloud&lt;/td&gt;
&lt;td&gt;Unofficial&lt;/td&gt;
&lt;td&gt;Free sandbox / $29/mo&lt;/td&gt;
&lt;td&gt;Groups &amp;amp; Channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeChat&lt;/td&gt;
&lt;td&gt;Unofficial (self-hosted)&lt;/td&gt;
&lt;td&gt;Free (server cost only)&lt;/td&gt;
&lt;td&gt;Full control, zero recurring fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wassenger&lt;/td&gt;
&lt;td&gt;Official&lt;/td&gt;
&lt;td&gt;€39.90/month&lt;/td&gt;
&lt;td&gt;Team inbox + AI replies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;p&gt;Need the green checkmark, or operating somewhere regulated? Start with Zernio or Wassenger. Both run on Meta's official platform, but Zernio cuts the setup time and the per-message markup.&lt;/p&gt;

&lt;p&gt;Just need something working today and can live with unofficial-protocol risk? WAAPI or Green API get you there for the least money.&lt;/p&gt;

&lt;p&gt;Optimizing for zero recurring cost and full control over your stack? CodeChat is the only one here that doesn't send you a bill every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between official and unofficial WhatsApp APIs?&lt;/strong&gt;&lt;br&gt;
Official APIs like Zernio and Wassenger connect through Meta's WhatsApp Business Platform. You need Business verification and you pay per message. Unofficial APIs (UltraMsg, Green API, WAAPI, Whapi.Cloud, CodeChat) connect through WhatsApp Web's protocol instead. Setup takes minutes and pricing is usually flat per number, but there's real ban risk if you send like a spammer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free WhatsApp API for developers?&lt;/strong&gt;&lt;br&gt;
Green API's free Developer plan is actually usable for testing, not just a teaser. CodeChat is free outright since it's open source and self-hosted — your only cost is the server it runs on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which WhatsApp API is best for AI agents?&lt;/strong&gt;&lt;br&gt;
Zernio is built for this specifically. It ships a hosted MCP server so an AI agent can manage WhatsApp conversations through tool calls, and it's on the official API, so you're not worried about getting banned for sending too much volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use an unofficial WhatsApp API without getting banned?&lt;/strong&gt;&lt;br&gt;
The risk is real but manageable. Warm new numbers up slowly, only message people who opted in, don't blast a fresh number with bulk sends. If you're doing cold outbound at real scale, the official API is the safer bet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do these APIs charge per message?&lt;/strong&gt;&lt;br&gt;
Most unofficial ones on this list (UltraMsg, WAAPI, Whapi.Cloud) charge flat monthly fees, nothing per message. The official ones (Zernio, Wassenger) pass through Meta's per-message fees, which vary by country and message type.&lt;/p&gt;

&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn: &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;linkedin.com/in/kevin-meneses-gonzalez&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>api</category>
      <category>whatsapp</category>
      <category>data</category>
      <category>software</category>
    </item>
    <item>
      <title>How LLMs Are Replacing Legacy OCR Workflows</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 08 Jul 2026 16:21:51 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-llms-are-replacing-legacy-ocr-workflows-37hc</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-llms-are-replacing-legacy-ocr-workflows-37hc</guid>
      <description>&lt;h2&gt;
  
  
  *&lt;em&gt;TL;DR: *&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;OCR digitizes a page. AI document processing turns that page into structured, usable data — no templates, no rules to maintain. Unstract is an open-source, no-code platform that does this with LLMs: define what you want extracted in plain language, deploy it as an API or ETL pipeline, and get clean JSON back regardless of how the document is laid out. Start with the &lt;a href="https://unstract.com" rel="noopener noreferrer"&gt;free 14-day trial&lt;/a&gt; or read the &lt;a href="https://docs.unstract.com" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; — no credit card required.&lt;/p&gt;

&lt;p&gt;OCR was never broken.&lt;/p&gt;

&lt;p&gt;It just stopped being enough.&lt;/p&gt;

&lt;p&gt;For thirty years, optical character recognition did exactly what it promised: turn pixels into characters. Feed it a scanned invoice, get back a string of text. That was the job, and it did the job well.&lt;/p&gt;

&lt;p&gt;But reading text and understanding a document are two different problems. And the industries that depend on documents the most — finance, accounting, insurance, capital markets — have spent years discovering that the hard way.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building automation for invoices, statements, or contracts,&lt;/li&gt;
&lt;li&gt;maintaining an OCR pipeline that breaks every time a vendor changes their layout,&lt;/li&gt;
&lt;li&gt;or evaluating whether to move from rule-based extraction to something smarter,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 200-Bank Problem
&lt;/h2&gt;

&lt;p&gt;Picture a finance team that receives bank statements from 200 different banks.&lt;/p&gt;

&lt;p&gt;Each bank uses its own layout. Different field labels. Different date formats. Some put the account balance top-right; others bury it in a footer table. A few still send scanned PDFs that look like they were faxed twice.&lt;/p&gt;

&lt;p&gt;Template-based OCR handles this the only way it knows how: one template per bank. 200 templates. Each one is built by hand, each one breaking the moment a bank redesigns its statement.&lt;/p&gt;

&lt;p&gt;Now widen the lens.&lt;/p&gt;

&lt;p&gt;An insurance underwriter doesn't just deal with bank statements — they deal with claims forms, KYC files, medical reports, and policy applications, each shaped differently depending on the insurer, the state, and the line of business. An accounting team doesn't process one invoice format — it processes one for every single vendor it works with, and vendors change their templates without asking anyone's permission.&lt;/p&gt;

&lt;p&gt;Multiply any of these by real transaction volume, and you get a pattern every ops team eventually recognizes.&lt;/p&gt;

&lt;p&gt;A Monday morning that repeats itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone opens 40 PDFs by hand.&lt;/li&gt;
&lt;li&gt;Copy numbers into a spreadsheet, one cell at a time.&lt;/li&gt;
&lt;li&gt;Pray the format didn't change since last month.&lt;/li&gt;
&lt;li&gt;Finds three that don't match any known template and sets them aside "for later."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's not automation. That's a workaround wearing automation's clothes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Was Never Reading Text
&lt;/h2&gt;

&lt;p&gt;Here's the reframe.&lt;/p&gt;

&lt;p&gt;The problem was never extracting characters from a page. OCR solved that decades ago, and modern OCR engines hit 99%+ accuracy on printed text and roughly 95% on handwriting.&lt;/p&gt;

&lt;p&gt;The problem is that reading text and understanding a document are not the same task.&lt;/p&gt;

&lt;p&gt;A human accountant doesn't process an invoice by reading every character in order. They recognize this is an invoice, they know the total usually sits near the bottom, and they adjust instantly when the layout is unfamiliar. That's comprehension, not character recognition.&lt;/p&gt;

&lt;p&gt;Legacy systems never had that layer. AI document processing exists because LLMs finally do.&lt;/p&gt;

&lt;p&gt;That distinction is also why "just add more OCR accuracy" was never going to solve this. You can push OCR accuracy on printed characters as close to 100% as you like, and it still won't tell you which extracted number is the invoice total versus a line-item subtotal. That's a comprehension task, not a recognition task — and comprehension is what large language models bring to the table that thirty years of OCR research never could.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is AI Document Processing, Actually
&lt;/h2&gt;

&lt;p&gt;AI document processing is the use of large language models to read, interpret, and structure the content of unstructured documents — PDFs, scans, images, spreadsheets — without relying on fixed templates or hand-coded rules.&lt;/p&gt;

&lt;p&gt;Instead of matching a document against a predefined layout, an LLM-based pipeline reads the document the way a person would: it understands that a number preceded by "Total Due" is different from a number labeled "Subtotal," even if their position on the page changes completely from one document to the next.&lt;/p&gt;

&lt;p&gt;Here's how the three generations actually compare:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9h462dcpx1u9r34bvkn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9h462dcpx1u9r34bvkn.png" alt=" " width="772" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traditional OCR isn't obsolete — for one repetitive form, from one source, in one layout, it's still fast and cheap. But finance, insurance, and accounting teams rarely deal with one format. They deal with hundreds of variants of the same document type, arriving in whatever shape a vendor or client happens to send.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Pipeline Actually Works, Step by Step
&lt;/h2&gt;

&lt;p&gt;Under the hood, an AI document processing pipeline is doing four distinct things, whether you notice them or not:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parsing&lt;/strong&gt; — turning the raw file (scanned PDF, native PDF, image, even a photo of a paper form) into text while preserving layout: tables stay tables, checkboxes stay checkboxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema definition&lt;/strong&gt; — describing, in plain language or a JSON schema, exactly what fields matter: customer name, invoice total, claim number, whatever the business needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction&lt;/strong&gt; — the LLM reads the parsed document against that schema and returns structured values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; — checking the extracted values for consistency, flagging low-confidence fields instead of guessing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most teams that try to build this themselves get step 3 working in a weekend with a raw LLM API call. Step 1 and step 4 are where things quietly fall apart in production — layout gets flattened, and a hallucinated total slips through with full confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Building This In-House Usually Stalls
&lt;/h2&gt;

&lt;p&gt;Plenty of engineering teams start this project the same way: a Python script, an LLM API call, a PDF-to-text library, done in an afternoon.&lt;/p&gt;

&lt;p&gt;It works — on the five sample documents used to build it.&lt;/p&gt;

&lt;p&gt;Then it hits production, and three things happen, almost every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A multi-column layout gets flattened into a single blob of text, and the LLM starts guessing which number belongs to which field.&lt;/li&gt;
&lt;li&gt;The model hallucinates a value with full confidence on a document type it hasn't seen before, and nobody notices until the number is already in a report.&lt;/li&gt;
&lt;li&gt;Token costs creep up as documents get longer, because nobody built in compression or prompt efficiency from day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are LLM problems. They're pipeline problems — the parsing, validation, and cost layers that a raw API call skips entirely. This is precisely the gap purpose-built platforms are designed to close, instead of every team rebuilding the same missing pieces independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trend: From Templates to Prompts
&lt;/h2&gt;

&lt;p&gt;Something changed in the last 24 months.&lt;/p&gt;

&lt;p&gt;Vision-capable LLMs and layout-aware parsers made it possible to feed a document into a pipeline and get structured output back — without teaching the system what that specific document looks like first.&lt;/p&gt;

&lt;p&gt;That shift shows up everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema-first extraction.&lt;/strong&gt; You define what you want (a JSON schema), not how the document is laid out. Even foundation model providers have moved this direction — structured outputs are now a first-class feature in most major LLM APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic workflows.&lt;/strong&gt; Instead of a human writing extraction rules by hand, an AI agent generates and refines the extraction logic itself, iterating against sample documents until accuracy stabilizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence over guesswork.&lt;/strong&gt; Modern pipelines can flag low-confidence extractions instead of silently returning wrong data — a "no value" is safer than a wrong one on a financial document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance pressure.&lt;/strong&gt; New 2026 obligations like the EU AI Act push regulated industries — banking, insurance, life sciences — toward document workflows with audit trails and traceability, something rule-based OCR was never built to provide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finance and insurance are leading this shift for a simple reason: they carry the highest document volume and the highest cost of a wrong number. A misread account balance isn't a UX bug — it's a compliance problem. BFSI (banking, financial services, insurance) alone represents close to a third of the entire IDP market in 2026, and that share keeps growing as more of that volume moves to LLM-based extraction.&lt;/p&gt;

&lt;p&gt;There's a second force behind this trend worth naming: MCP (Model Context Protocol) and the broader move toward agentic systems. AI agents making decisions — approving a claim, flagging fraud, triggering a payment — need structured, trustworthy input to act on. An agent can't reason well over a wall of unformatted OCR text. It needs the same clean JSON a downstream database would expect. That's pushing document extraction from being a back-office utility to being infrastructure other AI systems depend on directly.&lt;/p&gt;

&lt;p&gt;This is exactly the gap Unstract was built to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Costs vs. What It Replaces
&lt;/h2&gt;

&lt;p&gt;It's worth being blunt about the economics, because "AI is expensive" is the objection that kills most of these projects before they start.&lt;/p&gt;

&lt;p&gt;LLM token costs on a single document are real, but they're not the number that matters. The number that matters is the fully-loaded cost of the alternative: a person spending 15–20 minutes per document, multiplied by hundreds of documents a week, multiplied by the error rate of manual re-keying.&lt;/p&gt;

&lt;p&gt;Token-efficiency techniques like SinglePass and Summarized Extraction exist specifically because vendors know this comparison only holds up if per-document AI cost stays a small fraction of a person's hourly rate — which, for anything beyond a handful of documents a day, it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Unstract Fits
&lt;/h2&gt;

&lt;p&gt;Unstract is an open-source, no-code platform purpose-built for extracting structured data from unstructured documents using LLMs — deployable as an API or as an ETL pipeline, without writing extraction rules by hand.&lt;/p&gt;

&lt;p&gt;It's not a single model. It's a full pipeline, and each piece solves a specific failure mode that shows up when you try to run LLM extraction in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLMWhisperer&lt;/strong&gt; — a layout-preserving text extraction engine. It keeps tables, multi-column layouts, checkboxes, and handwritten fields intact before anything reaches the LLM, so the model isn't guessing at structure that got flattened away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Studio&lt;/strong&gt; — a no-code environment where you build extraction logic against real sample documents, compare output and cost across multiple LLMs side by side, and version your prompts instead of losing track of them in a spreadsheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLMChallenge&lt;/strong&gt; — Unstract's answer to hallucination. Two independent LLMs extract the same field; if they don't agree, you get NULL instead of a confidently wrong number. No value is safer than a wrong one on a financial document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SinglePass and Summarized Extraction&lt;/strong&gt; — techniques that cut token usage by up to 7x on large documents, which matters the moment you're processing thousands of statements a month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Hub and MCP Server&lt;/strong&gt; — once a schema is built, deploy it as a callable API in one click, or connect it directly to AI agents that need reliable structured data as an input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few concrete benefits this unlocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No templates, no per-vendor maintenance.&lt;/strong&gt; The same extraction project handles bank statements from 200 different banks, because it understands the concept of "account balance," not the position of a specific box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in hallucination control.&lt;/strong&gt; LLMChallenge means you get validated data or nothing — never a silent wrong value slipping into a downstream system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-source, no vendor lock-in.&lt;/strong&gt; Unstract is released under AGPL 3.0. Self-host it, inspect the code, or use the managed cloud edition — your choice, your data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy without writing a pipeline from scratch.&lt;/strong&gt; A finished extraction project becomes an API or ETL job in one click, feeding straight into Snowflake, BigQuery, or your existing database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document-agnostic by design.&lt;/strong&gt; No prior training run needed per document type — you point it at samples, define the schema, and it generalizes.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://unstract.com" rel="noopener noreferrer"&gt;Try Unstract free for 14 days&lt;/a&gt;&lt;/strong&gt; — no credit card required, pre-configured with an LLM, vector database, and LLMWhisperer.&lt;br&gt;

&lt;/div&gt;


&lt;p&gt;You can read the full breakdown of the platform in the &lt;a href="https://docs.unstract.com" rel="noopener noreferrer"&gt;Unstract documentation&lt;/a&gt; if you want to see how the pieces connect before touching any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Walkthrough #1: Extracting a Bank Statement
&lt;/h2&gt;

&lt;p&gt;Theory is fine, but this only matters if it holds up against a real document. Let's make it concrete.&lt;/p&gt;

&lt;p&gt;Say you receive credit card statements as PDFs — same core fields (customer name, issuer, statement date, list of transactions), wildly different formatting depending on the bank. This is one of the most common entry points teams use to test AI document processing, precisely because the pain of maintaining per-bank templates is so immediate and so familiar to anyone who has tried automating reconciliation before.&lt;/p&gt;

&lt;p&gt;With a template-based system, this is where things get painful. With Unstract, the workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload a handful of sample statements (from different banks) into Prompt Studio.&lt;/li&gt;
&lt;li&gt;Define your schema in plain language — "extract customer name, statement date, total due, and list of transactions with date/description/amount."&lt;/li&gt;
&lt;li&gt;Let Prompt Studio generate and test the extraction prompt against your samples, comparing accuracy and cost across LLMs.&lt;/li&gt;
&lt;li&gt;Deploy as an API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once deployed, calling it looks like a normal REST request:&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="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.unstract.com/deployment/api/&amp;lt;your-org&amp;gt;/&amp;lt;your-endpoint&amp;gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;headers&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;Authorization&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;Bearer YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;files&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;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statement_chase.pdf&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The output is standard, structured JSON — regardless of which bank the statement came from:&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;"customer_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;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chase"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statement_date"&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-06-30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1284.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactions"&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="nl"&gt;"date"&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-06-02"&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;"Amazon.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;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.99&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="nl"&gt;"date"&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-06-05"&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;"Uber"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;18.30&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;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated reconciliation pipelines that flag discrepancies&lt;/li&gt;
&lt;li&gt;fraud detection models fed by clean transaction data&lt;/li&gt;
&lt;li&gt;dashboards that update without anyone touching a spreadsheet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No template. No rule that breaks the next time a bank redesigns its statement.&lt;/p&gt;
&lt;h2&gt;
  
  
  Practical Walkthrough #2: Automating Vendor Invoice Approval
&lt;/h2&gt;

&lt;p&gt;Here's a second example — a workflow that's easy to replicate if you're on an accounting or AP (accounts payable) team.&lt;/p&gt;

&lt;p&gt;The scenario: invoices arrive by email from hundreds of vendors, every one with a different layout. Someone currently opens each one, checks it against a purchase order, and manually keys the total into the accounting system.&lt;/p&gt;

&lt;p&gt;The schema you'd define in Prompt Studio:&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;"vendor_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;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoice_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoice_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"due_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line_items"&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="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;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line_total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&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;"subtotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&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;Once that schema is validated against a sample of invoices from different vendors, this becomes an ETL pipeline instead of a one-off API call: drop incoming invoices into a shared Google Drive or S3 folder, and Unstract automatically extracts and pushes structured rows into your accounting database.&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;# Pseudocode for the downstream logic once structured data lands in your DB
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_extracted_invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;po&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;match_purchase_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_due&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;po&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_due&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;approve_for_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;flag_for_manual_review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PO mismatch&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 extraction layer's only job is turning a messy PDF into the JSON above. Everything downstream — matching against POs, approving payment, flagging exceptions — becomes a business logic problem instead of a "can we even read this PDF" problem.&lt;/p&gt;

&lt;p&gt;That's the actual unlock: once documents become structured data, the rest of your stack stops needing to know it ever came from a PDF at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where This Pays Off: Use Cases by Industry
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Finance — Multi-bank statement reconciliation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; A treasury or finance team pulling statements from multiple banking relationships hits the same wall every close cycle — different formats, different field labels, no shared structure. Reconciliation turns into a manual, error-prone task repeated every single month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; One schema handles every bank's statement layout, because the extraction logic understands the concept of "closing balance" or "transaction date" rather than a fixed position on the page. The output feeds directly into reconciliation logic without a human retyping anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams closing books monthly across multiple banking relationships, or finance functions managing treasury operations across subsidiaries with different local banks.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Insurance — Underwriting and claims processing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; Claims forms, KYC documents, and medical reports vary by insurer, state, and line of business — a nightmare for template-based systems that need a new template for every combination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; Schema-based extraction generalizes across formats instead of requiring a template per variant. Teams running this kind of pipeline report review times dropping from days to minutes, simply because the bottleneck was never document volume — it was the seams between formats that broke automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Underwriting teams processing high volumes of inconsistent intake documents, and claims teams under pressure to cut turnaround time without adding headcount.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Accounting — Accounts payable automation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; Every vendor invoices differently, and manual entry doesn't scale past a few dozen suppliers before someone becomes a full-time human OCR machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; Structured extraction feeds directly into PO-matching and payment approval logic, exactly as shown in Walkthrough #2 above — the invoice becomes a JSON object the moment it lands, and everything downstream is business logic, not document parsing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; AP teams handling invoices from 50+ vendors with no shared format, or finance ops teams trying to close the "three-way match" (PO, invoice, receipt) faster.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Capital markets — KYC and client onboarding
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; Onboarding documents (ID scans, proof of address, account applications) arrive in every format imaginable, and compliance requires traceable, auditable extraction — not just a fast one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; Layout-aware parsing handles scanned and handwritten fields that break traditional OCR, while validation layers like dual-LLM consensus create the audit trail regulators increasingly expect under frameworks like the EU AI Act.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Onboarding teams under regulatory pressure to document exactly how each data point was extracted and verified, not just that it was extracted.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Legal and contracts — Clause and obligation extraction
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; Contracts share no common template, and finding specific clauses (payment terms, termination conditions, indemnification language) manually doesn't scale across a growing contract portfolio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; Prompt-based extraction can target specific clause types across contracts of any format and length, structuring what used to require a paralegal reading every page line by line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Legal ops teams managing large, heterogeneous contract repositories, or M&amp;amp;A teams running due diligence against hundreds of agreements on a deadline.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Healthcare-adjacent finance — Medical billing and claims reconciliation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pain:&lt;/strong&gt; Medical billing documents mix structured codes (CPT, ICD-10) with unstructured physician notes and inconsistent payer formats, making reconciliation between what was billed and what was paid painfully manual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI document processing helps:&lt;/strong&gt; Extraction schemas can target both the structured codes and surrounding context simultaneously, flagging discrepancies between billed and reimbursed amounts without a human cross-referencing every line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Revenue cycle teams reconciling claims across multiple payers with inconsistent explanation-of-benefits formats.&lt;/p&gt;

&lt;p&gt;Across every one of these use cases, the underlying pattern repeats: the documents are different, the pain is identical, and the fix is the same — stop building a template per format and start defining a schema per outcome.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started, Practically
&lt;/h2&gt;

&lt;p&gt;If you want to test this against your own documents rather than take any of this on faith, the fastest path looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull 5–10 real samples of the document type that causes you the most pain — the messiest ones you have, not the cleanest.&lt;/li&gt;
&lt;li&gt;Sign up for the free 14-day trial — it comes pre-configured with an LLM, vector database, embedding model, and LLMWhisperer, so there's nothing to wire up first.&lt;/li&gt;
&lt;li&gt;Define your schema in Prompt Studio using plain language, exactly like the JSON examples in Walkthrough #1 and #2 above.&lt;/li&gt;
&lt;li&gt;Run it against your samples and compare accuracy and cost across the available LLMs before committing to one.&lt;/li&gt;
&lt;li&gt;Deploy as an API or ETL pipeline once the extraction is accurate enough to trust — no separate infrastructure to stand up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://docs.unstract.com" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; walks through this exact sequence using a credit card statement example, if you want a guided first run before pointing it at your own documents.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OCR reads characters. AI document processing understands context — that gap is the entire reason legacy IDP struggles with document variation.&lt;/li&gt;
&lt;li&gt;The real cost of template-based systems isn't the software. It's the ongoing human maintenance every new layout demands.&lt;/li&gt;
&lt;li&gt;Platforms like Unstract combine layout-aware parsing, schema-based extraction, and hallucination control into one deployable pipeline — so you build the extraction logic once and let it handle the variation, whether that's bank statements, invoices, or insurance claims.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;❓ What is AI document processing?&lt;/strong&gt;&lt;br&gt;
✅ It's the use of large language models to read, interpret, and structure data from unstructured documents — PDFs, scans, images — without relying on fixed templates. Unlike OCR, which only extracts text, it extracts meaning: it understands what a field represents, not just where it sits on the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Is OCR still useful in 2026?&lt;/strong&gt;&lt;br&gt;
✅ Yes, for narrow cases: high-volume batches of the exact same form, from the exact same source, where digitizing text is the only goal. For anything with real-world document variation, OCR alone isn't enough — that's the problem AI document processing solves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Is Unstract open source?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Unstract is released under the AGPL 3.0 license, with a self-hosted open-source edition and a managed cloud edition that adds enterprise features like LLMChallenge, SSO, and human review workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Do I need to know how to code to use Unstract?&lt;/strong&gt;&lt;br&gt;
✅ No. Prompt Studio is a no-code environment — you define what to extract in natural language against sample documents. Developers can go further and call the resulting extraction as a REST API or embed it in an ETL pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Can AI document processing handle handwritten documents?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Layout-aware parsing engines like LLMWhisperer include handwritten text detection and checkbox/radio button recognition, which is where template-based OCR historically struggled the most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ How is this different from just calling an LLM API directly with a PDF?&lt;/strong&gt;&lt;br&gt;
✅ A raw LLM call skips the layers that make extraction reliable in production: layout-preserving parsing so tables and forms aren't flattened, schema validation, and hallucination checks like dual-LLM consensus. Those layers are the difference between a demo and a system you can trust with financial data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ How long does it take to go from zero to a working extraction pipeline?&lt;/strong&gt;&lt;br&gt;
✅ With a no-code platform, most teams get a working schema validated against sample documents within a day or two — the bulk of the time goes into gathering representative samples, not building infrastructure. Deployment as an API or ETL job happens in the same session once the schema is accurate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❓ Can AI document processing integrate with an existing data warehouse?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Platforms built for this typically support direct output to destinations like Snowflake, BigQuery, Redshift, or standard databases via ETL pipelines, so structured data lands where your analytics and reporting tools already expect it.&lt;/p&gt;

&lt;p&gt;Legacy OCR asked documents to fit a template.&lt;/p&gt;

&lt;p&gt;AI document processing asks the system to understand the document instead — and that's the shift finance, accounting, and insurance teams have been waiting for.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://unstract.com" rel="noopener noreferrer"&gt;Start Unstract's free trial&lt;/a&gt;&lt;/strong&gt; — point it at your messiest documents first and let the comparison speak for itself.&lt;br&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>ocr</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to Backtest a Trading Strategy with Python and EODHD API</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:31:16 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-to-backtest-a-trading-strategy-with-python-and-eodhd-api-5401</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-to-backtest-a-trading-strategy-with-python-and-eodhd-api-5401</guid>
      <description>&lt;p&gt;Most backtests lie to you.&lt;/p&gt;

&lt;p&gt;Not intentionally. But they lie.&lt;/p&gt;

&lt;p&gt;You design a strategy, run it on historical data, and watch the returns look incredible. Then you run it live — and it underperforms a simple buy-and-hold from day one. The math wasn't wrong.&lt;/p&gt;

&lt;p&gt;The data was.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing momentum or mean-reversion strategies in Python,&lt;/li&gt;
&lt;li&gt;building quant tools for personal or professional use,&lt;/li&gt;
&lt;li&gt;or tired of backtests that collapse the moment real execution begins,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This changes how you work.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What this covers:&lt;/strong&gt; Backtesting trading strategies in Python using EODHD's historical OHLCV data API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; &lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;numpy&lt;/code&gt; — no heavy frameworks (no backtrader, no vectorbt)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts included:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Script 1 — Fetch adjusted historical price data from EODHD&lt;/li&gt;
&lt;li&gt;Script 2 — SMA crossover strategy (20/50-day)&lt;/li&gt;
&lt;li&gt;Script 3 — RSI mean-reversion strategy&lt;/li&gt;
&lt;li&gt;Script 4 — Performance metrics: Sharpe ratio, max drawdown, win rate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EODHD pricing:&lt;/strong&gt; Free tier available; full access from $19.99/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Developers and analysts who need reliable, split/dividend-adjusted data without scraping&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problem with Free Data
&lt;/h2&gt;

&lt;p&gt;Most developers start with Yahoo Finance or a scraped CSV.&lt;/p&gt;

&lt;p&gt;That works fine for a quick prototype. It stops working the moment your strategy includes anything that happened around a stock split, dividend payment, or ticker change.&lt;/p&gt;

&lt;p&gt;Non-adjusted price data creates ghost signals. A stock "drops 50%" when it actually split 2:1. Your moving average calculates a crossover that never happened in real life. Your strategy looks profitable because it's trading on a data artifact.&lt;/p&gt;

&lt;p&gt;The free path costs you accuracy. And in backtesting, accuracy is the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix Is Simpler Than You Think
&lt;/h2&gt;

&lt;p&gt;The real bottleneck isn't the strategy logic. It's the data source.&lt;/p&gt;

&lt;p&gt;Use split- and dividend-adjusted closing prices from a reliable provider, and half your backtest reliability problems disappear before you write a single signal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=backtest-trading-strategy-python-eodhd&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD APIs&lt;/a&gt; provides exactly this. Their historical data endpoint returns adjusted OHLCV data for 70,000+ tickers across 50+ exchanges, via a simple REST API. No scraping. No undocumented endpoints that break on weekends.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EODHD Financial Data API&lt;/strong&gt;&lt;br&gt;
Adjusted historical prices, fundamentals, and real-time data for 70,000+ tickers.&lt;br&gt;
→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=backtest-trading-strategy-python-eodhd&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Start free at eodhd.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Install the required libraries:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Set your API token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_eodhd_api_token_here&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get a free token at &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=backtest-trading-strategy-python-eodhd&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;eodhd.com&lt;/a&gt;. The free tier includes end-of-day data for US tickers with a 1-year delay — enough to test strategies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Script 1: Fetch Historical OHLCV Data from EODHD
&lt;/h2&gt;

&lt;p&gt;The foundation of every backtest is the raw price series. This function pulls adjusted daily OHLCV data for any ticker and returns a clean pandas DataFrame.&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;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_eodhd_api_token_here&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_historical_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Fetch adjusted EOD OHLCV data from EODHD for a given symbol.
    Returns a DataFrame indexed by date.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&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;json&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;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end&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;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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&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;high&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;low&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;close&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;adjusted_close&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;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;


&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_historical_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;            &lt;span class="k"&gt;open&lt;/span&gt;    &lt;span class="k"&gt;high&lt;/span&gt;     &lt;span class="k"&gt;low&lt;/span&gt;   &lt;span class="k"&gt;close&lt;/span&gt;  &lt;span class="k"&gt;adjusted&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;      &lt;span class="k"&gt;volume&lt;/span&gt;
&lt;span class="k"&gt;date&lt;/span&gt;
&lt;span class="ld"&gt;2024-12-24&lt;/span&gt;  &lt;span class="mf"&gt;255.5&lt;/span&gt;  &lt;span class="mf"&gt;258.4&lt;/span&gt;  &lt;span class="mf"&gt;254.2&lt;/span&gt;  &lt;span class="mf"&gt;257.9&lt;/span&gt;          &lt;span class="mf"&gt;257.9&lt;/span&gt;   &lt;span class="mf"&gt;32145600&lt;/span&gt;
&lt;span class="ld"&gt;2024-12-26&lt;/span&gt;  &lt;span class="mf"&gt;258.1&lt;/span&gt;  &lt;span class="mf"&gt;261.0&lt;/span&gt;  &lt;span class="mf"&gt;257.3&lt;/span&gt;  &lt;span class="mf"&gt;259.3&lt;/span&gt;          &lt;span class="mf"&gt;259.3&lt;/span&gt;   &lt;span class="mf"&gt;28761300&lt;/span&gt;
&lt;span class="ld"&gt;2024-12-27&lt;/span&gt;  &lt;span class="mf"&gt;258.8&lt;/span&gt;  &lt;span class="mf"&gt;259.4&lt;/span&gt;  &lt;span class="mf"&gt;254.6&lt;/span&gt;  &lt;span class="mf"&gt;255.6&lt;/span&gt;          &lt;span class="mf"&gt;255.6&lt;/span&gt;   &lt;span class="mf"&gt;41232100&lt;/span&gt;
&lt;span class="ld"&gt;2024-12-30&lt;/span&gt;  &lt;span class="mf"&gt;253.1&lt;/span&gt;  &lt;span class="mf"&gt;254.3&lt;/span&gt;  &lt;span class="mf"&gt;250.7&lt;/span&gt;  &lt;span class="mf"&gt;251.8&lt;/span&gt;          &lt;span class="mf"&gt;251.8&lt;/span&gt;   &lt;span class="mf"&gt;39814700&lt;/span&gt;
&lt;span class="ld"&gt;2024-12-31&lt;/span&gt;  &lt;span class="mf"&gt;250.4&lt;/span&gt;  &lt;span class="mf"&gt;252.1&lt;/span&gt;  &lt;span class="mf"&gt;248.9&lt;/span&gt;  &lt;span class="mf"&gt;250.4&lt;/span&gt;          &lt;span class="mf"&gt;250.4&lt;/span&gt;   &lt;span class="mf"&gt;44021900&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;adjusted_close&lt;/code&gt; column. That's what you backtest on — not raw &lt;code&gt;close&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Script 2: SMA Crossover Strategy
&lt;/h2&gt;

&lt;p&gt;The 20/50-day SMA crossover is the classic momentum signal: go long when the short-term average crosses above the long-term, exit when it crosses below.&lt;/p&gt;

&lt;p&gt;Simple in theory. The implementation details matter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sma_crossover_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;short_window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;long_window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SMA crossover strategy using adjusted closing prices.
    Signal:  1 = long, -1 = short, 0 = flat
    Position is shifted by 1 day to prevent look-ahead bias.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;short_window&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long_window&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Raw signal
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_long&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;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sma_long&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;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Shift by 1 to trade on the *next* day's open — no look-ahead
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;shift&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="c1"&gt;# Returns
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pct_change&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Cumulative performance
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;cumprod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;cumprod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;


&lt;span class="c1"&gt;# Run it
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_historical_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result_sma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sma_crossover_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;final_market&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result_sma&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;final_strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result_sma&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buy &amp;amp; Hold return:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_market&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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SMA Strategy return: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_strategy&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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;shift(1)&lt;/code&gt; on line 16 is the single most important detail. Without it, you're using today's signal to trade today's close — which is impossible in real life and produces inflated results.&lt;/p&gt;




&lt;h2&gt;
  
  
  Script 3: RSI Mean-Reversion Strategy
&lt;/h2&gt;

&lt;p&gt;RSI (Relative Strength Index) measures the speed of price changes on a 0–100 scale. Values below 30 signal oversold conditions; above 70 signals overbought.&lt;/p&gt;

&lt;p&gt;The mean-reversion hypothesis: when a stock is oversold, it tends to recover. When overbought, it tends to pull back.&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_rsi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Wilder&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s RSI using simple moving averages of gains and losses.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;gain&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lower&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;loss&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upper&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;avg_gain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;avg_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;avg_gain&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;avg_loss&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rsi_strategy_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;rsi_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;oversold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;overbought&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    RSI mean-reversion strategy.
    Enter long when RSI drops below &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;oversold&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.
    Exit (go flat) when RSI rises above &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;overbought&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rsi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_rsi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rsi_period&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rsi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;oversold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# Buy oversold
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rsi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;overbought&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# Sell overbought
&lt;/span&gt;
    &lt;span class="c1"&gt;# Hold position between signals (forward-fill non-zero values)
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ffill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&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="c1"&gt;# Again: no look-ahead
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pct_change&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;cumprod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;cumprod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;


&lt;span class="c1"&gt;# Run it
&lt;/span&gt;&lt;span class="n"&gt;result_rsi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rsi_strategy_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buy &amp;amp; Hold return:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_rsi&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cumulative_market&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RSI Strategy return: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_rsi&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Script 4: Performance Metrics (Sharpe, Max Drawdown, Win Rate)
&lt;/h2&gt;

&lt;p&gt;Return alone means nothing. A strategy returning 40% with -60% max drawdown is not a good strategy.&lt;/p&gt;

&lt;p&gt;This function calculates the three metrics that matter most for evaluating any backtest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_performance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;risk_free_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Compute annualized return, Sharpe ratio, max drawdown, and win rate
    for a backtested strategy DataFrame.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;strategy_returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Annualized return
&lt;/span&gt;    &lt;span class="n"&gt;total_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;annual_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_days&lt;/span&gt;
    &lt;span class="n"&gt;strategy_ann&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;annual_factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;market_ann&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;annual_factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Sharpe ratio (annualized)
&lt;/span&gt;    &lt;span class="n"&gt;daily_rf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;risk_free_rate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt;
    &lt;span class="n"&gt;excess&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy_returns&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;daily_rf&lt;/span&gt;
    &lt;span class="n"&gt;sharpe&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;excess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;excess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Maximum drawdown
&lt;/span&gt;    &lt;span class="n"&gt;cumulative&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cumulative_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;rolling_max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cumulative&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cummax&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;drawdown&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cumulative&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rolling_max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rolling_max&lt;/span&gt;
    &lt;span class="n"&gt;max_dd&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;drawdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Win rate (percentage of profitable trading days)
&lt;/span&gt;    &lt;span class="n"&gt;active&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy_returns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strategy_returns&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;win_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Strategy Annualized Return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;strategy_ann&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Market Annualized Return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_ann&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sharpe Ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sharpe&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max Drawdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_dd&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Win Rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                   &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;win_rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;# Evaluate both strategies
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=== SMA Crossover ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;calculate_performance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_sma&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== RSI Mean-Reversion ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;calculate_performance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_rsi&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample output (AAPL, 2020–2024):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== SMA Crossover ===
  Strategy Annualized Return: 18.4%
  Market Annualized Return:   22.1%
  Sharpe Ratio:               0.91
  Max Drawdown:              -14.3%
  Win Rate:                   53.2%

=== RSI Mean-Reversion ===
  Strategy Annualized Return: 15.7%
  Market Annualized Return:   22.1%
  Sharpe Ratio:               0.78
  Max Drawdown:              -18.6%
  Win Rate:                   51.8%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, buy-and-hold wins on raw return. But look at the max drawdown: the SMA strategy cuts the worst-case scenario from -30%+ to -14%. That's the real value — risk-adjusted performance, not just raw returns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's the full pipeline: fetch data, run both strategies, compare metrics.&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;# Full backtest pipeline
&lt;/span&gt;&lt;span class="n"&gt;symbols&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;AAPL&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;MSFT&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;NVDA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_historical_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2021-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;sma_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sma_crossover_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rsi_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rsi_strategy_backtest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  SMA Crossover:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;calculate_performance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sma_result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  RSI Mean-Reversion:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;calculate_performance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rsi_result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a parameter optimization loop (walk-forward testing)&lt;/li&gt;
&lt;li&gt;a multi-ticker portfolio backtest with position sizing&lt;/li&gt;
&lt;li&gt;a live signal generator using EODHD's real-time endpoints&lt;/li&gt;
&lt;li&gt;a dashboard to visualize equity curves and drawdown periods&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Is EODHD data adjusted for stock splits and dividends?&lt;/strong&gt;&lt;br&gt;
✅ Yes. The &lt;code&gt;adjusted_close&lt;/code&gt; field in the API response accounts for both splits and dividends. Always use this field for backtesting — raw &lt;code&gt;close&lt;/code&gt; prices will produce misleading signals around corporate actions.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I backtest strategies on non-US markets with EODHD?&lt;/strong&gt;&lt;br&gt;
✅ EODHD covers 50+ exchanges including LSE, TSX, ASX, Euronext, and major Asian markets. Just change the &lt;code&gt;exchange&lt;/code&gt; parameter in the API call (e.g., &lt;code&gt;"LSE"&lt;/code&gt; for London, &lt;code&gt;"TO"&lt;/code&gt; for Toronto).&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Does this approach suffer from survivorship bias?&lt;/strong&gt;&lt;br&gt;
✅ Potentially yes, if you only test on stocks that still exist today. To minimize it, include delisted tickers in your universe. EODHD provides data for delisted stocks — you can query them using their historical ticker symbols.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the difference between a good Sharpe ratio and a bad one?&lt;/strong&gt;&lt;br&gt;
✅ As a general benchmark: below 0.5 is weak, 0.5–1.0 is acceptable, above 1.0 is considered good. Above 2.0 in a backtest should raise suspicion — it often signals overfitting to historical noise.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there a free tier on EODHD to try this?&lt;/strong&gt;&lt;br&gt;
✅ Yes. EODHD offers a free API key that includes EOD data for US tickers. You can run all the scripts in this article with the free tier. Paid plans start at $19.99/month and unlock real-time data, fundamentals, and full global coverage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before You Ship Your Strategy Live
&lt;/h2&gt;

&lt;p&gt;Two things to check before treating any backtest as real:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction costs.&lt;/strong&gt; Every trade has a spread and a commission. Add a &lt;code&gt;-0.001&lt;/code&gt; cost per trade (0.1%) to your &lt;code&gt;strategy_return&lt;/code&gt; calculation and see if the edge survives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overfitting.&lt;/strong&gt; If you tuned your parameters (RSI period, SMA windows) on the same data you're testing on, your results are optimistic. Use a walk-forward split: train on 70% of the data, test on the remaining 30%.&lt;/p&gt;

&lt;p&gt;Bad data tells you the strategy works. Good data tells you the truth.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EODHD Financial Data API&lt;/strong&gt;&lt;br&gt;
Adjusted historical prices for 70,000+ tickers across 50+ global exchanges. REST API, JSON responses, Python-friendly.&lt;br&gt;
→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=backtest-trading-strategy-python-eodhd&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Start free — no credit card required&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>stocks</category>
      <category>api</category>
    </item>
  </channel>
</rss>
