API reference
BudsAI is now MCP + web. The /v1 REST surface
is retired (410 Gone) — use the panel API or MCP instead.
https://apichat.budsin.dev/v1 (keys from the panel).
Here only MCP (/mcp) and the web chat remain.
The endpoint docs below are kept for the panel API, whose shape is identical.
Introduction
One endpoint fronts every model across several providers. You use a single key of your own; the upstream provider keys never leave the server.
https://apichat.budsin.dev/v1
Authentication
Every request needs a bearer token from the panel: apichat.budsin.dev → create a token and use it here. BudsAI no longer issues its own keys; the panel is the single source of truth for users, quota and billing.
Authorization: Bearer sk-buds-...
x-api-key is accepted too, for clients that prefer it.
Quickstart
curl https://chat.budsin.dev/v1/chat/completions \
-H "Authorization: Bearer $BUDSAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "provtro:claude-haiku-4-5-20251001",
"messages": [{"role": "user", "content": "Hello"}]
}'
from openai import OpenAI
client = OpenAI(
base_url="https://chat.budsin.dev/v1",
api_key="sk-buds-...",
)
stream = client.chat.completions.create(
model="provtro:claude-haiku-4-5-20251001",
messages=[{"role": "user", "content": "Hello"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://chat.budsin.dev/v1",
apiKey: process.env.BUDSAI_KEY,
});
const res = await client.chat.completions.create({
model: "provtro:claude-haiku-4-5-20251001",
messages: [{ role: "user", content: "Hello" }],
});
console.log(res.choices[0].message.content);
GET/v1/models
Lists every model available to your key.
{
"object": "list",
"data": [
{
"id": "provtro:claude-haiku-4-5-20251001",
"object": "model",
"owned_by": "Gratisfy",
"context_window": 1000000
}
]
}
GET/v1/models/new
Recently added models, newest first. Same shape as /v1/models, plus added_at.
GET /v1/models/new?since=7d&limit=20
Model names
Three forms are accepted, so you rarely need the full id:
| Form | Example | Notes |
|---|---|---|
provider:model | provtro:claude-haiku-4-5-20251001 | Canonical, as returned by /v1/models |
provider/model | provtro/claude-haiku-4-5-20251001 | Same thing with a slash |
model | claude-haiku-4-5-20251001 | Resolved against the catalog |
Omit model entirely and the server default is used.
POST/v1/chat/completions
The main endpoint. Same request and response shape as OpenAI.
{
"model": "provtro:claude-sonnet-5",
"messages": [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "Explain photosynthesis"}
],
"temperature": 0.7,
"max_tokens": 500,
"stream": false
}
POST/v1/responses
OpenAI's Responses API, for agentic clients. Text-only subset: the request is translated to a chat completion upstream and the answer comes back as a Response object. instructions becomes the system prompt; input takes a string or a list of message items.
{
"model": "provtro:claude-haiku-4-5-20251001",
"instructions": "You are concise.",
"input": "Explain photosynthesis",
"max_output_tokens": 500,
"stream": false
}
tools are passed to the model and come back as function_call items for the client to execute; send the results back as function_call_output items. Other tool types are ignored. background: true is rejected: every call is stateless, so multi-turn clients must resend the full input each time. With stream: true the server synthesizes response.output_text.delta and response.function_call_arguments.delta events from the upstream tokens.
WebSocket clients can use wss://chat.budsin.dev/v1/responses with a subset of the Realtime protocol: session.update, conversation.item.create and response.create, receiving the same response.* events. Turns are kept per connection, so a response.create without input continues the session.
Streaming
Set "stream": true for server-sent events. The stream ends with
data: [DONE]. Omit the field and you get a single JSON
response, exactly as with OpenAI.
data: {"choices":[{"delta":{"content":"Plants"}}]}
data: {"choices":[{"delta":{"content":" capture"}}]}
data: [DONE]
: keepalive lines. The server emits SSE
comments every 15 s so proxies don't drop slow responses. Any compliant SSE
client skips them; if you parse by hand, discard lines that don't start with
data:.
Tool calling
Standard OpenAI tool calling, on models that support it — check
tools in the catalog before relying on it.
{
"model": "provtro:claude-haiku-4-5-20251001",
"messages": [{"role": "user", "content": "Weather in Lima?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}
The reply carries tool_calls and finish_reason:
"tool_calls". Append the result as a role: "tool" message
and call again.
Reasoning
Some models return their chain of thought separately. Two field names appear in the wild and both show up in the delta:
{"choices":[{"delta":{"reasoning_content":"Let me work through..."}}]}
{"choices":[{"delta":{"reasoning":"Let me work through..."}}]}
Look for models with thinking or reasoning in the
name. Reasoning arrives before the answer.
POST/v1/images/generations
Same shape as OpenAI. Pick a model whose modality is
image in the catalog; each generation counts as one message
against your quota.
curl https://chat.budsin.dev/v1/images/generations \
-H "Authorization: Bearer $BUDSAI_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "provtro:gpt-image-1.5", "prompt": "an astronaut cat", "size": "1024x1024"}'
Providers return either url or b64_json — handle
both.
POST/v1/messages
Anthropic's Messages API, translated to whatever the upstream model speaks.
Point the official Anthropic SDK, Claude Code or Claude Desktop at
https://chat.budsin.dev — note there is no /v1
suffix here, the SDK adds it — and it works unchanged. Authentication
accepts x-api-key as well as Authorization.
from anthropic import Anthropic
client = Anthropic(api_key="YOUR_BUDSAI_KEY",
base_url="https://chat.budsin.dev")
msg = client.messages.create(
model="provtro:claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{"role": "user", "content": "Explain photosynthesis"}],
)
print(msg.content[0].text)
Streaming emits the usual event sequence
(message_start, content_block_delta,
message_stop), and tool use arrives as tool_use
blocks with input_json_delta fragments.
/v1/messages/count_tokens answers with an estimate.
thinking blocks, prompt caching and exact token
counts have no equivalent and are not emitted. Everything else — system
prompts, images, tools, multi-turn tool results, stop sequences — is
translated both ways.
POST/mcp
An MCP server, so any MCP client can use BudsAI as a tool. This is the way into Claude Desktop, which accepts MCP servers but not a custom base URL. Transport is Streamable HTTP; authentication is the same API key, sent as a header.
Claude Code
claude mcp add --transport http budsai https://chat.budsin.dev/mcp \
--header "Authorization: Bearer $BUDSAI_KEY"
Claude Desktop — in claude_desktop_config.json:
{
"mcpServers": {
"budsai": {
"type": "http",
"url": "https://chat.budsin.dev/mcp",
"headers": { "Authorization": "Bearer YOUR_BUDSAI_KEY" }
}
}
}
Tools
| Tool | Arguments | What it does |
|---|---|---|
list_models |
search, modality, tools_only, limit |
Find an exact model id. Call this first. |
new_models |
since, search, modality, limit |
Newest additions first. since takes 24h, 7d or days (default 30d). |
ask_model |
model, prompt, system, max_tokens |
Send a prompt to any model and get its answer back. |
generate_image |
model, prompt, size |
Generate an image; returned inline when under 5 MB. |
GET /mcp answers 405 because there is no
server-to-client stream.
Clients that prefer WebSocket (Codex tries wss:// first) can use wss://chat.budsin.dev/mcp with the same key and JSON-RPC protocol. The server pings every 30 s to keep the connection alive.
MCP tools in the chat
BudsAI can also be an MCP client: connect it to MCP servers and the
models in the web app can call their tools. Configure them in
/etc/budsai-mcp.json on the server:
{
"servers": [
{
"id": "school",
"label": "School data",
"url": "https://mcp.example.org/mcp",
"headers": { "Authorization": "Bearer ..." },
"enabled": true
}
]
}
The file is re-read when it changes, so no restart is needed. Tools appear
to the model as serverid__toolname, and each server's tool list
is cached for five minutes. A server that is down is skipped; the rest keep
working.
In the chat, a Tools switch appears in the top bar whenever
at least one server is connected, and each call is shown as a row above the
answer. Tools are only offered to models whose catalog entry says
tools.
Architecture
BudsAI is two layers. New-API (panel at
https://apichat.budsin.dev) owns providers, keys, billing and
failover: every model lives in one or more channels, each with its
own keys, prices and priorities. If a channel dies, traffic moves on its own.
BudsAI sits in front and adds what New-API does not have:
an MCP server (list_models,
ask_model, generate_image,
generate_video), an agentic tool loop, a real browser for the
agent (navigate, click, login, screenshots), Anthropic↔OpenAI translation
(Messages), tiers and per-tier model allowlists.
client (opencode, Claude Desktop)
→ New-API https://apichat.budsin.dev/v1 (channels + billing, keys del panel)
→ upstream relays (emtf, chatwave, justwoker, ...)
web / MCP (agent, browser, tiers)
→ BudsAI https://chat.budsin.dev (/chat, /mcp — sin /v1)
Models served through New-API carry the newapi: prefix, e.g.
newapi:anthropic/claude-sonnet-4-6. Direct providers
(chatwave:, fetcher:, …) stay as backup.
Parameters
| Field | Type | Notes |
|---|---|---|
model | string | Optional — falls back to the server default |
messages | array | Required, non-empty |
stream | boolean | Defaults to true |
temperature | number | Passed through |
max_tokens | number | Passed through |
tools | array | Passed through |
reasoning_effort | string | Passed through where supported |
Unknown fields are forwarded to the provider untouched, so anything a provider supports works even if it isn't listed here.
Rate limits
Limits come from your tier, shared between the web app and the API.
| Tier | Per hour | Per day |
|---|---|---|
| Free | 15 | 60 |
| Pro | 40 | 200 |
| Plus | 100 | 600 |
| Max | ∞ | ∞ |
Over the limit you get 429 with a Retry-After
header in seconds.
Errors
Errors follow the OpenAI envelope:
{
"error": {
"message": "Invalid API key",
"type": "authentication_error"
}
}
| Code | Meaning |
|---|---|
401 | Missing or invalid key |
403 | Account blocked |
404 | Model not found |
413 | Body too large (25 MB) |
429 | Tier limit reached |
502 | Upstream provider failed |
200 reply —
for example "use.ai is at capacity right now". If a response looks
like an error message rather than an answer, treat it as one and retry on a
different provider.
SDKs
Any OpenAI-compatible client works: the official Python and JavaScript SDKs, LangChain, LlamaIndex, opencode, Cursor, and so on. Set the base URL and the key, nothing else.
There's also a small terminal client written for BudsAI, with a chat TUI and
a one-shot mode — see budsai-cli.