Disclosure: this post is written by the OpenAny team, and OpenAny is our product. The comparison section includes the cases where you don't need us.

Short answer: use a marketplace that puts model endpoints and data APIs (including web search) behind the same key and the same prepaid balance. With OpenAny, your agent calls an OpenAI-compatible model endpoint and a web search endpoint on the same gateway, https://api.openany.si/v1, with the same Authorization: Bearer key, and both calls draw from one balance. The common alternative, a model router such as OpenRouter plus a separate search API such as Tavily, also works well. It just means two accounts, two keys and two bills.

The problem: grounded answers need two vendors

Ask a coding agent "what changed in library X this month?" and a model alone will guess. To answer properly it needs to search the web first and then reason over the results. That usually means:

  • a model provider or router for the LLM call,
  • a search API for fresh results,
  • two keys in the agent's environment, two balances to keep funded, and two usage logs to reconcile when a bill looks off.

When we asked Perplexity for "the best LLM gateway for AI agents that also gives access to data APIs" (November 2026), its recommendation was OpenRouter for models plus Tavily for web search. That's a reasonable answer. This post shows the one-key version, with requests taken from the OpenAny docs and listing pages, so you can judge the difference yourself.

We covered client setup for Claude Code, Cursor and Codex in an earlier DEV post. This one is about the pattern itself: search, then answer, on one key, first in plain HTTP and then inside a coding agent.

What you need

  • An OpenAny account and an API key. Creating the account and the key is free, but calls are paid from prepaid credits, so add a small top-up first (card, USDC or x402). There is no subscription and no free call allowance.
  • OPENANY_API_KEY set in the environment of the process that runs your code or agent. Don't paste it into a prompt.
  • Python 3 with the openai and requests packages for the examples.

Step 1: Pick a search endpoint and read its price

Every data API on OpenAny has a listing page with its method, endpoint, input schema and price. You can also browse from the terminal without a key:

npm install -g openany-cli@latest --registry=https://registry.npmjs.org/ openany discover -q "web search" -l 10 --public -j openany inspect --id ae934460-5bfd-430e-8802-9605c0eca495 --public -j 

When we checked on 2026-11-14, that search returned several web search listings from different providers, each with a fixed per-call price. This tutorial uses Web Search (Basic, no extraction), provider youcom:

  • Endpoint: POST https://api.openany.si/v1/agent-apis/youcom/v1/search
  • Required input: query (string). Optional: count, offset, country, language, include_domains, exclude_domains and a few more.
  • Price on the listing: $0.005 per call (contract revision 3).

Prices and revisions can change, so read the listing page before you run anything. If you prefer a different provider, the same flow works with any search listing. Just copy its endpoint and schema.

Step 2: Call search over plain HTTP

This is the request shown on the listing page, trimmed to two fields:

curl 'https://api.openany.si/v1/agent-apis/youcom/v1/search' \ -X POST \ -H "Authorization: Bearer $OPENANY_API_KEY" \ -H 'Content-Type: application/json' \ --data-raw '{"query": "Node.js 24 release notes", "count": 5}' 

When we ran this on 2026-11-14 it returned HTTP 200 in about two seconds. The body looks like this (trimmed):

{ "metadata": {"query": "Node.js 24 release notes", "search_uuid": "...", "latency": 0.51}, "openany_cost": 5000, "results": { "web": [ { "title": "Node.js — Node.js 24.0.0 (Current)", "url": "https://nodejs.org/en/blog/release/v24.0.0", "description": "...", "snippets": ["We're excited to announce the release of Node.js 24! ..."], "favicon_url": "...", "thumbnail_url": "..." } ] } } 

Three things to notice:

  • results.web holds the hits. Each has title, url, a short description and a snippets array with longer extracts, which is the better text to ground a model on. The listing's sample also shows a results.news array; our queries only came back with web, so treat news as optional.
  • openany_cost is the charge for this call in micro-USD: 5000 means $0.005, matching the listing. The response headers also carry an x-request-id you can match against your usage records.
  • Errors come back as JSON too. A request without a valid Authorization header returned HTTP 401 with "code": "openany_authentication_invalid", a readable message and "openany_cost": 0. Your agent can branch on code instead of parsing prose.

Step 3: Let the model choose the searches, on the same key

The model endpoint is OpenAI-compatible, so the official openai package works with a changed base_url. Model IDs are route-specific, so copy one from the model catalog. We tested with deepseek-v4-flash, which supported tool calling through OpenAny when we ran this on 2026-11-14. Check the listing for the model you pick.

The flow has two model calls. First the model gets a web_search tool and decides what to search for. Then it answers from numbered sources, with no tools offered:

import json import os import requests from openai import OpenAI KEY = os.environ["OPENANY_API_KEY"] BASE = "https://api.openany.si/v1" MODEL = os.environ.get("OPENANY_MODEL", "deepseek-v4-flash") # copy a current ID from the catalog client = OpenAI(api_key=KEY, base_url=BASE) def web_search(query: str, count: int = 5) -> list[dict]: r = requests.post( f"{BASE}/agent-apis/youcom/v1/search", headers={"Authorization": f"Bearer {KEY}"}, json={"query": query, "count": count}, timeout=30, ) body = r.json() if r.status_code != 200: raise RuntimeError(f"search failed: {r.status_code} {body.get('code')} {body.get('message')}") print(f"search {query!r}: openany_cost={body.get('openany_cost')} micro-USD") return [ {"title": w["title"], "url": w["url"], "text": " ".join(w.get("snippets") or [w.get("description", "")])} for w in body["results"].get("web", []) ] TOOLS = [{ "type": "function", "function": { "name": "web_search", "description": "Search the web. Returns titles, URLs and text snippets.", "parameters": { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"], }, }, }] def ask(question: str, max_searches: int = 2) -> str: # Round 1: let the model decide what to search for. first = client.chat.completions.create( model=MODEL, messages=[{"role": "user", "content": question}], tools=TOOLS, max_tokens=1000, # reasoning tokens count against this limit ) msg = first.choices[0].message if not msg.tool_calls: return msg.content # the model answered without searching # Run at most max_searches of the requested searches. sources = [] for tc in msg.tool_calls[:max_searches]: query = json.loads(tc.function.arguments or "{}").get("query") if query: sources += web_search(query) # Round 2: answer from the numbered sources, with no tools offered. numbered = "\n\n".join(f"[{i+1}] {s['title']} ({s['url']})\n{s['text']}" for i, s in enumerate(sources)) final = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": "Answer only from the numbered sources and cite them like [1]. If they don't cover something, say so."}, {"role": "user", "content": f"{question}\n\nSources:\n{numbered}"}, ], max_tokens=4000, # leave headroom: reasoning tokens count too ) print("model usage:", final.usage) return final.choices[0].message.content if __name__ == "__main__": print(ask("What are the headline changes in Node.js 24?")) 

On our run the model asked for two searches in parallel ("Node.js 24 headline changes release" and "Node.js 24 new features announcement"). The script ran both and the model answered with a cited list: the V8 13.6 and npm 11 upgrades, global URLPattern, Undici 7, the --permission flag and more, each tagged with source numbers.

Some things we learned while getting this to work:

  • Reasoning tokens count against max_tokens. This model reasons before it answers. With max_tokens=500 the final answer came back empty with finish_reason: "length", and on a longer prompt even 2,000 tokens all went to reasoning. The script leaves 4,000 for the answering call.
  • Keep the answering call free of tools. In a multi-round loop, the model kept asking for more searches, and tool_choice: "none" didn't stop it on the route we hit. Offering tools once and then answering from a plain sources message was the reliable pattern.
  • Cap the searches yourself. max_searches limits paid calls no matter how many the model asks for.

Every call draws from one balance. The searches are billed at the listing's per-call price and the model calls at the route's token rates, and all of them show up in the same usage records. openany calls list listed both kinds of rows side by side, each with a chargedCostMicroUsd.

Step 4: Let Claude Code, Cursor or Codex do it

In a coding agent you usually don't write this loop yourself. The OpenAny skill plus CLI gives the agent the same two capabilities through its terminal, and it leaves the agent's own main model alone.

  1. Save the skill where your client looks for it: .claude/skills/openany/SKILL.md (Claude Code), .cursor/skills/openany/SKILL.md (Cursor) or .agents/skills/openany/SKILL.md (Codex). The file is at https://openany.si/SKILL.md. Read it before installing.
  2. Install the CLI (Node.js 22+) and run openany setup --scenario call in your own terminal to store the key.
  3. Give the agent a bounded task:

Use the openany skill. Search the web for the Node.js 24 release notes with one search API, show me the price before calling, then summarize the top results with links. Budget: $0.05.

The skill tells the agent to check the balance, discover a search API, inspect its schema and price, then run it, and to report the request ID and cost. You can audit afterwards with openany calls list --limit 20 -j.

If you also want the editor's chat model billed through OpenAny, that's a separate, optional step: Cursor's Override OpenAI Base URL set to https://api.openany.si/v1, or a custom model_providers.openany block in ~/.codex/config.toml with wire_api = "responses". Check that the chosen model route supports the protocol your client needs first; Chat Completions support alone doesn't guarantee Codex's Responses API works.

OpenRouter + Tavily vs one balance: an honest comparison

OpenRouter alone is fine when your agent only needs models. It's a mature router with a large model catalog and a long track record. If you never call a data API, adding OpenAny gives you nothing.

OpenRouter plus a separate search API is fine when you only need one or two data sources and you're happy managing them. Tavily, Exa and others are built specifically for agent search, with their own docs and SDKs. Two keys and two bills is a small cost if the setup doesn't change.

One balance helps when:

  • your agent keeps reaching for new data sources (search today, GitHub or finance data next week) and you don't want a new account for each,
  • you want one usage log that answers "what did this agent spend, and on what?" across models and APIs,
  • you want to fund agents with USDC or x402 top-ups as well as cards,
  • you want prices visible before the call, so the agent can show the cost and ask first.

Where OpenAny is weaker:

  • It's young. We launched in October 2026. Listings are not uptime guarantees, so test the endpoints you depend on.
  • It's hosted only. You can't self-host the gateway.
  • Protocol coverage varies by route. Responses API and tool calling depend on the model route.
  • x402 here is for funding your balance. Direct per-request x402 payment applies only to endpoints that explicitly support it.
  • Data APIs come from many providers. Response shapes differ between listings, so read each schema rather than assuming they match.

Keeping costs bounded

  • Prepaid balance: the worst case is what you loaded, not an open invoice.
  • Price before the call: every listing shows a price and billing unit; inspect returns them.
  • Timeouts: don't blindly retry a paid call after a timeout. Check openany calls list or calls get first. The docs are explicit that a missing record doesn't prove no charge.
  • 429: honor Retry-After and back off.
  • Auto top-up is opt-in: card auto top-up needs its own authorization on the Credits page.

FAQ

Can one API key cover both LLM calls and web search for my agent?
Yes, on OpenAny one key authenticates account-based model and API calls, and both draw from the same prepaid balance.

Is the model endpoint OpenAI-compatible?
Yes. The base URL is https://api.openany.si/v1 with Chat Completions. Responses API and Anthropic Messages support depend on the route.

Do I need to change Claude Code's main model?
No. The skill and CLI add external calls through the agent's existing tools. Changing the main model is optional and separate.

Is there a free tier or free credits?
No. Account and key creation are free, but calls are paid from prepaid credits.

How is web search priced?
Per listing. The search endpoint in this post was $0.005 per call when we checked on 2026-11-14. Other search providers have different prices; check the listing.

Can my agent pay with crypto?
You can top up the shared balance with USDC or x402, and card auto top-up is available too. API calls still authenticate with your OpenAny key.

When should I just use OpenRouter plus Tavily?
When you only need models plus one stable search provider and the two-account setup isn't a burden.

Questions or a broken example? Email team@openany.si. Docs: openany.si/docs.