If you give an agent an API key on a funded account, you've given it a card. Most APIs are fine with that: they bill per call, and a loop that runs all night shows up on next month's invoice.
We couldn't ship it that way. SpecStream turns one sentence ("critical vulnerabilities published this month, with the affected product and the fixed version") into a list of matches from the live web, and you pay per match. One badly worded request from an agent can mean thousands of rows nobody wanted.
So in our API the key isn't the permission. A price is. Nothing that spends money runs until someone approves a specific price for a specific request, with a ceiling in cents. Here's the whole flow in four calls.
The script
import os
import time
import uuid
import requests
API = "https://api.specstream.ai"
CEILING_CENTS = 500
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SPECSTREAM_API_KEY']}"
def call(method, path, **kwargs):
response = session.request(method, API + path, timeout=60, **kwargs)
response.raise_for_status()
return response.json()["data"]
request = call("POST", "/v1/parse", json={
"q": "Critical vulnerabilities published this month. Include the affected "
"product and the fixed version.",
"locale": "en-US",
})
assert request["status"] == "runnable", request["status"]
print("Read as:", request["canonical_sentence"])
price = call("POST", "/v1/capacity/quotes", json={
"ast_hash": request["ast_hash"],
"operation": "find",
"requested_units": 25,
})
if price["maximum_cents"] > CEILING_CENTS:
raise SystemExit(f"Price is {price['maximum_cents']} cents. Not approving.")
request_key = f"critical-vulnerabilities-{uuid.uuid4()}"
run = call("POST", "/v1/capacity/executions", json={
"quote_id": price["quote_id"],
"quote_hash": price["quote_hash"],
"confirm_max_cents": price["maximum_cents"],
"idempotency_key": request_key,
})
while True:
page = call("GET", f"/v1/finds/{run['find_id']}/rows")
if page["status"] in ("completed", "partial", "failed", "canceled", "expired"):
break
time.sleep(5)
for row in page["rows"]:
print({cell["label"]: cell["display"] for cell in row["cells"]})
for cell in row["cells"]:
source = cell.get("provenance") or {}
if source.get("source_url"):
print(" ", cell["label"], "from", source["source_url"], "at", source["observed_at"])
Set SPECSTREAM_API_KEY to a key from Agents & API in the workspace, and give the key a spending limit while you're there.
The four calls
Read (POST /v1/parse). The sentence comes back as a structured reading: what every row has to satisfy, which facts to bring back, and a hash of that reading. If the request is too vague to price, status is needs_edit and you reword it. Free.
Price (POST /v1/capacity/quotes). A quote for that exact hash: the most it can cost, a signed quote_hash, and a 30-minute expiry. Also free. An agent can read and price a hundred requests and the balance doesn't move.
Approve (POST /v1/capacity/executions). The only call that spends. It has to carry the quote's ID and hash, plus confirm_max_cents, the ceiling you agree to. Change the request and the reading's hash changes, so the old approval no longer covers it.
Rows (GET /v1/finds/{id}/rows). Every value comes with the URL it was read from and when. Anything that couldn't be confirmed lands in a separate possible list and isn't billed.
Three decisions we'd make again
The spending limit is a backstop, not a yes. A key can carry a spending limit, and it can't raise its own. But an account with $50 in it hasn't agreed to every request an agent can think of. Approval is per price, per request.
Retries carry a key. The approval takes an idempotency_key you choose. If the response gets lost, GET /v1/capacity/requests/{key} returns the run that already started instead of starting a second one. Agents retry constantly, and this is exactly where they'd pay twice.
You pay for matches, not attempts. A search that finds nothing costs nothing, and unconfirmed rows stay off the bill. Whatever the run doesn't use of the ceiling goes back to the balance.
From an agent, over MCP
The same four steps are tools on our remote MCP server at https://api.specstream.ai/mcp (Streamable HTTP, Authorization: Bearer <key>): compile, quote_capacity_work, authorize_capacity_work and list_find_rows. The server tells the model to show the quote and wait for the person's yes before it calls authorize_capacity_work, so a human agrees to a number, not to whatever the agent decided to do.
What it costs
A match is 10ยข on Standard (up to five facts a row), and new accounts get $10 of work free, no card. Everything else is on the pricing page. The API guide and the MCP guide have the full schemas.
How are you capping spend in your own agents: per-call budgets, a prepaid wallet, a person in the loop? We'd like to hear what's holding up in production.
We build SpecStream; this is how its API works.
Top comments (1)
Quote-hash binding is the piece most spend caps skip. I hit the same shape from the enforcement side: Microsoft's agent-hooks conformance kit, a $500 per-call ceiling, a fake client so the refund was the only variable. Three cases there:- one $840 refund: DENY at pre_tool_call, the function body was never entered- two $420 refunds: ALLOW, ALLOW, $840 out the door- two $420 with a per-order running total kept in the interceptor: ALLOW then DENY/over_cap_cumulative, $420 outThe published gate was per-call, not per-session, and the ledger had to live outside the interceptor - outside the process entirely if two workers can touch the same order.confirm_max_cents is the same idea as content-bound approval: the approval covers the action the person saw, so a changed request invalidates it. Two things I would want to know before trusting it in production: what happens when the quote's 30-minute expiry lands mid-run, and whether two concurrent executions can both pass a limit that only one of them should have been allowed to spend. Both are the failure modes a per-call cap hides.