<?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: Jeffrey Turov</title>
    <description>The latest articles on DEV Community by Jeffrey Turov (@jeffreyturov).</description>
    <link>https://dev.to/jeffreyturov</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%2F4050944%2Fabfb8306-248c-4a94-8471-d954f41366c3.png</url>
      <title>DEV Community: Jeffrey Turov</title>
      <link>https://dev.to/jeffreyturov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeffreyturov"/>
    <language>en</language>
    <item>
      <title>I built 8 pay-per-use scraping APIs that AI agents can call directly (Google Maps, TikTok, Instagram, YouTube, LinkedIn) — here's what I learned</title>
      <dc:creator>Jeffrey Turov</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:28:47 +0000</pubDate>
      <link>https://dev.to/jeffreyturov/i-built-8-pay-per-use-scraping-apis-that-ai-agents-can-call-directly-google-maps-tiktok-o61</link>
      <guid>https://dev.to/jeffreyturov/i-built-8-pay-per-use-scraping-apis-that-ai-agents-can-call-directly-google-maps-tiktok-o61</guid>
      <description>&lt;p&gt;A few weeks ago I published a set of Actors on Apify Store. Today they're all &lt;strong&gt;AI-agent ready&lt;/strong&gt;: any LLM agent (Claude, GPT, Cursor, LangChain, n8n) can discover and call them through the Apify MCP server — no custom integration code needed.&lt;/p&gt;

&lt;p&gt;This post is the full playbook: what the tools do, how the pay-per-event monetization works, the bugs I hit, and how AI agents actually consume them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The toolbox
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Actor&lt;/th&gt;
&lt;th&gt;What it extracts&lt;/th&gt;
&lt;th&gt;Price (pay-per-event)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/google-maps-scraper" rel="noopener noreferrer"&gt;Google Maps Business Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Names, phones, websites, ratings, reviews, GPS&lt;/td&gt;
&lt;td&gt;$0.005 / business&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/tiktok-scraper" rel="noopener noreferrer"&gt;TikTok Profile &amp;amp; Video Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Followers, likes, bio, per-video stats&lt;/td&gt;
&lt;td&gt;$0.01 / profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/instagram-scraper" rel="noopener noreferrer"&gt;Instagram Profile Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Followers, bio, verified, engagement&lt;/td&gt;
&lt;td&gt;$0.01 / profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/youtube-scraper" rel="noopener noreferrer"&gt;YouTube Video &amp;amp; Channel Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Views, likes, subscribers, search results&lt;/td&gt;
&lt;td&gt;$0.002 / video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/linkedin-profile-scraper" rel="noopener noreferrer"&gt;LinkedIn Profile Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Headlines, companies, skills, experience&lt;/td&gt;
&lt;td&gt;$0.02 / profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/rag-web-browser" rel="noopener noreferrer"&gt;RAG Web Browser&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Clean Markdown from any URL + Google search&lt;/td&gt;
&lt;td&gt;$0.003 / page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/hermes-revenu-api" rel="noopener noreferrer"&gt;Fuel Prices France API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Real-time prices, 9,800 stations, GPS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FREE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/travelmonitorlab/travel-monitor-launch" rel="noopener noreferrer"&gt;Hotel Rate Monitoring&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Competitor rates, parity checks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FREE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two free ones are deliberate lead magnets — more on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "AI-agent ready" changes everything
&lt;/h2&gt;

&lt;p&gt;The old model: a human finds your scraper on the store, reads the docs, clicks buttons.&lt;/p&gt;

&lt;p&gt;The new model: an AI agent gets a task ("find me 50 plumbers in Austin with their phone numbers"), searches the Apify Store via MCP, reads the actor's README and input schema, and calls it — end to end, no human.&lt;/p&gt;

&lt;p&gt;For that to work, three things must be true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your README is written for an LLM, not just humans.&lt;/strong&gt; Mine now all start with a "Use this tool when..." section — that's what the agent pattern-matches against the user's request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your input schema has a description on every field.&lt;/strong&gt; The agent constructs the JSON input from those descriptions. No description = hallucinated parameters = failed runs = no revenue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your output is documented field by field.&lt;/strong&gt; The agent needs to know what it gets back to reason over it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the actual flow with the Apify MCP server (&lt;code&gt;https://mcp.apify.com&lt;/code&gt; — add it to Claude Desktop or Cursor in 30 seconds):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Get me the follower counts of these 5 TikTok creators"
Agent: → search-actors("tiktok profile")
       → fetch-actor-details (reads README + input schema)
       → call-actor(travelmonitorlab/tiktok-scraper,
                    {"profiles": [...], "maxVideosPerProfile": 0})
       → returns structured JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip MCP entirely — every actor is a single synchronous HTTP call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/travelmonitorlab~google-maps-scraper/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="nv"&gt;$APIFY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"queries": ["plumbers Austin TX"], "maxResults": 50}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monetization: pay-per-event (and the trap that cost me hours)
&lt;/h2&gt;

&lt;p&gt;Apify offers several pricing models. For new actors, &lt;code&gt;PRICE_PER_DATASET_ITEM&lt;/code&gt; is &lt;strong&gt;rejected&lt;/strong&gt; — you must use &lt;code&gt;PAY_PER_EVENT&lt;/code&gt;. The model is better anyway: you define events (e.g. &lt;code&gt;business-scraped&lt;/code&gt; at $0.005) and charge explicitly in code:&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;await&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_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;business-scraped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;The trap:&lt;/strong&gt; put the charge AFTER &lt;code&gt;crawler.run()&lt;/code&gt; and your event loop may already be closed — the charge silently vanishes, you deliver data for free. Charge inside the handler, right before pushing data. Always verify with &lt;code&gt;chargedEventCounts&lt;/code&gt; in the run object after a test run.&lt;/p&gt;

&lt;p&gt;Setting pricing is pure API:&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;PUT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;acts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actorId&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;pricingInfos&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;pricingModel&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;PAY_PER_EVENT&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;reasonForChange&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;Launch pricing&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;pricingPerEvent&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;actorChargeEvents&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;business-scraped&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;eventTitle&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;Business scraped&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;eventDescription&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;One Google Maps business record&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;eventPriceUsd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.005&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;Apify takes 20%. Compute is paid by the user; you pocket the event fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battle scars (so you don't get them)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Crawlee 1.8 breaking changes:&lt;/strong&gt; &lt;code&gt;purge_on_start&lt;/code&gt; and &lt;code&gt;navigation_timeout_secs&lt;/code&gt; are no longer valid kwargs — use &lt;code&gt;page.set_default_navigation_timeout()&lt;/code&gt; in a &lt;code&gt;pre_navigation_hook&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Maps never fires &lt;code&gt;load&lt;/code&gt;:&lt;/strong&gt; analytics keep streaming forever, so navigation always times out. Fix: abort images/fonts/media via &lt;code&gt;page.route()&lt;/code&gt; (the handler must be a coroutine, not a lambda).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Residential proxies are mandatory for Google Maps&lt;/strong&gt;, and you must pass &lt;code&gt;actor_proxy_input=&lt;/code&gt; as a &lt;em&gt;named&lt;/em&gt; argument to &lt;code&gt;Actor.create_proxy_configuration()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;French number formats will crash your floats:&lt;/strong&gt; &lt;code&gt;"4,8"&lt;/code&gt; → replace comma; &lt;code&gt;"1 234"&lt;/code&gt; reviews can use &lt;code&gt;\xa0&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; &lt;code&gt;\u202f&lt;/code&gt; as thousand separator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "SUCCEEDED" run can contain zero useful data.&lt;/strong&gt; Always check &lt;code&gt;itemCount&lt;/code&gt; + sample the dataset + read the end of the log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't run 7 queries × 25 results in one run.&lt;/strong&gt; Split into parallel runs of ≤5 queries × 15 results; retry failed ones sequentially (residential proxy tunnels occasionally die).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The distribution strategy
&lt;/h2&gt;

&lt;p&gt;Publishing on the store is step 0. What actually moves the needle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Free lead magnets&lt;/strong&gt; — the fuel-price and hotel-rate actors are 100% free. Free tools get users, ratings, and store ranking; ranked actors surface in MCP search results; MCP visibility drives paying users to the paid actors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README written for LLMs&lt;/strong&gt; — agents choose tools whose docs they can parse. Clear "use when", typed inputs, example I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Niche SEO titles&lt;/strong&gt; — "Google Maps Scraper" is saturated; "Fuel Prices France API" has zero competition on the store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dogfooding&lt;/strong&gt; — I use my own Google Maps actor to build lead lists I sell elsewhere. Every sale is also a demo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try them
&lt;/h2&gt;

&lt;p&gt;All 8 actors are live on &lt;a href="https://apify.com/travelmonitorlab" rel="noopener noreferrer"&gt;Apify Store&lt;/a&gt;. If you build agents, add &lt;code&gt;https://mcp.apify.com&lt;/code&gt; to your MCP client and just ask for the data — the agent will find the tools.&lt;/p&gt;

&lt;p&gt;Feedback, bugs, feature requests: open an issue on any actor page, I answer fast.&lt;/p&gt;

</description>
      <category>apify</category>
      <category>webscraping</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
