Apify has spent this year making the phrase "your Actor as a tool for AI agents" concrete: a hosted Model Context Protocol (MCP) server, per-event billing an agent can actually pay, even crypto payment rails that don't require an Apify account on the caller's side. The pitch writes itself: build a good Actor, describe it well, and a robot with a budget will find you.
What I could not find anywhere was numbers. When an agent goes looking for a tool, what does it actually see? Does it see the same ranking a human sees in Apify Store, or something else? Is there — as I quietly hoped — a side door for new Actors that haven't accumulated users yet?
I had a good reason to want that side door. Between July 29 and August 1 I published three pay-per-event (PPE) Actors under a brand-new developer account: scrapers for Wildberries products and reviews, Avito real estate listings, and Lazada reviews — big marketplaces, quiet corners of the Store. New account, zero reviews, user counts I can list from memory. A textbook cold start.
So on August 6 I measured it. I connected to the Apify MCP server the same way any agent does, ran the searches an agent would run, and compared the results against the human-facing Store search from the same day. This article is the method, the numbers, and the two traps that produced convincingly wrong results before the real ones — one of them had me believing my Actors were invisible to agents entirely.
How an agent reaches your Actor
Before the measurements, a quick map of the pipeline, because it determines what's worth optimizing.
An agent (or its harness — Claude Desktop, an SDK loop, whatever) connects to https://mcp.apify.com, the hosted MCP server. Transport is Streamable HTTP; auth is either OAuth or a plain API token header. The minimal client config is:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com",
"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
}
}
}
The default toolset exposes, among others, three tools that matter for discovery:
-
search-actors— keyword search over the Store, -
fetch-actor-details— the full card of one Actor: description, pricing, stats, input schema, README, -
call-actor— runs an Actor by name with a JSON input, which is validated against your input schema at execution time.
The intended flow is exactly the one you'd guess: search → fetch details → call. Alternatively, a specific Actor can be mounted as a named tool by adding ?tools=username/name to the server URL, in which case the MCP server reads its input schema and generates a dedicated tool from it.
Who is in the searchable pool? Not everyone. The server returns only free and pay-per-event Actors to agentic callers — rental-model Actors are excluded — and it drops Actors that fail platform safety checks. Apify's monetization docs add two more conditions for an Actor to be usable by agentic (and crypto-paying) callers: it must run with limited permissions, and it must not be a Standby-mode Actor. There is no opt-in: a pay-per-event Actor with limited permissions and no Standby is in automatically. As of late July 2026, a bit over 29,000 Actors in the Store passed that bar (counted via the public Store API with the agentic-users filter). Mine are among them, which is what made the next question interesting.
The experiment: searching for my own Actors the way an agent would
I wanted the rawest possible view — no SDK, no client-side magic — so I spoke JSON-RPC to the server directly with curl. Three steps; the only prerequisite is your Apify API token exported as APIFY_TOKEN.
Step 1: initialize and capture the session. The session ID comes back as a response header (-D headers.txt below saves those), and everything after initialize must carry it:
S=$(curl -s -D headers.txt -X POST "https://mcp.apify.com/" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
> /dev/null; grep -i "^mcp-session-id:" headers.txt | tr -d '\r' | awk '{print $2}')
A small thing that cost me an attempt: grep for ^mcp-session-id: with the anchor. Without it, the first match is the access-control-expose-headers line, which merely mentions Mcp-Session-Id, and you spend a confused minute sending a CORS header list as your session ID.
Step 2: call the search tool. Responses arrive as a Server-Sent Events (SSE) stream; the payload is the last data: line:
curl -s -X POST "https://mcp.apify.com/" \
-H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" -H "mcp-session-id: $S" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search-actors","arguments":{"keywords":"Lazada reviews","limit":10,"offset":0}}}'
which comes back looking like this (trimmed):
event: message
data: {"result":{"content":[{"type":"text","text":"# Search results:\n- **Search query:** Lazada reviews\n- **Number of Actors found:** 9\n\n# Actors:\n\n## [Lazada Review Scraper](https://apify.com/hello.datawizards/lazada-review-scraper) ..."}]},"jsonrpc":"2.0","id":3}
Step 3: page through and extract positions. limit is capped at 10, so depth comes from offset. With TOKEN and SESSION set to the values captured in step 1 (paste them in), the probe I actually ran (per query, down to position 50):
import json, re, subprocess
def search(keywords, offset):
body = json.dumps({"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {"name": "search-actors",
"arguments": {"keywords": keywords, "limit": 10, "offset": offset}}})
out = subprocess.run(["curl", "-s", "-X", "POST", "https://mcp.apify.com/",
"-H", f"Authorization: Bearer {TOKEN}", "-H", "Content-Type: application/json",
"-H", "Accept: application/json, text/event-stream",
"-H", f"mcp-session-id: {SESSION}", "-d", body],
capture_output=True, text=True).stdout
data = [l for l in out.splitlines() if l.startswith("data: ")][-1]
return json.loads(data[6:])["result"]["content"][0]["text"]
ids = []
for off in (0, 10, 20, 30, 40):
ids += re.findall(r'https://apify\.com/([a-zA-Z0-9_.-]+/[a-z0-9-]+)', search("Lazada reviews", off))
positions = list(dict.fromkeys(ids)) # dedupe, keep order
That's the whole rig. Except my first two attempts produced garbage, and both failure modes are worth your time, because agents will hit them too.
Trap #1: the argument is keywords, and typos are silent
My first pass called the tool with {"search": "avito real estate", "limit": 10} — search felt like the obvious argument name for a search tool. The server didn't complain. It returned a perfectly plausible list of ten popular Actors. So did the next query. And the next.
The tell — which I only spotted the second time it happened — was that the listing was byte-identical for every query. Google Maps scraper, TikTok scraper, Instagram scraper, Google Search scraper, Website Content Crawler… for "avito real estate", for "lazada reviews", for anything. My Actors appeared in none of them, which read exactly like "your Actors are invisible to agents."

Two deliberately different queries, wrong argument name — one identical answer. This is what "your search never executed" looks like.
What actually happens: the tool's input schema declares no required arguments —
{ "properties": {
"keywords": { "type": "string", "default": "" },
"limit": { "type": "integer", "default": 5, "maximum": 10 },
"offset": { "type": "integer", "default": 0 } },
"required": [] }
— so an unknown argument is silently ignored, keywords falls back to "", and empty keywords are documented to return the Store's default sort order, which is popularity. (To be fair, silently dropping unknown properties is stock JSON-Schema behavior, not an Apify quirk — which is exactly why it's worth a warning.) You don't get an error. You get the most-used Actors on the platform, confidently, for any question you ask.
I re-verified it on the day of writing: two deliberately different queries with the wrong argument name returned identical listings, my Actors absent from both; the same queries with keywords returned distinct, correct results.

Same session, correct argument name: a real ranking, with my Actor at #4.
Two lessons. When you're the one measuring: always send a control query — if two obviously different searches return the same list, your search never executed. As a tool author: this is what LLM-facing APIs are like now. A tool that silently substitutes a default for a misspelled argument will feed an agent wrong-but-plausible data, and the agent will act on it. If you build MCP tools of your own, make unknown arguments loud.
Trap #2: the output is Markdown — parse the links, not the prose
search-actors returns Markdown, not JSON: a heading, a bullet with your query, then one section per Actor:
# Search results:
- **Search query:** Lazada reviews
- **Number of Actors found:** 9
# Actors:
## [Lazada Review Scraper](https://apify.com/hello.datawizards/lazada-review-scraper) (`hello.datawizards/lazada-review-scraper`)
- **URL:** https://apify.com/hello.datawizards/lazada-review-scraper
- **Description:** Lazada reviews Scraper Pro extracts detailed product reviews...
My first parser pulled anything shaped like owner/name out of that text. It came back with results like INN/OGRN, pros/cons, JSON/CSV, surface/rooms and 10/10 — README fragments and slashed phrases from Actor descriptions, interleaved with real slugs.
That's not just noise; it shifts positions. With the naive regex, one of my Actors showed at #8 instead of its real #7, another at #3 instead of #2, and a third dropped out of the top 10 entirely. If I'd stopped there, I'd have published wrong numbers that were only slightly wrong — the worst kind.
The reliable anchor is the canonical URL that every listing carries:
re.findall(r'https://apify\.com/([a-zA-Z0-9_.-]+/[a-z0-9-]+)', txt)
with order-preserving dedup afterwards, because each listing block repeats the Actor's URL up to three times (title link, URL field, pricing link).
One more parsing footnote: "Number of Actors found" is the page's own count, not a total. Unlike the public GET /v2/store API, the MCP channel never tells you how many results exist. An agent can't ask "how crowded is this niche" — and neither could I, through this channel.
The result: agent search is a mirror, not a side door
With the rig fixed, here is what an agent sees for seven queries relevant to my three Actors (positions in search-actors output, probed to depth 50, August 6, server 0.14.2):
keywords |
position | which Actor |
|---|---|---|
Avito real estate |
#2 | Avito real estate |
Avito property |
#2 | Avito real estate |
Lazada reviews |
#4 | Lazada reviews |
Russian marketplace |
#4 | Wildberries |
Wildberries reviews |
#7 | Wildberries |
Wildberries products |
#9 | Wildberries |
Wildberries |
#20 | Wildberries |
And here is the same day's human-side Store search — the default, filters-on view a visitor gets, which I measured through the public GET /v2/store API and have spot-checked by eye on the website:
| query | web Store search | agent (search-actors) |
|---|---|---|
avito real estate |
#2 | #2 |
lazada reviews |
#4 | #4 |
wildberries reviews |
#7 | #7 |
wildberries |
#20 | #20 |
Four exact matches, including the broadest term. The Actors above me were the same incumbents I compete with in the web Store, seen through a different pipe.

The human side of the mirror, same day: Store search for "avito real estate" — the incumbent at #1 (20 users), my Actor at #2 (2 users).
After measuring this from the outside, I found the receipts on the inside: the MCP server is open source, and search_actors.ts simply calls the same public Store search API, passing your keywords through as the search string. The MCP layer adds formatting, not ranking. Apify's documentation on Store search ranking states it plainly: search ranking evaluates parameters similar to the Actor quality score, and the two correlate strongly in Apify Store search and the MCP search-actors tool.
A short-lived curiosity from six days earlier: on July 30 — when only the first two of my Actors were published and my account's identity verification was still pending — the default web Store search excluded them entirely, while the agent channel already listed them at #2 and #4. For those first couple of days, agents were the only searchers who could find my Actors at all. That divergence has since converged: verification completed, the web results caught up, and both the docs and the server source describe safety filtering on the agentic side too. Ranking was identical all along; treat the episode as trivia.
The strategic conclusion is the honest one, and it's the opposite of what I hoped: there is no separate agent-SEO game and no cold-start bypass. An agent searching for "lazada reviews" sees the same #1 and #2 a human sees. Everything you do for your Store position transfers to the agent channel automatically — and nothing extra is available there. The queue is the queue.
What the agent reads once it finds you
Position gets you into the candidate set. The pick happens on the card. Here's what fetch-actor-details returned for my Wildberries Actor (trimmed — the pricing and stats lines are verbatim):
Wildberries Scraper — Products & Reviews (`actorforgehq/wildberries-scraper`)
- **Pricing:** This Actor is paid per event:
- **Actor Start**: Charged when the Actor starts running. Number of events
charged depends on Actor memory (one event per GB, minimum one event).
($0.02 per event)
- **Product scraped**: One Wildberries product record: name, brand, seller,
region-pinned price before and after discount... ($0.0035 per event)
- **Review scraped**: One Wildberries review: rating, text, author and date,
newest first. ($0.002 per event)
- **Stats:** 3 total users, 2 monthly users
- **Developed by:** [actorforgehq](https://apify.com/actorforgehq) (community)
- **Last modified:** 2026-08-06T04:17:14.001Z

The same card as a human sees it. The agent gets the Markdown version — same title, same prices, same user counts.
Three things in that card changed how I think about Actor metadata.
Your user counts are in the agent's context window. "3 total users, 2 monthly users" — the agent sees my cold start as plainly as any human browsing the Store. Social proof reaches agents too: it's right there in the tool output, next to the price. If you assumed agents would judge tools purely on descriptions and schemas: no, the popularity signal ships with the card.
Your PPE event names and descriptions are your pricing page. The event titles and descriptions I wrote in the monetization console are rendered verbatim to the agent as the explanation of what its money buys. Write them as product copy, not as internal event IDs. And mind the grammar of your titles: the Store's price line pluralizes the event title mechanically, so mine renders as "from $3.50 / 1,000 product scrapeds" — the top Actors in my niches sidestep this by naming events as plain nouns: Product, Review, Listing.

Event titles become customer-facing copy — including the mechanically pluralized price line.
Memory is a pricing parameter. Note the fine print on Actor Start: one event per GB, minimum one event. My Actors default to 1,024 MB, so a start bills exactly one event — $0.02 on this Actor. If I raised default memory to 4 GB, every start would silently bill four events without a single price field changing. Price increases on Apify come with a built-in delay before they take effect; a memory bump does not. I now treat defaultRunOptions.memoryMbytes as a line item on the price list, reviewed with the same care as the prices themselves.
Closing the loop: letting an agent run it
Discovery and reading are two-thirds of the story. The third tool is call-actor, so I finished the session the way an agent would — by running my own Lazada Actor through the MCP server, with a budget cap:
{ "name": "call-actor", "arguments": {
"actor": "actorforgehq/lazada-reviews-scraper",
"input": { "productUrls": ["https://www.lazada.vn/products/x-i246452966.html"],
"maxReviews": 3 },
"waitSecs": 45,
"callOptions": { "maxTotalChargeUsd": 0.05 } } }
The input is passed through as-is and validated against the Actor's input schema at execution time. waitSecs is capped at 45; a longer run would be collected asynchronously via get-actor-run. Mine came back inside the window:
{ "runId": "v230oDUAehwnWsJSt", "status": "SUCCEEDED",
"stats": { "runTimeSecs": 9.231 },
"summary": "SUCCEEDED in 9.231s. 4 items; 39 fields available.",
"nextStep": "Use get-dataset-items with datasetId=TEXypQcxLYA8sOVBt ..." }
Two details here earn their keep. The response enumerates the dataset's typed fields — the metadata from my dataset schema surfacing exactly where an agent needs it. And nextStep literally coaches the caller through the follow-up, field projection included. get-dataset-items then returned one product record and three Vietnamese reviews with verifiedPurchase flags, projected to just the fields I asked for.
The bill, straight from the run record:
{ "chargedEventCounts": { "apify-actor-start": 1, "product-scraped": 1, "review-scraped": 3 } }
One start ($0.01 on this Actor), one product ($0.002), three reviews (3 × $0.006) — three cents, itemized, against a five-cent cap.

The whole promise of "your Actor as a tool for AI agents" in one round trip: found by keyword, priced per event, run with a budget, billed itemized.
Making your Actor a good tool: what the measurements say actually matters
Given that ranking is shared with the Store and the card is what the agent reads, "my Actor as a tool for AI agents" decomposes into concrete, boring, checkable work. This is my list, each item traceable to something above.
1. Win the text match in the same fields humans search
The search — both channels — runs over title, name, description, username and README. In a web-search sweep I ran in mid-July, everything above roughly position 24 matched the query in its title; README-only matches started behind them. Exact phrases beat popularity: on "avito real estate scraper" that day, an Actor with 15 users outranked one with 608 because the phrase sits in its title. My Actor titles carry the platform and the data type as plain nouns ("Wildberries Scraper — Products & Reviews") for the same reason. It's unglamorous SEO, and the mirror means it's the agent-facing work too.
2. Write input schema descriptions as arguments, not labels
The MCP server converts your input schema into the tool schema the agent reasons over — truncating each property description at 500 characters, enum lists at 2,000. That budget is your entire opportunity to explain a parameter to the caller. In my Lazada Actor, the sort field doesn't say "Sort order (0–3)"; it says why the default is newest first: on a product I measured, Lazada's own "relevance" order put fifty 5-star reviews on page one and pushed every 1–2-star review to the very end — an agent sampling "relevant" reviews would hand its user a positively biased dataset without knowing it. A human skims that; an agent uses it to choose parameters correctly.
3. Fill the dataset schema — field metadata is how agents read your output
Apify's dataset schema docs are direct about this: agents interacting with Actors through MCP "rely on field metadata to understand the data in your dataset," and without it they "must infer field meanings from names alone, which leads to errors." Every field in my dataset schemas has a type, title, description and example — those are the "39 fields available" the call-actor response advertised. And where an Actor mixes row kinds — my Wildberries and Lazada Actors emit both products and reviews — rows carry an explicit discriminator ("type": "product" | "review"), so an agent, or the next Actor in a chain, doesn't have to guess which rows are which.
4. Replace the default example input
A freshly created Actor's public definition ships with exampleRunInput of {"helloWorld": 123}. That junk is part of what agent tooling can read about your Actor. Mine now carry a realistic minimal input (a real search query, a real product URL) — a two-minute API call that removes one entire class of failed first runs.
5. Be runnable and billable by a machine
The pool filter above doubles as a checklist: pay-per-event pricing, limited permissions, no Standby mode — that's what makes an Actor available to agentic callers at all (rental Actors are invisible to this entire channel), and the same three conditions gate the agentic payment rails, like the x402 protocol that landed in June 2026: USDC payments per call, no Apify account on the caller's side. None of it requires opting in; it's simply the default posture of a modern Actor.
6. Respect the caller's budget — it's an API now
An agent can cap a run with maxTotalChargeUsd — my demo run above did. On the Actor side, Actor.charge() does not throw when that cap is reached: it reports eventChargeLimitReached: true in its return value, and the SDK silently stops charging and pushing further items. The platform won't police the cap for you — Apify's pay-per-event guide puts the stop logic in your Actor's hands: check the charge result and end the run yourself. If your code ignores the return value, you'll keep scraping at full speed, for free, into the void. My pipeline checks it and shuts the run down cleanly:
const res = await Actor.charge({ eventName: 'review-scraped', count: batch.length });
if (res.eventChargeLimitReached) {
log.info('Charge limit reached — stopping gracefully');
break; // stop fetching; the caller got exactly what they paid for
}
Budget-capped callers are normal callers now. Design for the cap being hit on a good run.
Takeaways
- The agent channel is a mirror of Store search — same index, same ranking, confirmed both by measurement (positions matched one-for-one on the same day) and by the server's source code. There is no cold-start side door; your Store position is your agent position.
- Silent argument defaults are the measurement hazard. A wrong argument name gave me a confident, popularity-sorted answer to a question I never asked — twice, days apart. Send control queries; distrust identical results for different questions.
- Parse anchors, not prose. Position numbers extracted by casual regex from Markdown were off by one to two places — and in one case by enough to knock an Actor out of the visible top 10.
- The card is the pitch. Event names, event descriptions, user counts and last-modified date all land in the agent's context. Write every string as if the buyer will read it, because the buyer's agent will.
- Memory is a price. One start event per GB means your default memory setting multiplies what every caller pays before the first item arrives.
I went looking for a side door for cold-start Actors and found a mirror instead. I'd rather know. The work that remains is the same work it always was — be findable for the phrase, be legible on the card, be honest per event — except now half the readers parse it with a token budget, and they don't skim.
If you publish Actors, I'd genuinely like to compare notes: have you measured what agents see for your Actors — and did you find a query where the two channels disagree by more than a couple of positions?
Core measurements: August 6, 2026, mcp.apify.com server 0.14.2, probed to position 50 per query and re-verified the same day; supporting web-search sweeps July 20 – August 6, 2026. Positions drift daily — I've watched a term move three places overnight — so expect your numbers to differ. The three Actors involved: Wildberries Scraper, Avito Real Estate Scraper, Lazada Reviews Scraper.
Top comments (0)