Answers
How do I stop an AI agent from overspending on a paid data API?
Short answer
Have the agent price the task before it runs (looking up endpoints and prices on okdata is free), enforce a page cap and a dollar budget in your own code using the charge returned with every call, and retry only failures that are safe to retry. Because the balance is prepaid, the worst case is the balance itself: 500 runaway Xiaohongshu searches would cost $10.00, then calls stop.
The classic overnight bill comes from an agent stuck in a loop: a tool call fails, the agent tries again, pages a little further, tries again, thousands of times. People on the OpenAI forum describe account limits and try/except wrappers that did not stop the charge before it happened. The guardrail has to sit in the code that makes the call, and it has to know what each call cost.
Four guardrails, cheapest first
- Price the task before running it. Over MCP, the
findanddescribetools cost nothing and need no key, anddescribeshows the price of an endpoint. Ask the agent to say "this is about 12 calls, roughly X" before the first paidcall. Over HTTP, read prices from the endpoint pages, such as Xiaohongshu note search or Douyin video search. - Cap pages in code. Every page is a separate charge. A
max_pagesargument that the model cannot change beats any instruction in the prompt. - Track real spend. Each response carries
meta.billing.amount, the exact charge for that call as a decimal string. Add it up and stop when the budget is gone. - Retry only what is worth retrying. Errors say whether a retry can help (
retry: "yes","after_fix","no","same_key"). Failed calls are never charged, but a loop of pointless retries still burns time and rate limit.
What a budget buys
| Budget | Xiaohongshu searches | Douyin searches |
|---|---|---|
| $1.00 | 50 pages | — |
| $1.00 | — | 50 pages |
| Runaway loop, 500 calls | $10.00 | $10.00 |
A budget wrapper
import os, time, uuid
from decimal import Decimal
import requests
API = "https://api.okdata.online"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}
class Budget:
def __init__(self, usd: str, max_calls: int):
self.left = Decimal(usd)
self.calls_left = max_calls
def get(self, path: str, **params):
if self.left <= 0 or self.calls_left <= 0:
raise RuntimeError("budget used up, stopping")
key = str(uuid.uuid4()) # one key per logical call, reused on retry
for _ in range(3):
body = requests.get(API + path, params=params, timeout=60,
headers={**H, "Idempotency-Key": key}).json()
if body["code"] == "ok":
self.calls_left -= 1
self.left -= Decimal(body["meta"]["billing"]["amount"])
return body
if body.get("retry") not in ("yes", "same_key"):
raise RuntimeError(f"{body['code']}: {body['message']}")
time.sleep(body.get("retry_after_seconds") or 2)
raise RuntimeError("gave up after 3 tries")
budget = Budget("0.50", max_calls=40)
page = budget.get("/v1/xiaohongshu/notes/search", query="防晒霜", sort="latest")
Give the agent budget.get as its only way to fetch data. It can overshoot by one call at most, because the check runs before the call.
The hard stop you already have
okdata is prepaid. When the balance reaches zero, calls fail with insufficient_balance and nothing is charged, so no loop can spend more than you topped up. Keep the balance near what a week of normal use needs, and check it any time with GET /v1/account. A new key starts with $0.10.
Other layers worth adding
Agent frameworks have their own brakes: max_iterations on a LangChain agent, recursion_limit in LangGraph, a step limit in most others. Use them too; they stop runaway reasoning, which also costs model tokens. API gateways with spending rules help when several teams share one key. Billing dashboards and email alerts are useful, but they report after the money is gone. Prices for every endpoint are on the pricing page, and every error code with its retry advice is on the errors page.
Updated