Give an AI agent live web search with a hard spending cap

An agent that can search the web can also loop. Here is how to wire live search into an agent so the budget is enforced by the API, not by hoping the model behaves.

Giving a model a search tool is four lines of code. Giving it a search tool that cannot cost you $4,000 overnight takes slightly more thought.

The failure mode is specific: an agent that retries on empty results, or decomposes one question into forty sub-queries, or gets stuck in a plan-search-replan loop. None of these look like bugs while they happen. They look like the agent working hard.

Do not rely on the prompt for the budget

“Use at most 5 searches” in a system prompt is a preference, not a limit. It degrades under long contexts, gets overridden by a confident plan, and disappears entirely the moment the agent is handed a genuinely hard question.

Spending limits belong in three places that the model cannot argue with.

Layer 1: a credit balance the model cannot exceed

The outermost limit should be the key itself. A key with a finite credit balance cannot spend more than that balance, whatever the agent decides to do. On FastAF the balance lives on the account and every request reserves its cost before the work starts, so a runaway loop hits 402 insufficient_credits and stops.

The balance belongs to the account, and every key on that account draws from it. Keys are for revoking access (one per deployment, so a leaked one can be cut off alone), not for splitting money: if a side project must not be able to drain the budget of your production pipeline, give it its own account with its own balance, topped up deliberately.

Layer 2: a per-run budget in the tool wrapper

Inside a single run, count what you spend and refuse to go further. Every response carries its cost in a header, so the accounting is exact rather than estimated:

import requests

class SearchBudget:
    cost = 5  # credits per search

    def __init__(self, api_key, max_credits=100):
        self.api_key = api_key
        self.max_credits = max_credits
        self.spent = 0

    def search(self, query, country="us"):
        if self.spent + self.cost > self.max_credits:
            # Returned to the model as a normal tool result, not an exception:
            # the agent should be able to reason about running out.
            return {"error": "search budget exhausted for this run"}

        response = requests.get(
            "https://api.fastaf.co/v1/serp",
            params={"query": query, "country": country},
            headers={"Authorization": f"Bearer {self.api_key}"},
            timeout=60,
        )

        if response.status_code == 402:
            return {"error": "out of credits"}
        if response.status_code != 200:
            return {"error": response.json().get("error", {}).get("code", "search_failed")}

        self.spent += int(response.headers.get("X-FastAF-Credits-Used", 5))
        data = response.json()
        return {
            "results": [
                {"title": r["title"], "link": r["link"], "snippet": r.get("snippet")}
                for r in data.get("organic_results", [])[:5]
            ],
            "credits_remaining": self.max_credits - self.spent,
        }

The check is spent + cost > max_credits, not just spent: with a budget of 6, checking only spent would let a second 5-credit search through, for 10 in total.

Two details matter more than they look. First, exhaustion is returned as a tool result rather than raised as an exception, so the agent can adapt and answer with what it has instead of crashing the run. Second, telling the model how much budget is left measurably changes its behaviour: agents that can see a shrinking budget prioritise their queries.

Layer 3: trim what goes back into the context

A raw SERP response is large, and pushing all of it into context costs more in tokens than the search cost in credits. Returning the top five results with title, link and snippet is almost always enough for the model to decide what to read in full, and it keeps the loop cheap.

This is also why “search” and “fetch the page” should be two separate tools: let the model search cheaply, then pay for the full page only for the two or three links it actually wants.

Layer 4: bound the concurrency

If your agent fans out sub-queries in parallel, cap the fan-out at or below your plan’s concurrency limit. Above it you get 429 responses with a Retry-After header, which a naive retry loop turns into a burst of duplicate requests. Respect the header, and keep the worker pool small.

Where this is going

Today the clean way to wire this up is a REST call with a scoped key and a budget you enforce in the wrapper, which is what the code above does. The other way is the agent paying per call itself, with no account at all. FastAF supports x402 today on every REST endpoint, at $0.001 USDC per credit on Base, so a Google search costs $0.005 and is settled only if the call succeeds (MPP is not supported). The wallet balance then becomes the cap, while prepaid credits stay cheaper at volume. The x402 docs show the full flow.

The docs have the full endpoint list and error semantics, and pricing has the credit cost of each call so you can size a per-run budget properly.