API reference

BudsAI is now MCP + web. The /v1 REST surface is retired (410 Gone) — use the panel API or MCP instead.

/v1 retired. Chat, models and messages now live at 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.

Base URL — 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.

Your key carries your tier. Requests count against the same hourly and daily budget as the web app — see rate limits.

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:

FormExampleNotes
provider:modelprovtro:claude-haiku-4-5-20251001Canonical, as returned by /v1/models
provider/modelprovtro/claude-haiku-4-5-20251001Same thing with a slash
modelclaude-haiku-4-5-20251001Resolved 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
}
Function 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]
Ignore : 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.

What does not carry over. Upstream providers speak Chat Completions, so 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

ToolArgumentsWhat 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.
Every tool call counts against your quota, the same as a message in the web app. The server is stateless: no session ids, and 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.

Whatever you connect, every user gets. Tool calls run with the server's credentials, not the student's, and any prompt can trigger them. Connect read-only servers unless you have a reason not to. The loop stops after 5 rounds of tool calls and each result is truncated to 8000 characters.

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

FieldTypeNotes
modelstringOptional — falls back to the server default
messagesarrayRequired, non-empty
streambooleanDefaults to true
temperaturenumberPassed through
max_tokensnumberPassed through
toolsarrayPassed through
reasoning_effortstringPassed 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.

TierPer hourPer day
Free1560
Pro40200
Plus100600
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"
  }
}
CodeMeaning
401Missing or invalid key
403Account blocked
404Model not found
413Body too large (25 MB)
429Tier limit reached
502Upstream provider failed
Watch for failures dressed as answers. These providers sometimes return their own outage text as a normal 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.