<?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>Build an AI Financial Agent with EODHD's MCP Server</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Fri, 14 Aug 2026 17:48:59 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/build-an-ai-financial-agent-with-eodhds-mcp-server-4pjc</link>
      <guid>https://dev.to/kevin_menesesgonzlez/build-an-ai-financial-agent-with-eodhds-mcp-server-4pjc</guid>
      <description>&lt;p&gt;Most people building financial AI agents connect four or five APIs by hand.&lt;/p&gt;

&lt;p&gt;One for prices. One for fundamentals. One for news. One for earnings dates.&lt;/p&gt;

&lt;p&gt;The problem isn't lack of data. It's fragmentation.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;building a research copilot for equities,&lt;/li&gt;
&lt;li&gt;automating pre-earnings screening,&lt;/li&gt;
&lt;li&gt;or designing an agent that reasons across price, fundamentals, and sentiment,&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Here's how to build an AI financial agent that pulls all four together, using EODHD's financial data API as the backbone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Traditional Financial Data API Approach Breaks Down Fast
&lt;/h2&gt;

&lt;p&gt;Here's what building a financial agent usually looks like with a plain REST API.&lt;/p&gt;

&lt;p&gt;You write a function to fetch prices. Another to fetch fundamentals. Another for news. Then you write orchestration logic so your agent knows which endpoint to call, in what order, and how to merge the responses into something the model can reason about.&lt;/p&gt;

&lt;p&gt;Each new data type means a new wrapper function.&lt;/p&gt;

&lt;p&gt;Each wrapper function is a new failure point.&lt;/p&gt;

&lt;p&gt;And every time the agent needs to answer a question that spans two data types, like "is this company's fundamentals strong enough to justify holding through earnings," you're the one stitching the context together, not the model.&lt;/p&gt;

&lt;p&gt;Developers usually discover this the hard way. The agent works fine in a demo with one data source. Then a real question comes in that needs prices, fundamentals, and news at once, and the whole orchestration layer has to be rebuilt.&lt;/p&gt;

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

&lt;p&gt;Financial agents don't need more APIs.&lt;/p&gt;

&lt;p&gt;They need a way to reach every data type through one consistent interface, so the model can decide what to call and combine it, instead of you hardcoding that logic in advance.&lt;/p&gt;

&lt;p&gt;That's what MCP (Model Context Protocol) is built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MCP Server Changes for Financial Data APIs
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) standardizes how an AI model discovers and calls external tools. Instead of you writing a custom wrapper for every endpoint, the model sees a list of available tools with their schemas, and decides which ones to call based on the question it's answering. It's the same underlying idea as Claude function calling, just extended to a whole catalog of tools instead of one function at a time.&lt;/p&gt;

&lt;p&gt;For financial data, this matters because a single question rarely maps to a single endpoint.&lt;/p&gt;

&lt;p&gt;EODHD ships an official MCP server with over 70 tools covering fundamentals, historical and real-time prices, earnings calendars, news, and sentiment. You point your agent at the server, and it gets access to all of it without you writing a single API wrapper.&lt;/p&gt;

&lt;blockquote&gt;
&lt;strong&gt;Skip the wrapper functions&lt;/strong&gt;&lt;br&gt;
EODHD's MCP server exposes 70+ financial data tools out of the box, ready for Claude and other AI agents.&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=build-ai-financial-agent-eodhd-mcp&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your EODHD API key&lt;/a&gt;&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Building the AI Financial Agent with EODHD's MCP Server
&lt;/h2&gt;

&lt;p&gt;Let's build an agent that can answer a question spanning multiple data types in a single call: fundamentals, upcoming earnings, and recent price action for a given ticker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;You'll need an EODHD API token and an Anthropic API key. EODHD's MCP server is available as a hosted endpoint, so there's no server to run yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the agent to EODHD's MCP server in Python
&lt;/h3&gt;

&lt;p&gt;EODHD's MCP server has two versions: v1 takes your API key directly in the URL, v2 uses OAuth 2.1 for clients that support it (like Claude Desktop). For a server-side script like this one, v1 is simpler.&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;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&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_ANTHROPIC_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;EODHD_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_EODHD_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&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="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me a quick pre-earnings health check on AAPL: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key fundamentals, the next earnings date, and how the &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stock has moved over the last month.&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="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.eodhd.com/v1/mcp?apikey=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EODHD_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="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;eodhd-mcp&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="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&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;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's actually happening here
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;mcp_servers&lt;/code&gt; parameter is the whole trick. You're not calling three endpoints and merging the results yourself. You're handing the model a connection to EODHD's tool catalog and letting it decide what it needs.&lt;/p&gt;

&lt;p&gt;For this one prompt, the model will typically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call a fundamentals tool to pull margins, revenue growth, and valuation ratios&lt;/li&gt;
&lt;li&gt;Call an earnings calendar tool to find the next reporting date&lt;/li&gt;
&lt;li&gt;Call a historical prices tool to compute the recent price move&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three tool calls, one conversation, zero orchestration code written by you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the response
&lt;/h3&gt;

&lt;p&gt;The API returns a mix of content blocks: &lt;code&gt;text&lt;/code&gt; for the model's answer, &lt;code&gt;mcp_tool_use&lt;/code&gt; for each tool it called, and &lt;code&gt;mcp_tool_result&lt;/code&gt; for the raw data. If you want to inspect what the agent actually pulled (useful for debugging or logging), filter by block type instead of assuming a fixed order:&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;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;b&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&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;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_tool_use&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;call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_calls&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;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is worth doing early. It shows you exactly which tools the model reached for, which helps you spot when it's calling something unnecessary or missing a data type your prompt implied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring Stock Opportunities with EODHD's Financial Data API
&lt;/h2&gt;

&lt;p&gt;The MCP agent above decides on its own which EODHD tools to call. That's convenient, but it also hides what's happening. If you want to actually understand (and customize) the logic behind "which stocks look like good opportunities," it helps to call the same EODHD endpoints directly and build the scoring yourself.&lt;/p&gt;

&lt;p&gt;This is also useful if you want a scheduled script that scans the market every morning instead of waiting for you to ask a chat agent.&lt;/p&gt;

&lt;p&gt;Let's build that: a short pipeline that pulls candidates from the Screener API, pulls fundamentals and recent price action for each one, checks the news sentiment, and combines all of it into a single opportunity score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Shortlist candidates with the Screener API
&lt;/h3&gt;

&lt;p&gt;Instead of pulling fundamentals for thousands of tickers, start narrow. The Screener API filters the entire market down to a shortlist in one 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;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&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_candidates&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://eodhd.com/api/screener&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;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;filters&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="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;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;sector&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;Technology&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="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;20&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="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="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="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;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_candidates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&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;tickers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pulls US tech stocks above $1B market cap, sorted by size. Swap the filters for whatever criteria define a "good price" to you: low P/E, high dividend yield, positive EPS growth, or a 52-week low signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Pull fundamentals for each candidate
&lt;/h3&gt;

&lt;p&gt;Once you have a shortlist, fetch the fundamentals for each ticker. This is where you check whether the price is actually backed by solid financials, not just cheap for a reason.&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;get_fundamentals&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="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/fundamentals/&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;.US&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="n"&gt;data&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Highlights&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;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;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&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;PERatio&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;peg_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&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;PEGRatio&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;profit_margin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&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;ProfitMargin&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;eps_growth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&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;EPSEstimateNextYear&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Check recent price action
&lt;/h3&gt;

&lt;p&gt;Fundamentals tell you if a company is healthy. Price history tells you if the market has already priced that in, or if there's a gap worth paying attention to.&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;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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_price_trend&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="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;.US&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&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="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="p"&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="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&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="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="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;prices&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="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;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_close&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="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;month_ago_close&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="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;21&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="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;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="k"&gt;else&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;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="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="n"&gt;change_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;last_close&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;month_ago_close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;month_ago_close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

    &lt;span class="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;last_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;last_close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stock with strong fundamentals that just dropped 15% in a month is a very different opportunity than one that already ran up 40%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Read the news sentiment
&lt;/h3&gt;

&lt;p&gt;Price and fundamentals don't tell you why a stock moved. Sentiment fills that gap, and it's often what separates a real opportunity from a value trap.&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;get_sentiment&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="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://eodhd.com/api/sentiments&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;s&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;symbol&lt;/span&gt;&lt;span class="si"&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="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;from&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;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="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&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="nf"&gt;isoformat&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="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;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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;symbol&lt;/span&gt;&lt;span class="si"&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="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;entries&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;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;normalized&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;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&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;entries&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;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Combine everything into a single score
&lt;/h3&gt;

&lt;p&gt;This is the part a raw API can't do for you. Each endpoint gives you one dimension. Deciding what a "good opportunity" means is a judgment call, and it belongs in your code, not buried in someone else's black-box score.&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;score_opportunity&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="n"&gt;fundamentals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fundamentals&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="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_price_trend&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="n"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sentiment&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="n"&gt;score&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;if&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&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="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profit_margin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profit_margin&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="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&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="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;score_opportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ranked&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;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&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;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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;score:&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&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;| P/E:&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&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;| 30d change:&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| sentiment:&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&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;Four data points, one loop, one ranked list. Reasonable valuation, healthy margins, a recent dip, and improving sentiment together are a much stronger signal than any one of them alone.&lt;/p&gt;

&lt;p&gt;This scoring logic is intentionally simple. You'll want to weight it differently depending on your strategy: a value investor cares more about P/E and margins, a swing trader cares more about the price drop and sentiment shift. The point is that the four EODHD endpoints give you the raw material, and the decision logic is yours to tune.&lt;/p&gt;

&lt;p&gt;None of this is financial advice. It's a framework for turning scattered data into a shortlist worth researching further, not a signal to buy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring this back into the agent
&lt;/h3&gt;

&lt;p&gt;Once this scoring pipeline works as a standalone script, you can expose it to your MCP agent as a custom tool alongside EODHD's built-in ones. Then a prompt like "find me tech stocks that dropped recently but still look fundamentally solid" runs your exact scoring logic instead of the model guessing at criteria on its own.&lt;/p&gt;

&lt;p&gt;That's the real advantage of combining direct API calls with MCP: you get full control over the decision logic, and the model still handles the natural-language layer on top of it.&lt;/p&gt;

&lt;p&gt;This is, in practice, what it means to build an AI financial agent: not a single clever prompt, but a data layer you trust plus a model that knows when to reach for it.&lt;/p&gt;

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

&lt;p&gt;Once the agent can combine fundamentals, prices, earnings, and news on its own, the use cases stop being single-question demos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pre-earnings screeners that flag weak fundamentals before a reporting date&lt;/li&gt;
&lt;li&gt;sentiment-aware alerts that combine news tone with price moves&lt;/li&gt;
&lt;li&gt;research copilots that answer multi-part questions without a rigid script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires new wrapper code. It requires better prompts and, occasionally, a narrower system prompt telling the agent which tools to prioritize.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Traditional REST integrations force you to write and maintain orchestration logic for every combination of data types&lt;/li&gt;
&lt;li&gt;MCP moves that orchestration into the model itself, so one conversation can span fundamentals, prices, earnings, and news&lt;/li&gt;
&lt;li&gt;Combining the Screener, fundamentals, price, and sentiment endpoints directly gives you full control over what counts as a "good opportunity," instead of relying on a single metric&lt;/li&gt;
&lt;li&gt;EODHD's MCP server gives you 70+ financial tools without writing a single wrapper function, and you can add your own scoring logic as a custom tool on top of it&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;❓ &lt;strong&gt;Do I need to run my own MCP server to use EODHD's tools?&lt;/strong&gt;&lt;br&gt;
✅ No. EODHD hosts the MCP server, so you connect to it with a URL and your API token, the same way you'd call a REST endpoint.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Does MCP replace the EODHD REST API?&lt;/strong&gt;&lt;br&gt;
✅ No, it sits on top of it. The REST API still powers every tool call, MCP just gives the AI model a structured way to discover and use those endpoints without custom wrapper code.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I limit which tools the agent has access to?&lt;/strong&gt;&lt;br&gt;
✅ Yes. You can scope the system prompt or restrict the conversation to specific tool categories if you don't want the agent reaching for endpoints outside a given use case, like keeping it to fundamentals and earnings only.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is this approach only useful with Claude?&lt;/strong&gt;&lt;br&gt;
✅ No. MCP is an open protocol, so EODHD's server works with any MCP-compatible client, including ChatGPT, Cursor, and Windsurf, not just Claude.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;How many API calls does a scan like this use?&lt;/strong&gt;&lt;br&gt;
✅ It depends on your shortlist size. Each screener request counts as 5 calls, and each fundamentals, price, or sentiment call for a ticker counts as 1. Scanning 20 candidates works out to roughly 65 calls, well within EODHD's free tier for occasional runs.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I use this scoring script to automate actual trades?&lt;/strong&gt;&lt;br&gt;
✅ The script only ranks and prints candidates. Connecting it to a broker's execution API is a separate step, and it's worth adding manual review before any live-money decision, no matter how the shortlist was generated.&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;blockquote&gt;
&lt;strong&gt;Want more content like this?&lt;/strong&gt;&lt;br&gt;
Python, APIs, fintech, and AI agent tutorials for developers.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Visit kevinmeneses.com&lt;/a&gt;&lt;/strong&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>agents</category>
      <category>stocks</category>
      <category>api</category>
    </item>
    <item>
      <title>Best 5 Browser Automation API for AI Agents in 2026</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Thu, 06 Aug 2026 08:04:53 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/best-5-browser-automation-api-for-ai-agents-in-2026-5hcg</link>
      <guid>https://dev.to/kevin_menesesgonzlez/best-5-browser-automation-api-for-ai-agents-in-2026-5hcg</guid>
      <description>&lt;p&gt;Every team building browser automation hits the same fork in the road. Manage headless Chrome yourself, or hand the whole problem to an API.&lt;/p&gt;

&lt;p&gt;Browserless.io built this category. It turned "run a headless browser at scale" from a DevOps project into a single endpoint.&lt;/p&gt;

&lt;p&gt;The question worth asking in 2026 isn't whether Browserless works. It's what actually competes with it, and when one of those alternatives fits your stack better.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;scraping data at scale,&lt;/li&gt;
&lt;li&gt;running AI agents that need to browse the web,&lt;/li&gt;
&lt;li&gt;or automating screenshots, PDFs, and testing pipelines,&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  What a Managed Headless Browser API Actually Needs to Solve
&lt;/h2&gt;

&lt;p&gt;A "headless browser API" sounds simple on paper. Send a URL, get back HTML, a screenshot, or a PDF.&lt;/p&gt;

&lt;p&gt;In production, the real problems show up fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browsers crash and leak memory under load&lt;/li&gt;
&lt;li&gt;Websites detect and block automated traffic&lt;/li&gt;
&lt;li&gt;CAPTCHAs interrupt otherwise working scripts&lt;/li&gt;
&lt;li&gt;Sessions need to be debugged when an agent silently fails&lt;/li&gt;
&lt;li&gt;Scaling from 10 to 10,000 concurrent sessions breaks naive setups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every vendor in this space is really selling the same thing: someone else managing that complexity so you don't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Differentiator Isn't the Feature List
&lt;/h2&gt;

&lt;p&gt;Most comparison articles line up checkboxes. Stealth mode, yes or no. CAPTCHA solving, yes or no. Proxy rotation, yes or no.&lt;/p&gt;

&lt;p&gt;That's not where these tools actually diverge.&lt;/p&gt;

&lt;p&gt;The real differentiator is production reliability, plus how a vendor responds when something breaks at 2am on a Sunday. A landing page can promise stealth browsing. It can't promise someone will answer your support ticket in twenty minutes.&lt;/p&gt;

&lt;p&gt;That's the lens for this comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Headless Browser APIs Worth Evaluating in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Browserless.io
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless.io&lt;/a&gt; provides managed headless browser infrastructure built specifically for developers and engineering teams.&lt;/p&gt;

&lt;p&gt;Its core offerings are BrowserQL (a stealth-first query language for bot bypass and structured data extraction), Browsers as a Service (a pooled cloud of Chromium, Firefox, and WebKit instances compatible with Puppeteer and Playwright), and straightforward REST APIs for screenshots, PDFs, and scraping. It also solves CAPTCHAs automatically, including reCAPTCHA, hCaptcha, and Cloudflare Turnstile.&lt;/p&gt;

&lt;p&gt;The platform abstracts away browser lifecycle management: version updates, memory leaks, scaling, and detection avoidance become one API endpoint instead of a maintenance burden.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;BrowserQL gives you stealth and structured extraction without hand-rolling anti-detection logic&lt;/li&gt;
&lt;li&gt;Supports Puppeteer, Playwright, and Selenium with a one-line connection change&lt;/li&gt;
&lt;li&gt;Self-hosting is available (through providers like Sliplane) if you want fixed infrastructure costs instead of per-unit billing&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;Paid plans range from $25/month (Prototyping) up to $350/month (Scale), and cost can climb quickly with concurrency&lt;/li&gt;
&lt;li&gt;The free tier caps out at 1,000 units/month, which disappears fast in a real scraping job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that need full headless browser control (Puppeteer or Playwright scripts, PDF generation, multi-page navigation flows) without building that infrastructure themselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Looking for technical content that explains your API like this?&lt;/strong&gt;&lt;br&gt;
I write educational, SEO-optimized articles for developer tools and infrastructure companies, not marketing fluff.&lt;br&gt;
&lt;strong&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;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Hyperbrowser.ai
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.hyperbrowser.ai/" rel="noopener noreferrer"&gt;Hyperbrowser.ai&lt;/a&gt; positions itself as internet infrastructure built specifically for AI agents rather than general-purpose scraping.&lt;/p&gt;

&lt;p&gt;It provides instant, scalable browser infrastructure with built-in CAPTCHA solving, proxy management, and anti-bot detection, plus HyperAgent, an AI-native automation framework that layers natural language commands (&lt;code&gt;page.ai()&lt;/code&gt;, &lt;code&gt;page.extract()&lt;/code&gt;) on top of standard Playwright.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Built around agent workflows from day one, not scraping retrofitted for agents&lt;/li&gt;
&lt;li&gt;Falls back to regular Playwright when you don't need the AI layer, so you're not locked into one paradigm&lt;/li&gt;
&lt;li&gt;Backed by Y Combinator, Accel, and SV Angel, with active engineering hiring&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;Small team (4 employees as of 2026), which means less dedicated support bandwidth than a larger vendor&lt;/li&gt;
&lt;li&gt;Younger platform with a smaller track record at large scale compared to Browserless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building AI agents that need to browse and extract data using natural language instructions instead of brittle selector-based scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Steel.dev
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://steel.dev/" rel="noopener noreferrer"&gt;Steel.dev&lt;/a&gt; is an open-source browser API purpose-built for AI agents, with a clear focus on speed and observability.&lt;/p&gt;

&lt;p&gt;Its published lifecycle benchmark shows Steel completing a create-connect-navigate-release cycle in an average of 0.89 seconds, which the team reports as 1.70x to 8.95x faster than other providers tested under the same conditions. It adds live session viewers, MP4 replays, and Agent Logs, a timeline of every click, navigation, and scroll an agent takes.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Open source, so you can self-host and audit exactly what's running&lt;/li&gt;
&lt;li&gt;Agent Logs and session replay make debugging failed automation runs far less painful&lt;/li&gt;
&lt;li&gt;Fast session starts matter directly for agent workloads that spin up and tear down browsers repeatedly&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;Early-stage company; smaller team and less enterprise track record than Browserless&lt;/li&gt;
&lt;li&gt;Fewer built-in integrations outside the core Puppeteer/Playwright/Selenium compatibility layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that want an open-source, self-hostable browser API and care about debugging tools as much as raw automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Surfsky.io
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://surfsky.io/" rel="noopener noreferrer"&gt;Surfsky.io&lt;/a&gt; is built around one specific problem: bypassing advanced anti-bot systems that block most standard headless browser setups.&lt;/p&gt;

&lt;p&gt;It targets CAPTCHA, WAF, and bot-detection bypass as its primary differentiator, positioning itself directly against Browserbase and ZenRows on bypass reliability rather than general feature parity. The platform offers an API-first architecture designed to integrate with existing automation frameworks.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Purpose-built for the hardest scraping targets: sites with aggressive anti-bot protection&lt;/li&gt;
&lt;li&gt;Cloud-based infrastructure designed for scale without manual proxy management&lt;/li&gt;
&lt;li&gt;API-first design means less glue code to plug it into an existing pipeline&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;Narrower focus than Browserless. Less useful if PDF generation, screenshots, or general testing automation are your primary use case&lt;/li&gt;
&lt;li&gt;Smaller, less established brand compared to the other tools on this list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams whose main blocker is bypassing sophisticated bot detection on specific high-value targets, not general-purpose browser automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Gaffa
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gaffa.dev/" rel="noopener noreferrer"&gt;Gaffa&lt;/a&gt; is a lightweight API for web scraping and browser automation aimed at developers who want one API call instead of managing browser infrastructure.&lt;/p&gt;

&lt;p&gt;It handles residential proxies, scaling, and failure recovery internally, and supports actions like scrolling, clicking, and extracting page data as images, Markdown, HTML, or LLM-structured JSON. Credits are charged for actual browser execution time and bandwidth, with no fixed infrastructure costs.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Simple, single-endpoint design with low setup overhead&lt;/li&gt;
&lt;li&gt;Native output as clean Markdown or JSON, useful for feeding LLM and RAG pipelines directly&lt;/li&gt;
&lt;li&gt;Pay-for-usage pricing with no fixed infrastructure fees&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;Indie, early-stage project with a very small team, so support and roadmap velocity are unproven at scale&lt;/li&gt;
&lt;li&gt;Fewer public case studies or production references compared to established vendors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; solo developers or small teams that want a minimal, low-overhead scraping and automation API without evaluating a full enterprise platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless Browser API Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Team size&lt;/th&gt;
&lt;th&gt;Pricing model&lt;/th&gt;
&lt;th&gt;Stealth / anti-bot&lt;/th&gt;
&lt;th&gt;Self-host option&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;&lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Established&lt;/td&gt;
&lt;td&gt;$25 to $350/month tiers, free tier (1,000 units)&lt;/td&gt;
&lt;td&gt;BrowserQL stealth mode, CAPTCHA solving&lt;/td&gt;
&lt;td&gt;Yes (via Sliplane, ~€9/month)&lt;/td&gt;
&lt;td&gt;Full Puppeteer/Playwright control at production scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.hyperbrowser.ai/" rel="noopener noreferrer"&gt;Hyperbrowser.ai&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~4 employees&lt;/td&gt;
&lt;td&gt;Usage-based, free to start&lt;/td&gt;
&lt;td&gt;Built-in CAPTCHA + anti-bot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;AI-agent-native automation with natural language commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://steel.dev/" rel="noopener noreferrer"&gt;Steel.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Early-stage&lt;/td&gt;
&lt;td&gt;Open source, self-hosted or managed&lt;/td&gt;
&lt;td&gt;Stealth browsing patches&lt;/td&gt;
&lt;td&gt;Yes (open source)&lt;/td&gt;
&lt;td&gt;Self-hosted browser API with strong debugging tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://surfsky.io/" rel="noopener noreferrer"&gt;Surfsky.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Not publicly listed&lt;/td&gt;
&lt;td&gt;Advanced anti-bot / WAF bypass&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Bypassing aggressive bot detection on specific targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gaffa.dev/" rel="noopener noreferrer"&gt;Gaffa&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Indie / micro-team&lt;/td&gt;
&lt;td&gt;Pay-per-execution, no fixed cost&lt;/td&gt;
&lt;td&gt;Stealth mode with residential proxies&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Solo devs needing a simple, low-overhead API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Browserless.io remains the safest default pick if you need full browser control (Puppeteer, Playwright, PDF generation) at production scale, backed by a mature platform and a self-hosting path.&lt;/li&gt;
&lt;li&gt;If your workload is AI-agent-specific, Hyperbrowser.ai and Steel.dev are worth testing for their agent-native tooling and debugging features.&lt;/li&gt;
&lt;li&gt;If bot detection bypass is your single hardest problem, Surfsky.io is a narrower but more targeted option than a general-purpose platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller vendors on this list (Hyperbrowser, Steel.dev, Surfsky.io, Gaffa) can move faster on support and roadmap requests precisely because they're small. That's a real advantage, but it comes with less production history than Browserless.io has built over time.&lt;/p&gt;

&lt;p&gt;For most teams evaluating a headless browser API in 2026, that trade-off, and not the feature list, is the decision that actually matters.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Building a developer tool in this space?&lt;/strong&gt;&lt;br&gt;
I create technical, SEO-optimized content like this comparison for API and infrastructure companies. If that's useful for your product, let's talk.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Connect with me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More technical writing and projects:&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>ai</category>
      <category>api</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to Build an AI Trading Bot with Claude and the EODHD API</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:52:53 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-to-build-an-ai-trading-bot-with-claude-and-the-eodhd-api-1l2a</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-to-build-an-ai-trading-bot-with-claude-and-the-eodhd-api-1l2a</guid>
      <description>&lt;p&gt;Most trading bots are just if-statements wearing a costume.&lt;/p&gt;

&lt;p&gt;RSI below 30, buy. Moving averages cross, sell. That works fine until the market does something the rules didn't anticipate, and then the bot keeps executing the same logic anyway, because it can't actually think.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;building AI agents that need to reason over structured data,&lt;/li&gt;
&lt;li&gt;exploring what LLMs can actually do in a finance context,&lt;/li&gt;
&lt;li&gt;or evaluating how to combine market data with automated decision-making,&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  The problem with rule-based bots
&lt;/h2&gt;

&lt;p&gt;A rule-based bot doesn't understand the market. It pattern-matches against a formula someone wrote months ago.&lt;/p&gt;

&lt;p&gt;Earnings surprise. A sudden news event. A sector rotation nobody coded for. The bot has no way to account for any of it, because it was never built to interpret context, only to check conditions.&lt;/p&gt;

&lt;p&gt;Developers usually discover this the hard way. They backtest a strategy, watch it perform well on historical data, deploy it, and then spend the next few months tweaking thresholds every time the market shifts. The bot isn't wrong. It's just blind.&lt;/p&gt;

&lt;p&gt;There's also the data problem underneath all of this. Plenty of hobby projects lean on scraped Yahoo Finance endpoints or free tiers that cap out fast, and both tend to fail exactly when you need them most.&lt;/p&gt;

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

&lt;p&gt;A trading bot doesn't need more rules. It needs reasoning.&lt;/p&gt;

&lt;p&gt;That's a different kind of system entirely. Instead of "if X then Y," you want something closer to "given this data, what would a reasonable analyst conclude, and why."&lt;/p&gt;

&lt;p&gt;Large language models are good at exactly that kind of contextual judgment, as long as you feed them clean, structured data instead of asking them to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;This tutorial combines two pieces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EODHD API&lt;/strong&gt; for market data. End-of-day prices, real-time quotes, fundamentals, and historical data through a single REST interface, without maintaining scrapers that break every time a website changes its HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; for the reasoning layer. Given a snapshot of price action, volume, and basic fundamentals, Claude produces a structured decision (buy, hold, sell) along with the reasoning behind it, in a format your code can actually parse and act on.&lt;/p&gt;

&lt;p&gt;You could swap either piece out. The point isn't "use these exact two tools forever," it's showing how the pattern works so you can adapt it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want reliable market data without building your own scraping layer?&lt;/strong&gt;&lt;br&gt;
EODHD covers over 150,000 tickers with a generous free tier and no rate-limit surprises mid-project.&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=ai-trading-bot-claude-eodhd-api&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your free EODHD API key →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting up
&lt;/h2&gt;

&lt;p&gt;You'll need two API keys: one from EODHD, one from Anthropic.&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 anthropic python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store your keys in a &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EODHD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_eodhd_key_here
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_anthropic_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Pull market data from EODHD
&lt;/h2&gt;

&lt;p&gt;Start with a function that grabs recent price history and a live quote for a given ticker.&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;os&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;EODHD_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;EODHD_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;def&lt;/span&gt; &lt;span class="nf"&gt;get_market_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="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;symbol&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;ticker&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="c1"&gt;# Last 30 days of end-of-day prices
&lt;/span&gt;    &lt;span class="n"&gt;eod_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;eod_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;EODHD_KEY&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="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;order&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="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;eod_resp&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;eod_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;eod_params&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Live quote
&lt;/span&gt;    &lt;span class="n"&gt;quote_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/real-time/&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;quote_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;EODHD_KEY&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="n"&gt;quote_resp&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;quote_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;quote_params&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;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;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quote_resp&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;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;change_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quote_resp&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;change_p&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="n"&gt;quote_resp&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;volume&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;recent_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;eod_resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clean payload: current price, percentage change, volume, and 30 days of history. No scraping, no broken HTML selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Ask Claude to reason over the data
&lt;/h2&gt;

&lt;p&gt;This is the part that separates it from a rule-based bot. Instead of hardcoding thresholds, you hand Claude the data and ask for a structured judgment call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&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;Anthropic&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;ANTHROPIC_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;def&lt;/span&gt; &lt;span class="nf"&gt;get_trading_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_data&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="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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a trading analyst reviewing market data for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_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;ticker&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;.

Current price: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_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;current_price&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;
Change today: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_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;change_pct&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;%
Volume: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_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;volume&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;
Recent 30-day history: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_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;recent_history&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Based on this data, provide a trading decision. Respond ONLY with valid JSON in this exact format:
{{
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;buy&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;hold&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;sell&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 0.0 to 1.0,
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&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;2-3 sentence explanation grounded in the data provided&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;raw_text&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="n"&gt;content&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;text&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail here is the response format. Asking for structured JSON, not a paragraph, is what makes this usable in an actual pipeline instead of a chat window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Wire it together
&lt;/h2&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;run_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_market_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;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_trading_decision&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="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="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — Decision: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&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="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;Confidence: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;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;Reasoning: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decision&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;run_analysis&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AAPL — Decision: HOLD
Confidence: 0.62
Reasoning: Price is up 0.8% on below-average volume, suggesting limited
conviction behind the move. Recent history shows consolidation rather than
a clear trend, so waiting for a volume-confirmed breakout makes more sense
than acting now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reasoning field is the whole point. You get a decision plus the logic behind it, which you can log, review, and adjust over time. A rule-based bot never gives you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it without risking real money
&lt;/h2&gt;

&lt;p&gt;Before connecting this to anything resembling a real account, run it against a paper trading environment.&lt;/p&gt;

&lt;p&gt;Alpaca is a solid option here. Its paper trading API mirrors the live trading endpoints exactly, so you can route Claude's decisions through simulated buy and sell orders and see how the strategy would have performed, with fake money and real market conditions.&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;alpaca_trade_api&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tradeapi&lt;/span&gt;

&lt;span class="n"&gt;alpaca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tradeapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;ALPACA_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;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;ALPACA_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://paper-api.alpaca.markets&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;def&lt;/span&gt; &lt;span class="nf"&gt;execute_paper_trade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hold&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;No action for &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="s"&gt;, holding position.&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;side&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&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;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&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;sell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;alpaca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&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;qty&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;side&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;time_in_force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paper &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; order submitted for &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="s"&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;Run &lt;code&gt;execute_paper_trade&lt;/code&gt; after &lt;code&gt;get_trading_decision&lt;/code&gt; and you have a full loop: EODHD for data, Claude for reasoning, Alpaca for execution, all without a single dollar at risk.&lt;/p&gt;

&lt;p&gt;This is also where you'd start tracking accuracy. Log every decision, compare it against what actually happened three or five days later, and you'll quickly see whether the reasoning holds up or needs a better prompt.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;a scheduler that runs this analysis daily across a watchlist&lt;/li&gt;
&lt;li&gt;a logging layer that scores decisions against actual outcomes&lt;/li&gt;
&lt;li&gt;a risk filter that blocks trades below a confidence threshold&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;An LLM adds contextual reasoning that fixed rules can't replicate on their own.&lt;/p&gt;

&lt;p&gt;Clean, reliable market data matters more than people expect. Claude is only as good as what you feed it, and EODHD removes the guesswork of scraping or rate-limited free APIs.&lt;/p&gt;

&lt;p&gt;This is a starting point, not a production system. Position sizing, stop losses, and proper risk management live outside the scope of this tutorial, and skipping them before going live is how paper gains turn into real losses.&lt;/p&gt;

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

&lt;p&gt;❓ Do I need trading experience to build this?&lt;br&gt;
✅ No. You need basic Python and an understanding of what a buy, hold, or sell decision means. The reasoning comes from Claude, not from you having to encode strategy logic manually.&lt;/p&gt;

&lt;p&gt;❓ Is this real algorithmic trading?&lt;br&gt;
✅ It's a form of it, specifically an LLM-assisted decision layer rather than a pure quantitative model. Traditional algo trading uses fixed mathematical rules; this approach adds a reasoning step on top of the data.&lt;/p&gt;

&lt;p&gt;❓ Can I connect this to a real brokerage account?&lt;br&gt;
✅ Technically yes, since Alpaca's live and paper APIs share the same structure. Don't, until you've backtested extensively and added proper risk controls. This tutorial is educational, not a plug-and-play trading system.&lt;/p&gt;

&lt;p&gt;❓ Does EODHD provide real-time data or only end-of-day?&lt;br&gt;
✅ Both. EODHD offers end-of-day historical data going back years, plus real-time and delayed quotes depending on your plan, which is why it works for both the historical context and the live price checks in this tutorial.&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;Kevin Meneses González&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Building something with market data?&lt;/strong&gt;&lt;br&gt;
EODHD gives you 30+ years of historical data, real-time quotes, and fundamentals in one API.&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=ai-trading-bot-claude-eodhd-api&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Start free with EODHD →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More tutorials like this&lt;/strong&gt;&lt;br&gt;
I write about fintech APIs, Python, and AI agents every week.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Read more on 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>ai</category>
      <category>api</category>
      <category>stocks</category>
      <category>trading</category>
    </item>
    <item>
      <title>7 Best OCR Tools with AI in 2026 (Compared)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 04 Aug 2026 08:18:47 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/7-best-ocr-tools-with-ai-in-2026-compared-1189</link>
      <guid>https://dev.to/kevin_menesesgonzlez/7-best-ocr-tools-with-ai-in-2026-compared-1189</guid>
      <description>&lt;p&gt;Every "best OCR tool" article online is one of two things: a vendor's own product page, or a listicle that copied the same five names from another listicle. Neither tells you which tool actually fits what you're building.&lt;/p&gt;

&lt;p&gt;This one does.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;digitizing scanned paperwork for a small team,&lt;/li&gt;
&lt;li&gt;building a document pipeline that needs to run unattended,&lt;/li&gt;
&lt;li&gt;or deciding whether an OCR API is worth the integration work,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this comparison is built around those three decisions, not around which vendor has the biggest marketing budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  OCR still fails in predictable ways
&lt;/h2&gt;

&lt;p&gt;Optical character recognition has gotten good enough that most people assume it just works now. Feed it a document, get clean text back.&lt;/p&gt;

&lt;p&gt;That assumption breaks down fast on three kinds of input: a photo taken at an angle under bad light, a PDF with a table instead of plain paragraphs, and anything with handwriting. Vendor pages rarely mention this. They quote a single accuracy number, usually from a clean, high-resolution test document that looks nothing like what actually lands in most inboxes.&lt;/p&gt;

&lt;p&gt;The other thing nobody advertises up front: uploading a document to a free online OCR tool means sending its content to someone else's server. For a random screenshot, that's irrelevant. For an ID, a payslip, or a signed contract, it's the first question you should be asking, not the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real decision isn't "which OCR is best"
&lt;/h2&gt;

&lt;p&gt;It's whether you need a one-off web tool, an API you call from code, or something self-hosted that never leaves your machine.&lt;/p&gt;

&lt;p&gt;That single choice determines cost, speed, and how much control you have over where your data goes, more than any accuracy benchmark does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Typical cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web tool (Adobe, Google Lens)&lt;/td&gt;
&lt;td&gt;Upload a file, it's processed on the vendor's server, download the result&lt;/td&gt;
&lt;td&gt;One-off use, low volume&lt;/td&gt;
&lt;td&gt;Free with limits, or a paid plan for full features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API (Google Vision, Mistral OCR, Mindee, Unstract)&lt;/td&gt;
&lt;td&gt;You call it from your own app or pipeline&lt;/td&gt;
&lt;td&gt;Volume, automation, product integration&lt;/td&gt;
&lt;td&gt;Per page or per call, usually a few cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted (Tesseract)&lt;/td&gt;
&lt;td&gt;Runs on your own machine or server, nothing gets uploaded&lt;/td&gt;
&lt;td&gt;Full data control, no recurring fee&lt;/td&gt;
&lt;td&gt;Free, but you handle setup and tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you know which lane you're in, picking between tools gets a lot faster. Here are the 7 worth knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Google Cloud Vision API: the default cloud OCR
&lt;/h2&gt;

&lt;p&gt;Vision API is Google's general-purpose image analysis service, and text detection is one feature inside it, alongside label and object detection.&lt;/p&gt;

&lt;p&gt;Google Vision is built for general OCR and image analysis, while specialized services like AWS Textract focus on structured document extraction such as tables and invoices. That distinction matters: Vision reads text well, but it returns raw text and bounding boxes, not structured fields. Pricing runs on a free tier of 1,000 images a month, then roughly $1.50 per 1,000 images for standard text detection, with a separate and considerably more expensive Document AI product for forms and tables.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Backed by Google's infrastructure, so uptime and language coverage are strong&lt;/li&gt;
&lt;li&gt;Cheap at low-to-mid volume for plain text extraction&lt;/li&gt;
&lt;li&gt;Well-documented Python SDK&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;Billing is split across features and products, and it's easy to underestimate cost once you add Document AI for tables&lt;/li&gt;
&lt;li&gt;No built-in structured output for invoices or forms without the pricier add-on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who need reliable text extraction from images or scans and are comfortable managing a GCP project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/vision" rel="noopener noreferrer"&gt;cloud.google.com/vision&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Mistral OCR: the AI-native option built for RAG pipelines
&lt;/h2&gt;

&lt;p&gt;Mistral built its OCR model specifically around modern document AI use cases: feeding clean, structured text into retrieval pipelines and AI agents.&lt;/p&gt;

&lt;p&gt;Mistral OCR handles complex document elements including interleaved images, mathematical expressions, tables, and layouts like LaTeX formatting, and supports over 170 languages. It's priced per page rather than per token, which keeps costs predictable at scale, and a self-hosted deployment option exists for organizations that need to keep sensitive documents off shared infrastructure.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Markdown output with reconstructed tables, ready to drop into a RAG pipeline&lt;/li&gt;
&lt;li&gt;Flat per-page pricing instead of token-based billing&lt;/li&gt;
&lt;li&gt;Self-hosting available for privacy-sensitive workloads&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 product, so community resources and third-party tutorials are thinner than Google's or AWS's&lt;/li&gt;
&lt;li&gt;Overkill if you just need plain text from a handful of documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building AI agents or RAG systems that need documents converted into clean, structured markdown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mistral.ai/" rel="noopener noreferrer"&gt;mistral.ai&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. ABBYY: the enterprise veteran
&lt;/h2&gt;

&lt;p&gt;ABBYY has been in document recognition longer than most of the other names on this list, and it shows in how many languages and document types it handles out of the box.&lt;/p&gt;

&lt;p&gt;Its FineReader Engine SDK targets developers building OCR into desktop or server applications, while FineReader PDF is the consumer-facing product for individuals converting scans into editable files. Pricing for the SDK is quote-based and depends on volume and deployment, so there's no flat number to point to. The consumer product runs in the low hundreds of dollars per year.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Recognition quality across a very wide set of languages and scripts&lt;/li&gt;
&lt;li&gt;Long track record in regulated industries like finance and legal&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;SDK pricing requires a sales conversation, which slows down evaluation for smaller teams&lt;/li&gt;
&lt;li&gt;Feels dated next to API-first competitors built around REST and JSON from day one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; enterprises with existing document workflows in legal, finance, or healthcare that need broad language support and are willing to go through a sales process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.abbyy.com/ai-document-processing/api/" rel="noopener noreferrer"&gt;abbyy.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Mindee: developer-first document extraction
&lt;/h2&gt;

&lt;p&gt;Mindee sits closer to Mistral than to Google: it's built for developers who want structured fields back, not just raw text.&lt;/p&gt;

&lt;p&gt;It ships pre-trained models for invoices, receipts, IDs, and bank statements, so instead of parsing raw OCR output yourself, you get named fields like supplier name or total amount directly in the response. Pricing is usage-based, with a free tier for testing and a per-page rate that scales down as volume increases.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Pre-built models for common document types cut integration time significantly&lt;/li&gt;
&lt;li&gt;Clear, transparent per-page pricing published on the site&lt;/li&gt;
&lt;li&gt;EU-based, which matters for teams with GDPR requirements&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 flexible than a general OCR API if your documents don't fit the pre-trained categories&lt;/li&gt;
&lt;li&gt;Custom document types require more setup than the ready-made models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams processing a specific, repeatable document type (invoices, receipts, IDs) who want structured JSON without building their own parsing logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.mindee.com/pricing" rel="noopener noreferrer"&gt;mindee.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Unstract: open-source, no-code document pipelines
&lt;/h2&gt;

&lt;p&gt;Unstract takes a different approach entirely: instead of a fixed OCR model, you define what you want extracted using prompts, test them against sample documents, then deploy the result as an API or an automated pipeline.&lt;/p&gt;

&lt;p&gt;It's open source under AGPL-3.0, available self-hosted through Docker, as a managed cloud service, or on-premise for enterprise deployments. Its OCR preprocessor, LLMWhisperer, is built to preserve the layout of tables and columns before handing the text to an LLM, which is exactly the kind of detail that matters when a document isn't just paragraphs of text.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Prompt-based extraction adapts to new document layouts without retraining&lt;/li&gt;
&lt;li&gt;Open-source option removes vendor lock-in entirely&lt;/li&gt;
&lt;li&gt;Built-in connectors for ingesting from S3, Dropbox, or a data lake&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;The no-code/prompt-based workflow has a learning curve if you're used to a simple REST call&lt;/li&gt;
&lt;li&gt;Best results depend on which underlying LLM you pair it with, which adds a decision on top of the tool itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that need to extract structured data from varied or changing document layouts without hand-coding a parser for each one.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  6. Adobe Acrobat: the one everyone already has
&lt;/h2&gt;

&lt;p&gt;More people search for Adobe's OCR than for any other tool on this list, mostly because Acrobat is already installed somewhere in every office.&lt;/p&gt;

&lt;p&gt;OCR only ships in the Pro tier, not the free Reader. It converts scanned files into editable, searchable PDFs and can batch-process folders through Action Wizard. Adobe doesn't publish a specific accuracy figure the way ABBYY does, but it covers 50-plus languages with automatic detection, and the Pro plan runs close to $20 a month.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Already familiar to most office workers, so there's no learning curve&lt;/li&gt;
&lt;li&gt;Batch processing across folders is straightforward&lt;/li&gt;
&lt;li&gt;Output stays inside the PDF workflow most teams already use&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;No API for automated, high-volume processing, it's a desktop tool&lt;/li&gt;
&lt;li&gt;OCR is locked behind the paid Pro tier, not available in free Reader&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; individuals or small teams doing occasional OCR inside a document workflow they already run through Acrobat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.adobe.com/acrobat/how-to/ocr-software-convert-pdf-to-text.html" rel="noopener noreferrer"&gt;adobe.com/acrobat&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tesseract: free, self-hosted, no data leaves your machine
&lt;/h2&gt;

&lt;p&gt;Tesseract is the answer for anyone who read the privacy section above and decided nothing should leave their own infrastructure.&lt;/p&gt;

&lt;p&gt;It's open source under the Apache 2.0 license, was originally developed at HP and later taken over by Google, and has a fully featured API that can be compiled for a wide range of targets, including Android and iPhone. It supports over 100 languages out of the box and runs entirely on your own hardware. Python developers usually reach it through the &lt;code&gt;pytesseract&lt;/code&gt; wrapper.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Completely free, no per-page or per-call cost ever&lt;/li&gt;
&lt;li&gt;Nothing gets uploaded anywhere, which settles the privacy question by design&lt;/li&gt;
&lt;li&gt;Actively maintained, wide language support&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;Accuracy on messy scans or handwriting lags noticeably behind AI-native APIs&lt;/li&gt;
&lt;li&gt;You own the image preprocessing and language-model tuning yourself, that work doesn't disappear, it just moves to you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who need full control over where documents are processed and are willing to handle image preprocessing themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;github.com/tesseract-ocr/tesseract&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What no one else covers: what happens to your document
&lt;/h2&gt;

&lt;p&gt;Every free online OCR tool works the same way behind the scenes: your file gets uploaded to a server, processed, and the result gets sent back. What happens to the file after that depends entirely on the vendor's retention policy, and most people never check.&lt;/p&gt;

&lt;p&gt;Before uploading anything sensitive (an ID, a payslip, a signed contract) to a free web tool, three questions are worth answering:&lt;/p&gt;

&lt;p&gt;Does the provider delete the file immediately after processing, or keep it for some retention window? Where are the servers located, which matters for GDPR if you're in the EU and for general data-handling expectations everywhere else? And does the free tier reuse uploaded documents to train future models?&lt;/p&gt;

&lt;p&gt;If you can't find a clear answer to those three on the vendor's own site, treat that as the answer. For anything sensitive, either pick a tool with a documented deletion policy or process it locally with Tesseract, where the question doesn't even come up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need a technical writer who actually tests the tools?&lt;/strong&gt;&lt;br&gt;
I write developer-focused comparisons and tutorials for API and document-processing companies, not marketing copy dressed up as content.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three things to take away
&lt;/h2&gt;

&lt;p&gt;Volume decides more than accuracy does: below a few hundred documents a month, a free web tool or Tesseract covers it, above that, an API pays for itself in time saved.&lt;/p&gt;

&lt;p&gt;The advertised accuracy number is almost never the number you'll get on a bad scan or a handwritten form, budget for that gap when picking a tool for production use.&lt;/p&gt;

&lt;p&gt;Privacy isn't a footnote, it's a selection criterion. Know where your document goes before you upload it, not after.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What is OCR and what is it used for?&lt;/strong&gt;&lt;br&gt;
OCR (optical character recognition) extracts text from images or scanned documents so it can be edited, searched, or processed by software. It's used for digitizing paperwork, converting scanned PDFs into searchable files, and automating data entry from invoices, receipts, and forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between traditional OCR and AI-powered OCR?&lt;/strong&gt;&lt;br&gt;
Traditional OCR matches character shapes against a fixed pattern library, which struggles with handwriting, unusual fonts, and messy scans. AI-powered OCR uses machine learning models trained on huge datasets, which handles those same cases far better and can also understand document structure like tables and forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it safe to upload documents to an online OCR tool?&lt;/strong&gt;&lt;br&gt;
It depends on the provider's data retention policy. Free tools don't always delete files immediately or disclose where they're processed. For sensitive documents, check the vendor's privacy policy first or use a self-hosted option like Tesseract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the best free OCR tool?&lt;/strong&gt;&lt;br&gt;
Tesseract is the strongest free option if you're comfortable with some setup, since it runs entirely on your own machine. Google Vision and Mistral OCR both offer a limited free tier for testing before you need to pay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should I use an OCR API instead of a web tool?&lt;/strong&gt;&lt;br&gt;
Once you're processing documents regularly, automatically, or as part of a larger workflow, an API replaces manual uploads with a single function call and scales with your volume without extra manual work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Tesseract still a good option in 2026?&lt;/strong&gt;&lt;br&gt;
Yes, for teams that prioritize cost and data privacy over top-tier accuracy on difficult documents. It's less accurate than AI-native APIs on handwriting or low-quality scans, but it's free, self-hosted, and actively maintained.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Looking for technical content like this for your company?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I write comparisons and tutorials for API and document-processing companies.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;More about my work&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>pdf</category>
      <category>coding</category>
      <category>data</category>
    </item>
    <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>
  </channel>
</rss>
