Tools for your apps
Give Berth your API keys once, and every app can use web search, weather, product search, image generation, text to speech, transcription, translation and more, in any language, without holding a key. Weather and web fetch work with no key at all.
berth tools:enable myapp # sets BERTH_TOOLS_URL and BERTH_TOOLS_KEY
berth tools:key exa # prompts for the key
Every tool is a JSON POST from your server code:
curl "$BERTH_TOOLS_URL/v1/web/search" -H "Authorization: Bearer $BERTH_TOOLS_KEY" \
-d '{"query":"asics running shoes","limit":5}'
{"result":{"results":[…]},"meta":{"tool":"web.search","provider":"exa","cached":false,"ms":412}}
Failures are {"error":{"code","message"}} with a matching status, and the
message says what to run: 503 means a tool has no provider yet, and 429 is a
limit. The gateway only answers Berth containers, so call it from your server,
not from a browser.
| Tool | What it does | Providers |
|---|---|---|
web.search |
search the web | exa, serpapi |
web.fetch |
read a public page as text | none needed |
weather.current, weather.forecast |
conditions and daily forecast | weatherapi, or openmeteo with no key |
shopping.search, shopping.item |
products for sale | ebay (key is AppID:CertID) |
image.generate |
make an image | gemini, openai |
image.edit |
change an image | gemini |
image.describe |
describe an image, or answer a question about it | gemini, ai |
audio.speak |
text to speech | openai, elevenlabs |
audio.transcribe |
speech to text | openai |
text.translate |
translate text | deepl, ai |
pdf.parse |
read a PDF as Markdown | gemini |
video.generate |
make a short video; a job, and costly | gemini |
Apps name the tool, not the vendor, so you can change provider without touching
them. berth tools:use web.search serpapi,exa takes effect on the next call,
and the second provider takes over if the first fails. Open-Meteo is free for
non-commercial use; add a weatherapi key for anything else.
eBay keeps a production keyset disabled until it has an endpoint for marketplace
account deletion notices. berth tools:ebay-deletion turns one on and prints the
URL and verification token to paste into eBay's Alerts & Notifications page.
Berth keeps no eBay user data, so it only acknowledges the notices: an app that
stores eBay usernames has to handle those deletions itself.
berth tools:docs myapp prints the tools with their arguments as Markdown for
a coding agent, GET $BERTH_TOOLS_URL/v1/openapi.json generates a client in
any language, and berth tools:call weather.current '{"location":"Paris"}'
tries one from your terminal.
If you've given the AI gateway an OpenAI or Gemini key,
tools use it too, and berth tools:key is only for using a different one. ai
in the table is the AI gateway itself: your default model, or an alias called
tools if you make one, so translation can run on a local model with no key.
Images, audio and other files
Arguments that take a file accept a public URL, a data: URI or a file id.
Upload a file to get an id, and tools that make a file return one:
curl "$BERTH_TOOLS_URL/v1/files" -H "Authorization: Bearer $BERTH_TOOLS_KEY" \
-H 'Content-Type: image/jpeg' --data-binary @photo.jpg
curl "$BERTH_TOOLS_URL/v1/image/generate" -H "Authorization: Bearer $BERTH_TOOLS_KEY" \
-d '{"prompt":"a Mac mini in a sunny closet","aspect_ratio":"16:9"}'
{"result":{"file":{"id":"file_…","url":"http://…/v1/files/file_…","content_type":"image/png","bytes":1048576,"expires":"…"}},"meta":{…}}
Fetch url with the same Authorization header and save the bytes somewhere
your app serves from. Files last 24 hours, only the app that made one can read
it, and browsers can't reach the gateway, so a file's url won't work in an
<img> tag.
video.generate takes minutes, so it answers 202 with a job. Poll
GET $BERTH_TOOLS_URL/v1/jobs/<id> every 10 seconds or so until status is
done, with result, or failed, with error. A job survives Berth
restarting.
Video can run up a real bill in a few calls, so tools:enable leaves it out
unless you name it: --tools 'video.*', or --tools '*' for everything.
Gemini video needs a paid Gemini API key.
A tool that fails answers 502. If your app passes that on to a browser, answer 500 instead: Cloudflare swaps a 502 for its own HTML error page, and your message is lost.
From the admin page
The Tools tab on the admin page does all of this in a
browser: paste as many keys as you like, test each one with a real call, set
daily limits, choose which provider goes first, and turn tools on for each app
with its own limits. Keys are write-only there: the page shows that a key is
saved, never the key. Only @owners can change anything, each change is in
berth events with who made it, and turning tools on for an app redeploys it.
A key pasted there travels through Cloudflare's tunnel like the rest of the
page; berth tools:key on the Mac doesn't.
Limits and usage
berth tools:enable myapp --tools 'web.*,weather.forecast' --monthly-calls 10000 --rpm 60
berth tools:key ebay --daily 5000 # shared across every app using the key
berth tools # tools, providers and who uses them
berth tools:usage [app]
Answers are cached and shared between apps, for between five minutes and an
hour depending on the tool, so a free tier goes further; cache hits don't count
towards --monthly-calls. Arguments and results are never logged.
A dev version gets its own token, limited to keyless providers and the cache,
so an agent looping on dev:push can't spend your quota.
berth tools:enable myapp-dev --full lifts that.