What search and scrape cost once an agent loops on them
An agent decides how many calls it makes, which is a cost model most teams don't plan for. The three controls that bound it, and the number to instrument.
The reason to run agent web access on Infrai isn’t the per-call rate — it’s that the model, the search tool, the vector store and the budget cap that bounds all three are one credential. An agent’s spend is the sum of its tools, and being able to see that sum in one GET /v1/account/usage is worth more than a fractional rate difference on any single tool.
That said, agent loops have a cost shape that catches people out, and it’s worth understanding before you ship one.
The rates, read live
curl -sS "https://api.infrai.cc/v1/discovery/web.search" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"id": "web.search",
"method": "POST",
"path": "/v1/web/search",
"minimum_tier": "standard",
"vendors_ready": ["tavily"],
"billing": {
"is_billable": true,
"unit": "per_call",
"price_usd": 0.008,
"currency": "USD",
"approximate": false
}
}
Per call, approximate: false. GET /v1/discovery/web.scrape carries its own rate, lower than search. Both verified 2026-09-21, both free to read, and both drifting downward as vendor contracts improve — so read them there rather than trusting this paragraph.
Why an agent’s cost is different
With a normal API you decide how many calls you make. With an agent, the model decides — and it will happily search five times to answer a question a human would have answered with one lookup, then scrape three of the results to be thorough.
That turns your cost from a function of user actions into a function of model behaviour, which is much harder to predict and much easier to leave unbounded.
| Pattern | Calls per conversation | Why it happens |
|---|---|---|
| One search, snippet is enough | 1 | good tool description |
| Search, then read the top result | 2 | the honest common case |
| Search, read three results | 4 | thoroughness with no budget |
| Re-search after each partial answer | 6-10 | no guidance on when to stop |
| Search for things it already knows | unbounded | tool description too permissive |
The last row is the one to fix first, and it’s free: a tool description saying “use when the answer depends on facts that change, or on anything after your training data” removes most of it.
Three controls that bound it
A per-conversation tool budget. Count calls in your agent loop and stop. Not a rate limit — a decision about how much a single answer is worth.
Instrument the reason. Which tool, how many times, for what kind of question. The distribution is always more lopsided than anyone expects.
An account-level ceiling. PUT /v1/account/budget/set is the backstop for the case your loop logic misses.
import os
from collections import Counter
import requests
API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
MAX_TOOL_CALLS = 4
USAGE = Counter()
class ToolBudgetExhausted(Exception):
"""Raised when one conversation has spent its allowance of tool calls."""
def search(query: str, spent: Counter) -> dict:
if sum(spent.values()) >= MAX_TOOL_CALLS:
raise ToolBudgetExhausted(f"{MAX_TOOL_CALLS} tool calls already made")
spent["search"] += 1
USAGE["search"] += 1
resp = SESSION.post(f"{API}/v1/web/search",
json={"query": query, "max_results": 3}, timeout=40)
body = resp.json()
return body.get("data", {"results": []})
def read(url: str, spent: Counter) -> dict:
if sum(spent.values()) >= MAX_TOOL_CALLS:
raise ToolBudgetExhausted(f"{MAX_TOOL_CALLS} tool calls already made")
spent["scrape"] += 1
USAGE["scrape"] += 1
resp = SESSION.post(f"{API}/v1/web/scrape",
json={"url": url, "format": "markdown"}, timeout=60)
return resp.json().get("data", {"content": ""})
def report() -> dict:
"""What the agent actually did, and what the account actually paid. Comparing
the two is how you find the tool that costs more than you assumed."""
usage = SESSION.get(f"{API}/v1/account/usage", timeout=25).json()["data"]
rows = {r["key"]: r for r in usage.get("breakdown", [])}
return {
"tool_calls_this_process": dict(USAGE),
"account_web_search": rows.get("web.search", {}),
"account_web_scrape": rows.get("web.scrape", {}),
"period_total": usage.get("total_cost"),
}
if __name__ == "__main__":
spent = Counter()
try:
hits = search("current status of the Sora video API", spent)
for hit in hits.get("results", [])[:2]:
read(hit["url"], spent)
except ToolBudgetExhausted as exc:
print(f"stopped: {exc}")
print(report())
A budget expressed in calls rather than dollars is the right shape, because it survives a repricing and because it’s the unit your agent loop actually controls.
The lever that beats the rate
Search and scrape are cheap per call. Inference usually isn’t — and the tool calls drive extra inference rounds, because every tool result goes back into the context and gets re-read by the model.
So the expensive consequence of an over-eager search tool isn’t the search. It’s the four extra model turns, each carrying a context window that now includes three scraped pages. Check GET /v1/account/usage and you’ll usually find the chat line dwarfs the web lines — which means the way to spend less on web tools is to make the agent search less, and the way to spend less overall is to keep what it retrieves small.
Three results instead of ten. Snippets instead of full pages when a snippet answers. A tool description that says when not to bother.
Structural facts worth planning on
Both endpoints are per-call with no plan minimum and no credit bundle to size in advance, so an agent that searches rarely costs almost nothing. Reads of the rates and of your usage are free, so instrumenting this costs nothing to run. And the $2 every new account starts with covers a substantial number of tool calls — enough to measure your own agent’s behaviour before committing to a volume estimate.
Limitations
There’s no caching layer for repeated searches: the same query twice is two calls and two charges, so if your agent asks predictable questions, a cache of your own in front of the tool is real money saved. There’s also no per-tool spend cap — the budget cap in PUT /v1/account/budget/set is account-wide, so a separate key per workload with POST /v1/account/keys/create is how you keep an experimental agent from eating your production allowance.
And the web surface is two endpoints deep, Tavily-backed. Firecrawl or Exa going direct gets you crawl or semantic retrieval respectively, along with their own plan pricing, which may well be cheaper at steady high volume — this isn’t a good fit if web data is the substance of your product rather than a tool your agent occasionally reaches for.