Create a new backend app with isolated database and API endpoints.
Returns: app_id, api_url, url (frontend URL), and provisioning status.
Example:
Input: { name: "my-blog" }
Output: {
app_id: "app_abc123",
api_url: "https://api.butterbase.dev/v1/app_abc123",
url: "https://my-blog.butterbase.dev",
_meta: { next_actions: [...] }
}
URL guide:
- api_url: Your API endpoint for database queries, auth, and functions (e.g. https://api.butterbase.dev/v1/app_abc123)
- url: Your frontend URL where your deployed site is served (e.g. https://my-blog.butterbase.dev)
- These are different! The api_url is for backend requests, the url is where users visit your app.
Next steps: Use manage_schema (action: "apply") to define tables, then manage_oauth (action: "configure") for auth.
Common errors:
- Name already exists: Choose a different name or use manage_app (action: "list") to find existing app
- Invalid characters: Use only lowercase letters, numbers, hyphens, underscores
- Name too long: Maximum 63 characters
The response includes _meta.next_actions with recommended next steps.
Parameters (2)
namestringrequired
App name (lowercase alphanumeric, hyphens, underscores)
regionstring
Region to provision in (e.g. "us-east-1", "us-west-2"). Defaults to the control-api's home region.
manage_schema
Manage the database schema: read current schema, apply changes, preview changes, and audit migration history.
Actions:
- "get": Get the current schema (tables, columns, indexes) and api_base
- "apply": Apply a declarative schema. Diffs against current and runs the safe DDL.
- "dry_run": Preview the SQL that "apply" would run, without executing
- "list_migrations": List applied migrations (most recent first)
Parameters by action:
get: { app_id, action: "get" }
apply: { app_id, action: "apply", schema, name? }
dry_run: { app_id, action: "dry_run", schema }
list_migrations: { app_id, action: "list_migrations" }
Schema example:
{
tables: {
posts: {
columns: {
id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
title: { type: "text", nullable: false },
author_id: { type: "uuid", references: { table: "users", column: "id", onDelete: "CASCADE" } },
created_at: { type: "timestamptz", default: "now()" }
}
}
}
}
Idempotency: "apply" is safe to call multiple times. If the schema is already up-to-date, returns "Schema is up to date".
Destructive operations: Require explicit opt-in via the _drop (table-level) or _dropColumns (column-level) fields.
Common errors:
- VALIDATION_INVALID_SCHEMA: schema format does not match the DSL
- STATE_PREREQUISITE_MISSING: add _drop / _dropColumns to authorize destructive ops
- QUOTA_TABLE_LIMIT: max 50 tables per app
- RESOURCE_NOT_FOUND: app_id does not exist
Parameters (4)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
schemaobject
Required for apply/dry_run. The desired database schema.
namestring
Optional for apply. Migration name (auto-generated if omitted).
manage_rls
Manage Row-Level Security (RLS): enable on tables, create/update/delete policies, list, and one-shot user isolation setup.
Actions:
- "enable": Enable RLS on a table (foundation — no policies yet)
- "create_policy": Create a custom RLS policy with USING / WITH CHECK expressions
- "update_policy": Atomically update an existing policy (drops and re-creates in one tx)
- "create_user_isolation": One-shot — enable RLS, create policy so users see only their rows, install auto-populate trigger
- "list": List all RLS policies for the app (and tables_with_rls without policies)
- "delete": Delete one policy (if policy_name set) or ALL policies on the table (and disable RLS)
Parameters by action:
enable: { app_id, action: "enable", table_name }
create_policy: { app_id, action: "create_policy", table_name, policy_name, command?, role?, using_expression?, with_check_expression?, restrictive?, user_column? }
update_policy: { app_id, action: "update_policy", table_name, policy_name, command?, role?, using_expression?, with_check_expression?, restrictive? }
create_user_isolation: { app_id, action: "create_user_isolation", table_name, user_column, public_read_column? }
list: { app_id, action: "list" }
delete: { app_id, action: "delete", table_name, policy_name? }
Built-in roles (assigned automatically by the platform — you never create them):
- butterbase_anon: no auth header → "anon" in policies
- butterbase_user: valid end-user JWT → "user" in policies; current_user_id() returns user id
- butterbase_service: platform API key → automatic full-access bypass; no policy needed
create_policy guidance:
- command defaults to ALL. SELECT/DELETE: only using_expression. INSERT: only with_check_expression. UPDATE/ALL: both.
- role: omit to apply to all roles, or set "anon" / "user" to scope and prevent cross-role policy leaks.
- restrictive: true → policy is AND'd with permissive ones; useful for cross-table checks that must always hold.
- user_column: pass to install a BEFORE INSERT trigger that auto-fills the column from current_user_id() —
without it, clients must include the column in POST bodies or insert is rejected with AUTH_RLS_POLICY_VIOLATION.
- For UUID columns, cast: current_user_id()::uuid
Cross-table subqueries pitfall:
EXISTS(SELECT 1 FROM other_table WHERE ...) inside a policy runs under the SAME user's RLS context.
If other_table has user_isolation, the subquery only sees the current user's rows, even for "public" rows.
Fix: add a permissive SELECT policy on the referenced table for the rows the subquery needs, OR use
"create_user_isolation" with public_read_column to set this up in one call.
create_user_isolation does:
1. Enables RLS on the table
2. User isolation policy (rows where user_column = current_user_id())
3. Auto-populate trigger for user_column on INSERT
4. Auto service bypass policy
5. If public_read_column set: extra SELECT policies for butterbase_user + butterbase_anon
allowing reads where that boolean column is true ("own rows + public read" pattern in one call)
delete behavior:
- With policy_name: removes that single policy (RLS stays enabled)
- Without policy_name: removes ALL policies AND disables RLS — table becomes globally accessible
Common errors:
- VALIDATION_TABLE_NOT_FOUND: create the table with manage_schema (action: "apply") first
- VALIDATION_COLUMN_NOT_FOUND: user_column missing from the table
- VALIDATION_INVALID_TYPE: user_column must be UUID or TEXT
- RLS_TYPE_MISMATCH: cast types in expressions, e.g. current_user_id()::uuid
- RLS_INVALID_EXPRESSION: SQL syntax error
- RESOURCE_NOT_FOUND: policy doesn't exist (update_policy) — use create_policy first
Idempotency: enable, create_policy, update_policy, create_user_isolation, delete — all safe to retry.
Parameters (11)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
table_namestring
Required for enable/create_policy/update_policy/create_user_isolation/delete.
policy_namestring
Required for create_policy/update_policy. Optional for delete (omit to remove ALL policies). Alphanumeric + underscores.
commandstring
create_policy/update_policy. Default: ALL.
rolestring
create_policy/update_policy. Scope to butterbase_anon or butterbase_user. Recommended.
using_expressionstring
create_policy/update_policy. SQL for USING clause. Required for SELECT/DELETE/ALL/UPDATE.
with_check_expressionstring
create_policy/update_policy. SQL for WITH CHECK clause. Required for INSERT.
restrictiveboolean
create_policy/update_policy. If true, AS RESTRICTIVE policy (AND with permissive).
user_columnstring
create_policy: install BEFORE INSERT auto-populate trigger. create_user_isolation: required, the user-id column.
public_read_columnstring
create_user_isolation only. Boolean column — adds SELECT policies for user/anon allowing reads where this is true.
manage_oauth
Manage OAuth providers for end-user authentication (configure, get, update, delete).
Actions:
- "configure": Set up a new OAuth provider (idempotent upsert)
- "get": Read provider config (single provider or all). client_secret is redacted.
- "update": Patch existing provider — only supplied fields change
- "delete": Remove a provider. Existing sessions remain valid until expiry.
Built-in providers (URLs/scopes auto-filled — only client_id, client_secret, redirect_uris required):
google, github, discord, facebook, linkedin, microsoft, apple, x
For any other provider name, supply authorization_url, token_url, userinfo_url manually.
Parameters by action:
configure: { app_id, action: "configure", provider, client_id, client_secret, redirect_uris, scopes?, authorization_url?, token_url?, userinfo_url?, provider_metadata? }
get: { app_id, action: "get", provider? } // omit provider to list all
update: { app_id, action: "update", provider, ...fields-to-change }
delete: { app_id, action: "delete", provider }
Example — configure (Google):
Input: { app_id: "app_abc123", action: "configure", provider: "google",
client_id: "...", client_secret: "GOCSPX-...",
redirect_uris: ["https://api.butterbase.ai/auth/app_abc123/oauth/google/callback"] }
Example — configure (Apple, requires provider_metadata):
Input: { ..., provider: "apple", provider_metadata: { teamId, keyId, privateKey } }
Provider notes:
- X (Twitter): no email — synthetic {username}@users.noreply.x.local is used
- Apple: only returns name on first auth; uses POST callback (handled automatically); requires provider_metadata { teamId, keyId, privateKey }
- Facebook: default scopes email, public_profile
OAuth flow after configure:
GET {api_base}/auth/{app_id}/oauth/{provider}?redirect_to=https://yourapp.com/auth/callback
After successful authentication, user is redirected to redirect_to with tokens as query params.
Common errors:
- RESOURCE_NOT_FOUND: app or provider doesn't exist
- VALIDATION_INVALID_SCHEMA: empty client_id/client_secret, or invalid URL on a custom provider
Idempotency: configure/update/delete are safe to retry.
Warning (delete): prevents future sign-ins via that provider; existing sessions remain valid until they expire.
Parameters (11)
app_idstringrequired
The app ID (e.g. app_abc123def456)
actionstringrequired
The action to perform
providerstring
Provider name. Required for configure/update/delete. Optional for get (omit to list all).
client_idstring
OAuth client ID. Required for configure.
client_secretstring
OAuth client secret. Required for configure (Apple: placeholder OK; real secret derived from provider_metadata).
redirect_urisarray
Required for configure. Format: ["{api_base}/auth/{app_id}/oauth/{provider}/callback"]
scopesarray
OAuth scopes. Optional for built-in providers (sensible defaults).
authorization_urlstring
Required for custom providers on configure.
token_urlstring
Required for custom providers on configure.
userinfo_urlstring
Required for custom providers on configure.
provider_metadataobject
Provider-specific metadata. Required for Apple: { teamId, keyId, privateKey }.
butterbase_docs
Read comprehensive Butterbase documentation (local, no API calls).
Available topics:
- all: Complete documentation (default)
- overview: Platform introduction and key features
- mcp: MCP tool reference and examples
- rest: HTTP data API (auto-generated REST endpoints)
- auth: End-user authentication (OAuth, JWT)
- storage: File upload/download with S3
- functions: Serverless functions (triggers, context)
- frontend: Static frontend deployment (upload zip, deploy to live URL)
- ai: AI model gateway (chat completions, BYOK, usage)
- meetings: Meeting bots that join Zoom/Meet/Teams/Webex calls and return recordings + transcripts
- billing: Your Butterbase plan, usage meters, app-level Stripe Connect (subscriptions and one-time payments)
- platform: MCP over HTTP, /llms.txt, subdomains, suggestions, rate limits
- regions: Choosing a region at app creation, moving apps between regions, discovering the live region list
- schema: Schema DSL reference (types, indexes, constraints)
- sdk: TypeScript SDK installation, client setup, query builder, auth, storage, functions
- cli: CLI installation, commands for apps, schema, functions, storage, config
- integrations: Third-party integrations (OAuth connect flow, tool execution, SDK, CLI)
- substrate: Per-user memory + action coordination plane for AI agents (entities, decisions, attention rules, action ledger, outbox, ws stream, ctx.substrate inside functions)
Example:
Input: { topic: "auth" }
Output: Full authentication documentation with OAuth setup, JWT handling, etc.
Don't know the topic slug? Pass a freeform { query: "..." } instead and the tool
returns the best-matching section plus an index of related topics:
Input: { query: "how do I send email" }
Output: Ranked topic index + the full text of the top-matching section.
Use this to:
- Learn Butterbase features and APIs
- Get code examples for common tasks
- Reference schema DSL syntax
- Understand authentication flow
- Learn about app monetization (subscriptions and one-time purchases)
Note: This is a local documentation tool. No network requests are made.
Idempotency: Safe to call anytime (read-only operation).
Freeform search across all docs. When provided (and no topic is given), returns the best-matching section plus a ranked index of related topics. Use when you do not know the exact topic slug.
manage_storage
Manage app storage: presigned upload/download URLs, list/delete objects, update config.
Actions:
- "upload_url": Get a presigned PUT URL to upload a file (expires in 15 min)
- "download_url": Get a presigned GET URL for a stored file (expires in 1 hour)
- "list": List all objects in app storage with metadata
- "delete": Permanently delete an object from S3 + database
- "update_config": Update storage config (e.g., publicReadEnabled)
Parameters by action:
upload_url: { app_id, action: "upload_url", filename, content_type, size_bytes, public? }
download_url: { app_id, action: "download_url", object_id }
list: { app_id, action: "list" }
delete: { app_id, action: "delete", object_id }
update_config: { app_id, action: "update_config", publicReadEnabled? }
object_id is the UUID returned from upload or list. Do NOT pass the s3_key / bucket path
(e.g. app_id/user_id/uuid_file.jpg) — that is metadata only and is not a usable URL.
Upload workflow:
1. action: "upload_url" → returns { upload_url, object_id, expires_at }
2. PUT the file to upload_url with the matching Content-Type header
3. Persist object_id (e.g. users.avatar_id)
4. Later: action: "download_url" with that object_id
Set public: true on upload_url to make the file downloadable by any authenticated user
(e.g. post images, avatars). Files are private by default.
publicReadEnabled (update_config):
- true: any authenticated user can download any file (uploads/deletes still user-scoped)
- false (default): users can only download their own files; platform auth (API key) can still access any file
Limits & errors:
- Files: max 10 MB each (QUOTA_FILE_SIZE_EXCEEDED)
- QUOTA_STORAGE_EXCEEDED: delete unused files or upgrade plan
- RESOURCE_NOT_FOUND: app or object doesn't exist (verify object_id, not s3_key)
- delete is idempotent (no-op if already deleted); upload/download URL generation is not (new URL each call)
Warning: "delete" cannot be undone. Update DB references (e.g. users.avatar_id) first.
Parameters (8)
app_idstringrequired
The app ID (e.g. app_abc123def456)
actionstringrequired
The action to perform
filenamestring
Required for upload_url. The filename for the upload.
content_typestring
Required for upload_url. MIME type (e.g. "image/png", "application/pdf").
size_bytesinteger
Required for upload_url. File size in bytes.
publicboolean
Optional for upload_url. Mark file as publicly downloadable. Default: false.
object_idstring
Required for download_url and delete. Storage object UUID — not the s3_key path.
publicReadEnabledboolean
Optional for update_config. Enable/disable app-wide public read.
manage_kv
Manage app KV store: config rules (expose/unexpose namespaces) and data-plane operations (get/set/del/incr etc).
Actions — Config:
- "list_rules": List all KV namespace exposure rules for the app
- "expose": Expose a key pattern with read/write role access control
- "unexpose": Remove an exposure rule by pattern
- "stats": Get KV usage stats (key count, memory, etc.)
- "scan": Scan keys by prefix (cursor-based, params: prefix, limit, cursor)
- "flush": Delete ALL keys in the KV store (requires confirm: true; include_config?: true also wipes rules)
Actions — Data plane:
- "get": Get the value of a key (opts: raw?, touch?)
- "set": Set a key to a value (opts: ttl?, ephemeral?, raw?)
- "del": Delete one key
- "incr": Increment a key's integer value (opts: by?)
- "decr": Decrement a key's integer value (opts: by?)
- "setnx": Set a key only if it does not already exist (opts: value, ttl?)
- "setex": Set a key with an explicit TTL in seconds (same as set + ttl)
- "cas": Compare-and-swap: atomically set next only if current value matches expected
- "exists": Check if a key exists
- "ttl": Get remaining TTL of a key in seconds
- "expire": Set a TTL on an existing key
- "mget": Get values of multiple keys at once (uses batch op)
- "mset": Set multiple key-value pairs at once (uses batch op; entries: {key: value})
Parameters by action:
list_rules: { app_id, action: "list_rules" }
expose: { app_id, action: "expose", pattern, read, write }
unexpose: { app_id, action: "unexpose", pattern }
stats: { app_id, action: "stats" }
scan: { app_id, action: "scan", prefix?, limit?, cursor? }
flush: { app_id, action: "flush", confirm: true, include_config? }
get: { app_id, action: "get", key, raw?, touch? }
set: { app_id, action: "set", key, value, ttl?, ephemeral?, raw? }
del: { app_id, action: "del", key }
incr: { app_id, action: "incr", key, by? }
decr: { app_id, action: "decr", key, by? }
setnx: { app_id, action: "setnx", key, value, ttl? }
setex: { app_id, action: "setex", key, value, ttl }
cas: { app_id, action: "cas", key, expected, next }
exists: { app_id, action: "exists", key }
ttl: { app_id, action: "ttl", key }
expire: { app_id, action: "expire", key, ttl }
mget: { app_id, action: "mget", keys }
mset: { app_id, action: "mset", entries }
Warning: "flush" deletes ALL keys and cannot be undone. Always pass confirm: true explicitly.
Parameters (21)
app_idstringrequired
The app ID (e.g. app_abc123def456)
actionstringrequired
The action to perform
patternstring
Key pattern for expose/unexpose (e.g. "user:*")
prefixstring
Key prefix for scan (e.g. "user:")
keystring
The KV key for single-key data-plane actions
valueany
The value to set — can be any JSON (object, array, string, number)
expectedany
The expected current value for CAS (compare-and-swap)
nextany
The next value to set for CAS if expected matches
ttlnumber | null
TTL in seconds (null = no expiry; for set, setex, expire, setnx)
cursorstring
Pagination cursor for scan (omit for first page)
limitinteger
Max keys to return per scan page (max 1000)
confirmboolean
Must be true to execute flush
include_configboolean
Also wipe exposure rules when flushing (use with flush)
keysarray
Array of keys for mget
entriesobject
Object of key→value pairs for mset (values can be any JSON)
readstring
Read access role for expose
writestring
Write access role for expose
bynumber
Amount to increment/decrement (default 1, for incr/decr)
ephemeralboolean
Store in ephemeral DB (shorter-lived, for set/setnx)
rawboolean
Store/retrieve raw string without JSON wrapping (for set/get)
touchboolean
Reset TTL to original on read (for get)
query_audit_logs
Query audit events for an app — authentication, admin mutations, and function invocations.
Returns a unified event stream. Each event has:
category 'auth' | 'admin' | 'function'
event_type e.g. 'login', 'schema.apply', 'function.deploy', 'function.invoke'
action 'create' | 'update' | 'delete' | 'invoke' | 'enable' | 'disable' | null
resource_type which resource the event acted on
resource_id the resource identifier (function name, policy name, deployment id, etc.)
actor_type 'platform_user' | 'app_user' | 'api_key' | 'system' | 'anonymous'
actor_id platform user id / app user id / api key id
event_data event-specific payload
success whether the event succeeded
correlation_id request id (ties related events together)
Use this to:
- Investigate who did what and when
- Debug failing auth / admin / function flows
- Monitor suspicious activity
- Trace a request across subsystems via correlation_id
Common filters:
- category='admin' to see only administrative mutations
- resource_type='function' + resource_id='my-fn' to see one function's history
- actor_id=<user-id> to see one actor's activity
- from / to to narrow to a time window
Idempotency: Safe to call anytime (read-only). Historical auth events predating migration 034 are included (normalized).
Parameters (12)
app_idstringrequired
The app ID (e.g. app_abc123def456)
categorystring
Event category filter
event_typestring
Exact event type match (e.g. "login", "schema.apply", "function.invoke")
actionstring
Action filter
resource_typestring
Resource type filter (e.g. "function", "rls_policy")
resource_idstring
Resource identifier filter
actor_idstring
Actor ID filter (platform user, app user, or API key id)
user_idstring
DEPRECATED — alias for actor_id
fromstring
ISO-8601 timestamp, inclusive lower bound on created_at
tostring
ISO-8601 timestamp, exclusive upper bound on created_at
limitinteger
Number of logs to return (default 100, max 500)
offsetinteger
Pagination offset (default 0)
deploy_function
Deploy or update a serverless function with custom business logic.
Example:
Input: {
app_id: "app_abc123",
name: "send-welcome-email",
code: "export async function handler(req, ctx) { ... }",
trigger: {
type: "http",
config: { method: "POST", path: "/welcome", auth: "required" }
}
}
Output: {
function_id: "fn_xyz789",
name: "send-welcome-email",
url: "https://api.butterbase.ai/v1/app_abc123/fn/send-welcome-email",
status: "deployed"
}
Function signature:
export async function handler(request: Request, context: {
db: PostgresClient, // Query your app database
env: Record<string, string>, // Access envVars
user: { id: string } | null, // Current user (if auth: required)
waitUntil: (promise: Promise) => void, // Keep alive for background work after response
idempotency: { // Webhook / event dedup primitive
claim: (key: string, opts?: { scope?: string; ttlSeconds?: number }) => Promise<boolean>
}
}): Promise<Response>
Console output: console.log(), console.info(), console.warn(), console.error(), and console.debug() calls are captured and stored with invocation logs. View them via manage_function (action: "get_logs").
IMPORTANT: Handlers MUST return a Response object (Web API standard).
Do NOT return plain objects like { status: 200, body: "..." }.
Idempotent webhook handlers with ctx.idempotency.claim():
Third-party webhook providers (Stripe, Telegram, GitHub, Slack, Twilio, Discord)
retry delivery on non-2xx responses with the same event id. Use ctx.idempotency.claim()
to atomically dedupe — it returns true if you're the first to see this key, false if
another invocation already claimed it.
export async function handler(req, ctx) {
const event = await req.json();
if (!(await ctx.idempotency.claim(event.id))) {
// Already processed — ack the retry without re-doing work.
return new Response('duplicate', { status: 200 });
}
await processEvent(event);
return new Response('ok', { status: 200 });
}
Options:
- scope: 'stripe' | 'telegram' | ... (default: 'default'). Namespace claims so
keys from different providers can never collide.
- ttlSeconds: mark the claim with an expiry. Cleanup is your responsibility:
DELETE FROM _idempotency_keys WHERE expires_at < now();
Background work with ctx.waitUntil():
Use ctx.waitUntil(promise) to keep the function alive after the response is sent.
This is useful for fire-and-forget tasks like sending emails or logging.
Background work has a 30-second timeout. ctx.db is available inside waitUntil promises.
export async function handler(req, ctx) {
ctx.waitUntil(fetch("https://api.email.com/send", { method: "POST", body: "..." }));
return new Response(JSON.stringify({ accepted: true }), {
headers: { "Content-Type": "application/json" }
});
}
Example:
export async function handler(req, ctx) {
const data = { hello: "world" };
return new Response(JSON.stringify(data), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}
Row-Level Security in Functions:
Functions respect RLS policies based on how they're invoked:
- Invoked with end-user JWT → butterbase_user role (RLS enforced)
* ctx.db queries see only the user's data
* ctx.user.id contains the authenticated user ID
* Use case: User-facing operations
- Invoked with platform API key → butterbase_service role (RLS bypassed)
* ctx.db queries see all data
* ctx.user is null
* Use case: Admin operations, background jobs
- Invoked by cron trigger → butterbase_service role (RLS bypassed)
* ctx.db queries see all data
* ctx.user is null
* Use case: Scheduled tasks, cleanup jobs
Trigger types:
- http: Invoke via HTTP request (GET, POST, etc)
- cron: Schedule periodic execution (e.g., "0 9 * * *" = daily at 9am)
- websocket: Trigger on WebSocket event from client via realtime connection
- s3_upload: Trigger on file upload [not yet implement
Parameters (14)
app_idstringrequired
The app ID to deploy the function to
namestringrequired
Function name (alphanumeric, hyphens, underscores)
codestringrequired
TypeScript/JavaScript code that exports a handler function
descriptionstring
Function description
envVarsobject
Environment variables (will be encrypted)
timeoutMsnumber
Execution timeout in milliseconds (default: 30000)
memoryLimitMbnumber
Memory limit in MB (default: 128)
triggerobject
Single-trigger shorthand. Prefer `triggers` for new code; this is kept for back-compat.
triggersarray
Canonical multi-trigger array. At most one trigger per type.
agent_toolboolean
When true, this function is exposed to agents in this app as a tool. Agents must still list its name under their graph_spec.tools.functions[] to call it.
agent_tool_descriptionstring
Short imperative description shown to the LLM (max 500 chars). Required for any function the model is expected to choose intelligently.
agent_tool_modestring
read_only (default) | read_write. read_write tools require human approval per call (HITL).
agent_tool_exposed_tostring
developer_only (default) — only dashboard/CLI test runs can call it. end_user — also callable by public agent invocations.
allow_service_key_impersonationboolean
Default true. Lets an app-scoped service-key caller assert "act as user X" via the X-Butterbase-As-User header — the runtime populates ctx.user with the asserted id before invoking. Set to false on admin-only or billing-webhook handlers that must never accept an as-user assertion: control-api will 403 such calls at the edge.
invoke_function
Invoke a deployed function and return its full HTTP response.
Example — POST with body:
Input: {
app_id: "app_abc123",
function_name: "submit-inquiry",
body: { email: "user@example.com", message: "hello" }
}
Output: {
status: 200,
headers: { "content-type": "application/json" },
body: { id: "uuid-1234" },
duration_ms: 47
}
Example — GET (no body):
Input: {
app_id: "app_abc123",
function_name: "public-catalog",
method: "GET"
}
Parameters:
- method defaults to POST. The function's trigger config determines which methods are valid.
- body is sent as JSON. Omit for GET/HEAD requests.
- headers are merged with the default auth headers.
Use this to:
- Test a function immediately after deployment
- Debug function logic with different inputs
- Verify function response format and status codes
Common errors:
- RESOURCE_NOT_FOUND: Function doesn't exist, use manage_function (action: "list") to verify
- Function timeout: Increase timeoutMs in deploy_function
- Runtime error: Check manage_function (action: "get_logs") for stack trace
Idempotency: Depends on function implementation (may have side effects).
Parameters (5)
app_idstringrequired
The app ID
function_namestringrequired
The function name to invoke
methodstring
HTTP method (default: POST)
bodyany
Request body (sent as JSON)
headersobject
Additional request headers
manage_function
Manage function lifecycle: list, get source, delete, get logs, and update environment variables.
Actions:
- "list": List all deployed functions with status, metrics, and invocation URLs
- "get": Retrieve a single function's full detail including its deployed source code
- "delete": Delete a deployed function permanently (IRREVERSIBLE)
- "get_logs": Retrieve recent invocation logs for debugging and monitoring
- "update_env": Update environment variables for a deployed function without redeploying code
- "update_settings": Toggle per-function settings (currently: allow_service_key_impersonation)
Parameters by action:
list: { app_id, action: "list" }
get: { app_id, action: "get", function_name }
delete: { app_id, action: "delete", function_name }
get_logs: { app_id, action: "get_logs", function_name, limit?, since?, level?, include_deleted? }
update_env: { app_id, action: "update_env", function_name, env }
update_settings: { app_id, action: "update_settings", function_name, allow_service_key_impersonation? }
Common errors:
- RESOURCE_NOT_FOUND: Function doesn't exist
- VALIDATION_INVALID_SCHEMA: Invalid parameter format
Idempotency: Safe to call anytime (list is read-only; delete is idempotent; update_env is safe to call multiple times).
Parameters (9)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
function_namestring
The function name (required for get, delete, get_logs, update_env, update_settings)
limitnumber
Maximum number of logs to return (default: 100)
sincestring
ISO timestamp to filter logs after this time
levelstring
Filter by log level (default: all)
include_deletedboolean
Include logs for soft-deleted functions (post-incident forensics). Default: false.
envobject
Environment variables to set (string) or delete (null)
allow_service_key_impersonationboolean
Per-function gate (Phase 2). When false, the platform 403s any call carrying X-Butterbase-As-User at the edge — use for admin-only or billing-webhook handlers that must never accept an as-user assertion.
select_rows
Query rows from a table using the auto-generated REST API.
By default, this tool authenticates with the platform API key (butterbase_service role),
which bypasses Row-Level Security and returns ALL rows regardless of RLS policies.
To test RLS enforcement from this tool, use the as_role and as_user parameters:
- as_role: "anon" — simulate an anonymous request (butterbase_anon role)
- as_role: "user", as_user: "<user-uuid>" — simulate a specific end-user (butterbase_user role)
Without as_role, this tool always runs as butterbase_service (full access).
Use this to:
- Fetch data from tables (as admin/service — sees all rows)
- Filter, sort, and paginate results
- Select specific columns
Example — Basic query:
Input: {
app_id: "app_abc123",
table: "posts",
limit: 10
}
Output: [
{ id: "uuid-1", title: "Hello World", created_at: "2024-01-15T10:00:00Z" },
...
]
Example — With filters:
Input: {
app_id: "app_abc123",
table: "posts",
filters: {
"status": "eq.published",
"created_at": "gt.2024-01-01"
},
order: "created_at.desc",
limit: 20
}
Filter operators:
- eq (equals): status=eq.published
- neq (not equals): status=neq.draft
- gt (greater than): age=gt.18
- gte (greater than or equal): age=gte.18
- lt (less than): price=lt.100
- lte (less than or equal): price=lte.100
- like (pattern match): title=like.%hello%
- ilike (case-insensitive): title=ilike.%hello%
- is (null/true/false): deleted_at=is.null
- in (list): id=in.(1,2,3)
- fts (full-text search): title=fts.hello world
Common errors:
- VALIDATION_TABLE_NOT_FOUND: Table doesn't exist, use manage_schema (action: "get") to verify
- VALIDATION_INVALID_SCHEMA: Invalid filter format
Idempotency: Safe to call multiple times (read-only operation).
Parameters (9)
app_idstringrequired
The app ID
tablestringrequired
The table name
filtersobject
Filter conditions (column: "operator.value")
selectstring
Comma-separated column names to return
orderstring
Sort order (e.g., "created_at.desc")
limitinteger
Maximum number of rows to return
offsetinteger
Number of rows to skip
as_rolestring
Simulate a specific role for RLS testing. "anon" for anonymous, "user" for authenticated end-user (requires as_user).
as_userstring
User ID (UUID) to simulate when as_role is "user". Required when as_role is "user".
insert_row
Insert a new row into a table using the auto-generated REST API.
By default, this tool authenticates with the platform API key (butterbase_service role),
which bypasses Row-Level Security. Inserts via this tool are not subject to RLS policies.
To test RLS enforcement on writes, use the as_role and as_user parameters:
- as_role: "anon" — simulate an anonymous insert (butterbase_anon role)
- as_role: "user", as_user: "<user-uuid>" — simulate a specific end-user (butterbase_user role)
Without as_role, this tool always runs as butterbase_service (full access, bypasses RLS).
Use this to:
- Add new records to tables (as admin/service — bypasses RLS)
- Bootstrap initial data
- Create test data
Example:
Input: {
app_id: "app_abc123",
table: "posts",
data: {
"title": "Hello World",
"body": "This is my first post",
"status": "draft"
}
}
Output: {
id: "uuid-1234",
title: "Hello World",
body: "This is my first post",
status: "draft",
created_at: "2024-01-15T10:00:00Z"
}
Notes:
- Only provide columns that exist in the table schema
- Columns with defaults (like id, created_at) can be omitted
- The response includes the full inserted row with generated values
Common errors:
- VALIDATION_TABLE_NOT_FOUND: Table doesn't exist, use manage_schema (action: "get") to verify
- VALIDATION_UNIQUE_CONSTRAINT_VIOLATION: Duplicate value in unique column
- VALIDATION_FOREIGN_KEY_VIOLATION: Referenced record doesn't exist
- VALIDATION_NOT_NULL_VIOLATION: Required field is missing
Idempotency: Not idempotent - creates a new row each time.
Parameters (5)
app_idstringrequired
The app ID
tablestringrequired
The table name
dataobjectrequired
Column values to insert
as_rolestring
Simulate a specific role for RLS testing. "anon" for anonymous, "user" for authenticated end-user (requires as_user).
as_userstring
User ID (UUID) to simulate when as_role is "user". Required when as_role is "user".
create_frontend_deployment
Create a frontend deployment and get an upload URL. Upload your built frontend as a zip file to the returned URL, then use manage_frontend (action: "start_deployment") to trigger the deploy.
Steps:
1. Call this tool to get an upload URL
2. Upload your zip file to the URL (e.g. curl -X PUT "{uploadUrl}" -H "Content-Type: application/zip" --data-binary @frontend.zip)
3. Call manage_frontend (action: "start_deployment") with the returned deployment_id
Example:
Input: { app_id: "app_abc123", framework: "react-vite" }
Output: {
deployment_id: "uuid-1234",
uploadUrl: "https://...",
expiresIn: 900,
maxSizeBytes: 104857600
}
Prerequisites:
- App must exist (use init_app to create)
Free plan: 1 deployment per app. Deploying again automatically replaces the previous deployment (no need to delete first).
Starter+: unlimited deployments.
Framework options:
- react-vite: React app built with Vite (zip the dist/ folder)
- nextjs-static: Next.js static export (zip the out/ folder)
- static: Plain HTML/CSS/JS
- other: Any framework that produces static output
SPA routing: For SPA frameworks (react-vite, nextjs-static, other), a _redirects file is auto-injected so all routes serve index.html. If your zip already includes a _redirects file, it is preserved.
IMPORTANT — Zip file paths must use forward slashes (/), not backslashes (\). On Windows, zips created with built-in tools use backslashes, which causes all files to be served as text/html (breaking JS/CSS with MIME errors). On Windows use Git Bash or WSL to run: cd dist && zip -r ../frontend.zip .
Common errors:
- RESOURCE_NOT_FOUND: App doesn't exist
Idempotency: Not idempotent — creates a new deployment each time (replaces existing on free plan).
Your frontend will be deployed to https://<app-name>.butterbase.dev.
Next steps: Upload your zip to the returned URL, then call manage_frontend (action: "start_deployment").
Parameters (2)
app_idstringrequired
The app ID
frameworkstring
Frontend framework type
manage_edge_ssr
Manage Edge SSR (Cloudflare Workers) deployments: prebuilt-zip flow, server-side build flow, list history.
Actions:
- "create": Create a deployment from a locally-built zip; returns upload URL + deployment_id
- "start": Start the deployment after the zip is uploaded; polls until READY/ERROR (≤60s)
- "create_from_source": Server-side build — Mode 1: create deployment + return upload_url
- "start_from_source": Server-side build — Mode 2: kick off the build after source upload
- "list": List recent deployments (status, URL, sizes)
Two flows (pick ONE):
FLOW A — local build (you build with @cloudflare/next-on-pages locally):
1. Run `npx @cloudflare/next-on-pages` then zip the CONTENTS of `.vercel/output/static/`
(cd .vercel/output/static && zip -r ../../../edge-ssr.zip .)
On Windows use Git Bash or WSL; built-in zip tools use backslashes which break uploads.
2. action: "create" → { deployment_id, uploadUrl, expiresIn }
3. PUT zip to uploadUrl with Content-Type: application/zip
4. action: "start" → polls; returns { url, status: "READY" }
FLOW B — server-side build (Butterbase runs the build for you):
1. action: "create_from_source" → { deployment_id, upload_url, max_source_bytes }
2. PUT source zip (≤50 MB) to upload_url with Content-Type: application/zip
3. action: "start_from_source" with deployment_id + lockfile_hash (sha256 of package-lock.json)
→ { build_id, status, logs_url, status_url }
4. Stream logs_url for live build output; poll status_url for terminal status
Parameters by action:
create: { app_id, action, framework? }
start: { app_id, action, deployment_id }
create_from_source: { app_id, action, framework? }
start_from_source: { app_id, action, deployment_id, lockfile_hash, build_command?, output_dir?, package_manager?, user_env? }
list: { app_id, action, limit? }
framework: "nextjs-edge" (default) | "remix-edge" | "other-edge"
Status values: WAITING | UPLOADING | BUILDING | READY | ERROR | CANCELED | TIMEOUT
On TIMEOUT: deployment did not reach a terminal state within 60s. Use action: "list" to check
the current status, or call "start" again if it is still BUILDING.
Plan limits: Free = 1 deployment per app (replaces previous). Starter+ = unlimited.
Common errors:
- INVALID_STATUS / UPLOAD_EXPIRED: zip not uploaded before "start"
- STATE_PREREQUISITE_MISSING: source zip not uploaded before "start_from_source"
- QUOTA_FILE_SIZE_EXCEEDED: source zip exceeds 50 MB
- RESOURCE_NOT_FOUND: app or deployment doesn't exist
- EXTERNAL_CLOUDFLARE_ERROR: Workers for Platforms not configured
Build caching (start_from_source):
lockfile_hash is the node_modules cache key — same hash means cached node_modules (faster builds).
Compute it with: sha256sum package-lock.json | cut -d' ' -f1
Parameters (10)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
frameworkstring
Framework for create / create_from_source. Default: nextjs-edge.
deployment_idstring
Required for start and start_from_source.
limitnumber
Optional for list. Default: 20.
lockfile_hashstring
Required for start_from_source. sha256 of package-lock.json — node_modules cache key.
Manage frontend deployments, environment variables, and custom domains for a Butterbase app.
Actions:
- "start_deployment": Start a frontend deployment after uploading your zip file. Call after uploading zip to the URL returned by create_frontend_deployment. Polls until complete (up to 5 minutes).
- "list_deployments": List frontend deployment history for an app (read-only).
- "create_from_source": Create a source-based deployment and get a presigned upload URL (Mode 1). Upload your source zip to the URL via HTTP PUT with Content-Type: application/zip (max 50 MB).
- "start_from_source": Start the build for a source-based deployment (Mode 2). Requires deployment_id from create_from_source and a lockfile_hash.
- "set_env": Set environment variables for frontend builds (upserts).
- "configure_custom_domain": Manage custom domains. Requires domain_action sub-option.
Parameters by action:
start_deployment: { app_id, action: "start_deployment", deployment_id }
list_deployments: { app_id, action: "list_deployments" }
create_from_source: { app_id, action: "create_from_source" }
start_from_source: { app_id, action: "start_from_source", deployment_id, lockfile_hash, build_command?, output_dir?, package_manager?, user_env? }
set_env: { app_id, action: "set_env", vars }
configure_custom_domain: { app_id, action: "configure_custom_domain", domain_action, hostname?, domain_id? }
domain_action sub-options:
"add": { hostname } — Register a new custom domain
"list": {} — List all custom domains for an app
"status": { domain_id } — Check verification/SSL status of a domain
"remove": { domain_id } — Remove a custom domain
"verify": { domain_id } — Trigger re-verification of a pending domain
Common errors:
- RESOURCE_NOT_FOUND: App or deployment doesn't exist
- INVALID_STATUS: Deployment is not in WAITING status (zip may not have been uploaded yet)
- UPLOAD_EXPIRED: The upload URL expired before the zip was uploaded
- STATE_PREREQUISITE_MISSING: Source zip not yet uploaded (PUT to upload_url first)
- QUOTA_FILE_SIZE_EXCEEDED: Source zip exceeds 50 MB
- BUILD_FAILED: Build command exited with non-zero status (check logs_url for details)
- VALIDATION_INVALID_SCHEMA: vars must be a non-empty object
- feature_not_available: Free plan — upgrade to Pro (custom domains)
- RESOURCE_ALREADY_EXISTS: Hostname already registered
Parameters (12)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
deployment_idstring
The deployment ID (required for "start_deployment" and "start_from_source")
lockfile_hashstring
Required for "start_from_source": hex sha256 hash of the lockfile — used as the node_modules cache key
build_commandstring
"start_from_source": build command to run after install (default: npm run build)
output_dirstring
"start_from_source": output directory containing built static files (default: dist)
package_managerstring
"start_from_source": package manager to use for install (default: npm)
user_envobject
"start_from_source": environment variables to inject during the build (default: {})
varsobject
Required for "set_env": environment variables as key-value pairs
domain_actionstring
Required for "configure_custom_domain": the domain sub-action to perform
hostnamestring
Custom domain hostname (required for domain_action "add", e.g. app.example.com)
domain_idstring
Domain ID (required for domain_action "status", "remove", "verify")
submit_suggestion
Submit feedback, bug reports, or feature suggestions to the Butterbase platform team.
Use this tool when you encounter issues with Butterbase tools, want to suggest improvements,
or when a user asks you to report something to the Butterbase team.
Categories:
- bug_report: Something isn't working as expected or documented.
Example: "apply_schema fails silently when adding an enum column with a default value"
- feature_request: A capability that doesn't exist yet but would be useful.
Example: "Support for composite unique constraints across multiple columns"
- improvement: An existing feature works but could be better.
Example: "get_schema should include index definitions in its output"
- documentation: The docs are missing, unclear, or incorrect.
Example: "The deploy_function tool description doesn't mention the 50MB size limit"
Source:
- agent: You (the AI agent) are reporting this on your own initiative
- human_prompted: The human user asked you to report this
Returns: The created suggestion with a unique ID and status.
Example output:
{
suggestion: {
id: "a1b2c3d4-...",
category: "bug_report",
severity: "medium",
description: "apply_schema returns success but...",
affected_tool: "apply_schema",
status: "new",
created_at: "2026-04-05T10:00:00Z"
}
}
Recent tool calls are automatically captured as context — you don't need to
manually describe what you called. Just describe the issue or suggestion clearly.
Idempotency: Each call creates a new suggestion. Avoid submitting duplicates.
Parameters (8)
categorystringrequired
Type of feedback
descriptionstringrequired
Clear description of the issue, suggestion, or request. Be specific.
severitystring
How impactful is this? critical = blocks work, high = significant pain, medium = annoying, low = minor
affected_toolstring
Name of the Butterbase tool affected (e.g. apply_schema, deploy_function)
proposed_solutionstring
Your suggested fix or approach, if you have one
sourcestring
Whether you are reporting this on your own or at the user's request (default: agent)
app_idstring
The app ID if this suggestion relates to a specific app
agent_contextobject
Any additional structured context you want to attach (error messages, parameters tried, etc.)
manage_realtime
Manage realtime WebSocket notifications for database tables.
Actions:
- "configure": Enable realtime broadcasts (INSERT/UPDATE/DELETE) on the given tables.
Idempotent — already-enabled tables are skipped.
- "get": Return current realtime config (which tables, active LISTEN connection, websocket URL).
Parameters by action:
configure: { app_id, action: "configure", tables: [...] }
get: { app_id, action: "get" }
After configuring, clients connect via WebSocket:
ws://api.butterbase.local/v1/{app_id}/realtime
Client sends: { "type": "subscribe", "table": "messages" }
Server sends: { "type": "change", "table": "messages", "op": "INSERT", "record": {...} }
RLS enforcement:
- End-user JWT connections only receive changes they have permission to see
- API key / service connections receive all changes (RLS bypassed)
- Anonymous connections use butterbase_anon role policies
Prerequisites:
- Tables must already exist (use manage_schema action: "apply" first)
- For user-scoped data, enable RLS on the table first (manage_rls action: "enable" / "create_user_isolation")
Parameters (3)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
tablesarray
Required for configure. Table names to enable realtime on.
manage_auth_users
Manage end-user auth records for an app.
Actions:
- "list": Paginated list of app_users (id, email, provider, provider_uid, email_verified,
last_sign_in_at, created_at). Pass the next_cursor from a prior response to page.
- "delete": Hard-delete an app user by id. Cascades to refresh tokens and verification codes.
Use this to unblock OAuth migrations when an existing email/password row collides.
Parameters by action:
list: { app_id, action: "list", limit?, cursor? }
delete: { app_id, action: "delete", user_id }
Tips:
- Looking for a user by email? Call list and filter client-side; this tool does not search by email.
- To switch a user from email/password to Google OAuth without deleting, just have them sign in
with Google — the OAuth callback now links the existing email row in place automatically.
Parameters (5)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
user_idstring
Required for delete. The app_user UUID to delete.
limitinteger
Optional for list. 1–200, default 50.
cursorstring
Optional for list. Pass next_cursor from a prior response.
seed_database
Insert multiple rows into a table in a single call. Useful for seeding sample data,
bootstrapping test fixtures, or populating lookup tables.
IMPORTANT: This tool authenticates with the platform API key (butterbase_service role),
which bypasses Row-Level Security. Inserts via this tool are not subject to RLS policies.
Rows are inserted sequentially. If a row fails (e.g., duplicate key, constraint violation),
the tool skips it and continues with the remaining rows. The response reports how many
rows were inserted vs failed, with error details for each failure.
Example:
Input: {
app_id: "app_abc123",
table: "products",
rows: [
{ "name": "Widget", "price": 999, "category": "tools" },
{ "name": "Gadget", "price": 1499, "category": "electronics" },
{ "name": "Doohickey", "price": 299, "category": "tools" }
]
}
Output: {
inserted: 3,
failed: 0,
errors: [],
rows: [ { id: "uuid-1", ... }, { id: "uuid-2", ... }, { id: "uuid-3", ... } ]
}
Notes:
- Columns with defaults (like id, created_at) can be omitted
- Each row is an independent insert — failures don't roll back other rows
- Maximum 100 rows per call
Common errors (per row):
- VALIDATION_UNIQUE_CONSTRAINT_VIOLATION: Duplicate value in unique column
- VALIDATION_FOREIGN_KEY_VIOLATION: Referenced record doesn't exist
- VALIDATION_NOT_NULL_VIOLATION: Required field is missing
Idempotency: Not idempotent — creates new rows each time.
Parameters (3)
app_idstringrequired
The app ID
tablestringrequired
The table name
rowsarrayrequired
Array of row objects to insert (max 100)
manage_rag_content
Manage RAG (Retrieval-Augmented Generation) collections and documents. Collections are named
containers for documents that are chunked, embedded, and indexed for semantic search.
Actions:
Collection actions:
- "create_collection": Create a new collection
- "list_collections": List all collections in an app
- "get_collection": Get details for a specific collection (includes document counts by status)
- "delete_collection": Permanently delete a collection and all its documents/embeddings
Document actions:
- "ingest_document": Add a document (raw text or uploaded file) to be chunked, embedded, and indexed
- "list_documents": List all documents in a collection with their status
- "get_document_status": Check the processing status of a specific document
- "delete_document": Permanently delete a document and its chunks/embeddings
Parameters by action:
create_collection: { app_id, action: "create_collection", name, description?, access_mode?, chunk_size?, chunk_overlap? }
list_collections: { app_id, action: "list_collections" }
get_collection: { app_id, action: "get_collection", name }
delete_collection: { app_id, action: "delete_collection", name }
ingest_document: { app_id, collection, action: "ingest_document", text?, storage_object_id?, filename?, metadata? }
list_documents: { app_id, collection, action: "list_documents" }
get_document_status: { app_id, collection, action: "get_document_status", document_id }
delete_document: { app_id, collection, action: "delete_document", document_id }
access_mode options (create_collection):
- "private" (default): Only the app owner can query
- "shared": All authenticated users can query
- "custom": Use RLS policies for fine-grained access
Ingestion modes for ingest_document (provide one):
1. Raw text: provide "text" directly
2. File-based: upload via manage_storage (action: "upload_url") first, then provide "storage_object_id"
Supported file types: PDF, TXT, Markdown, CSV, HTML, DOCX, XLSX, PPTX.
Document statuses: "pending" → "processing" → "ready" (or "failed")
Workflow: create_collection → ingest_document → poll get_document_status until "ready" → query with rag_query.
Warning: "delete_collection" permanently removes the collection, all documents, and embeddings. Cannot be undone.
Warning: "delete_document" permanently removes the document and its embeddings. To replace, delete then re-ingest.
Common errors:
- RESOURCE_NOT_FOUND: App, collection, or document doesn't exist
- VALIDATION_DUPLICATE_NAME: Collection name already exists (create_collection)
- VALIDATION_ERROR: Neither text nor storage_object_id provided (ingest_document)
Parameters (13)
app_idstringrequired
The app ID (e.g. app_abc123def456)
actionstringrequired
The action to perform
namestring
Collection name (required for create_collection/get_collection/delete_collection). Lowercase alphanumeric, hyphens, underscores only.
Access control mode (create_collection only, default: private)
chunk_sizenumber
Max tokens per chunk (create_collection only, default: 512)
chunk_overlapnumber
Overlap tokens between chunks (create_collection only, default: 50)
collectionstring
The collection name (required for document actions)
document_idstring
Document ID (required for get_document_status/delete_document)
textstring
Raw text content to ingest (ingest_document only)
storage_object_idstring
UUID of an uploaded storage object from manage_storage action: "upload_url" (ingest_document only)
filenamestring
Filename hint for display and format detection (ingest_document only)
metadataobject
Key-value metadata to attach (ingest_document only, e.g. { source: "wiki" })
rag_query
Query a RAG collection using natural language to retrieve relevant document chunks.
Performs semantic search over the collection's indexed documents and returns the most
relevant chunks ranked by similarity. Optionally synthesizes an AI-generated answer
using the retrieved context.
Parameters:
- query: Natural language question or search phrase
- top_k: Number of chunks to retrieve (default 5, max 20)
- threshold: Minimum similarity score 0-1 (only return chunks above this score)
- synthesize: If true, uses an LLM to generate a natural language answer from the
retrieved chunks (default false — returns raw chunks only)
- model: LLM model to use for synthesis (only relevant when synthesize is true,
default: anthropic/claude-haiku-4.5)
- filter: Metadata filter to narrow results (e.g. { category: "faq" })
Example — raw retrieval:
Input: {
app_id: "app_abc123",
collection: "knowledge-base",
query: "How do I reset my password?",
top_k: 3
}
Output: {
chunks: [
{
text: "To reset your password, go to Settings > Security > Reset Password...",
score: 0.92,
document_id: "doc_abc",
metadata: { category: "faq", source: "help-center" }
},
...
]
}
Example — with synthesis:
Input: {
app_id: "app_abc123",
collection: "knowledge-base",
query: "How do I reset my password?",
top_k: 5,
synthesize: true
}
Output: {
answer: "To reset your password, navigate to Settings > Security and click...",
chunks: [ ... ],
model: "gpt-4o-mini"
}
Example — with metadata filter:
Input: {
app_id: "app_abc123",
collection: "knowledge-base",
query: "pricing plans",
filter: { category: "billing", version: "2.0" }
}
Use this to:
- Search documentation or knowledge bases using natural language
- Build AI-powered Q&A features for end users
- Find relevant context for AI assistants
- Power search bars with semantic understanding
Common errors:
- RESOURCE_NOT_FOUND: App or collection doesn't exist
- COLLECTION_EMPTY: No documents have been ingested yet
Idempotency: Safe to call anytime (read-only operation).
Parameters (8)
app_idstringrequired
The app ID (e.g. app_abc123def456)
collectionstringrequired
The collection name to query
querystringrequired
Natural language query or search phrase
top_knumber
Number of top matching chunks to return (default 5, max 20)
thresholdnumber
Minimum similarity score (0-1). Only chunks scoring above this threshold are returned.
synthesizeboolean
If true, generate a natural language answer from retrieved chunks using an LLM
modelstring
LLM model for synthesis (only used when synthesize is true)
filterobject
Metadata filter to narrow results (e.g. { category: "faq" })
manage_integrations
Manage third-party integrations for a Butterbase app (e.g., Gmail, Slack, Google Calendar).
Actions:
- "configure": Enable or manage a third-party integration toolkit for an app
- "rotate_credentials": Swap in new BYO OAuth client_id/client_secret without dropping connected accounts
- "disable": Disable a configured integration toolkit
- "list_available": List available integrations that can be enabled (curated or full catalog)
- "list_connected": List connected integration accounts for an app
- "list_tools": List available tool actions for connected integrations
- "execute_action": Execute a tool action on a connected integration (e.g., send email, create event)
Parameters by action:
configure: { app_id, action: "configure", toolkit, scopes?, display_name?, oauth_credentials? }
rotate_credentials: { app_id, action: "rotate_credentials", toolkit, oauth_credentials }
disable: { app_id, action: "disable", toolkit }
list_available: { app_id, action: "list_available", search? }
list_connected: { app_id, action: "list_connected" }
list_tools: { app_id, action: "list_tools", toolkit? }
execute_action: { app_id, action: "execute_action", tool_name, params?, user_id? }
Curated toolkits (first-class support, no BYO credentials needed):
gmail, google-calendar, slack, google-sheets, notion, github, hubspot, outlook, google-drive, discord
Non-curated toolkits (Twitter, LinkedIn, Reddit, etc.) usually require BYO OAuth credentials.
Use list_available with search=<name> first to inspect requires_byo_credentials and auth_schemes.
Example — configure (curated, managed auth):
Input: { app_id: "app_abc123", action: "configure", toolkit: "gmail", scopes: ["gmail.send"] }
Output: { id: "...", toolkit_slug: "gmail", enabled: true }
Example — configure (BYO OAuth credentials, e.g. Twitter/X):
Input: {
app_id: "app_abc123", action: "configure", toolkit: "twitter",
scopes: ["tweet.read", "tweet.write", "users.read", "offline.access"],
oauth_credentials: {
client_id: "...",
client_secret: "...",
generic_id: "<Twitter App Bearer Token>", // toolkit-specific extra field
auth_scheme: "OAUTH2"
}
}
Output: { id: "...", toolkit_slug: "twitter", enabled: true }
Example — rotate_credentials (after upstream OAuth client rotation):
Input: {
app_id: "app_abc123", action: "rotate_credentials", toolkit: "twitter",
oauth_credentials: { client_id: "new...", client_secret: "new..." }
}
Output: { id: "...", toolkit_slug: "twitter", enabled: true }
Example — list_available:
Input: { app_id: "app_abc123", action: "list_available", search: "twitter" }
Output: { integrations: [{ toolkit: "twitter", displayName: "Twitter", curated: false, auth_schemes: ["OAUTH2"], requires_byo_credentials: true }, ...] }
Example — list_connected:
Input: { app_id: "app_abc123", action: "list_connected" }
Output: { connections: [{ toolkit_slug: "gmail", status: "active", connected_at: "..." }, ...] }
Example — list_tools:
Input: { app_id: "app_abc123", action: "list_tools", toolkit: "gmail" }
Output: { tools: [{ name: "GMAIL_SEND_EMAIL", description: "Send an email", parameters: {...} }, ...] }
Example — execute_action (send email):
Input: { app_id: "app_abc123", action: "execute_action", tool_name: "GMAIL_SEND_EMAIL", params: { to: "user@example.com", subject: "Hello", body: "World" } }
Output: { successful: true, data: { messageId: "..." } }
Common errors:
- INTEGRATIONS_NOT_CONFIGURED: Integration API key not set
- INTEGRATIONS_BYO_CREDENTIALS_REQUIRED: Toolkit has no Composio-managed auth; pass oauth_credentials
- INTEGRATIONS_UPSTREAM_ERROR: Composio rejected the auth config (bad slug or bad credentials)
- INTEGRATIONS_NOT_CONNECTED: User hasn't connected this integration
- INTEGRATIONS_EXECUTION_FAILED: Integration tool execution failed
- RESOURCE_NOT_FOUND: App doesn't exist
Parameters (10)
actionstringrequired
The action to perform
app_idstringrequired
The app ID (e.g. app_abc123def456)
toolkitstring
Integration toolkit slug (e.g. gmail, slack). Required for configure and disable.
scopesarray
OAuth scopes to request (configure only)
display_namestring
Custom display name (configure only)
oauth_credentialsobject
BYO OAuth credentials for non-curated toolkits (configure / rotate_credentials). Required fields vary by toolkit — pass any extras the toolkit needs (e.g. twitter requires `generic_id` as the App Bearer Token). Omit for curated toolkits.
searchstring
Search query to find integrations by name (list_available only)
tool_namestring
Integration tool slug (e.g. GMAIL_SEND_EMAIL). Required for execute_action.
paramsobject
Arguments for the tool action (execute_action only)
user_idstring
Execute on behalf of a specific user, service-level only (execute_action only)
manage_auth_config
Manage authentication configuration for an app.
Actions:
- "configure_auth_hook": Configure a post-authentication hook function
- "update_jwt": Update JWT token expiration times
- "generate_service_key": Generate a new API key (service key)
Parameters by action:
configure_auth_hook: { app_id, action: "configure_auth_hook", post_auth_function }
update_jwt: { app_id, action: "update_jwt", accessTokenTtl?, refreshTokenTtlDays? }
generate_service_key: { action: "generate_service_key", name }
---
### configure_auth_hook
Configure a post-authentication hook function for an app.
When set, the specified Butterbase function is invoked (fire-and-forget) after
every successful auth event: OAuth login, email login, and email signup.
The hook function receives a JSON POST body:
{
"event": "oauth_login" | "signup" | "login",
"user": { "id": "uuid", "email": "...", "provider": "google", "display_name": "...", "avatar_url": "..." },
"isNewUser": true | false,
"provider": "google" | "github" | "email" | ...
}
The function runs as butterbase_service (RLS bypassed, ctx.user is null).
Use the payload body to identify the user.
Set post_auth_function to null to remove the hook.
Prerequisites: The function must be deployed first (use deploy_function).
Example — set hook:
Input: { app_id: "app_abc123", action: "configure_auth_hook", post_auth_function: "on-auth" }
Output: { auth_hook_function: "on-auth", message: "Post-auth hook set to function \"on-auth\"" }
Example — remove hook:
Input: { app_id: "app_abc123", action: "configure_auth_hook", post_auth_function: null }
Output: { auth_hook_function: null, message: "Post-auth hook removed" }
Common errors:
- Function not found: Deploy the function first before configuring it as a hook.
Idempotency: Safe to call multiple times (overwrites previous setting).
---
### update_jwt
Update JWT token expiration times for access and refresh tokens.
Example:
Input: {
app_id: "app_abc123",
action: "update_jwt",
accessTokenTtl: "1h",
refreshTokenTtlDays: 30
}
Output: {
message: "JWT config updated",
app_id: "app_abc123",
jwt_config: {
accessTokenTtl: "1h",
refreshTokenTtlDays: 30
}
}
Token types:
- Access token: Short-lived token for API requests (default: 15m)
- Refresh token: Long-lived token to get new access tokens (default: 7 days)
Time formats:
- Access token: "15m", "1h", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days)
- Refresh token: Integer days (7, 30, 90)
Use this to:
- Increase security with shorter access tokens
- Improve UX with longer refresh tokens
- Balance security vs. convenience
Common errors:
- RESOURCE_NOT_FOUND: App doesn't exist
- VALIDATION_INVALID_SCHEMA: Check time format is valid
Idempotency: Safe to call multiple times (updates config).
Note: Changes apply to new tokens only. Existing tokens keep their original expiration.
---
### generate_service_key
Generate a new API key (service key) for programmatic access to the Control API.
Use this to:
- Create API keys for automation scripts
- Generate keys for CI/CD pipelines
- Provide keys to team members or services
The generated key (bb_sk_...) can be used to:
- Access all MCP tools programmatically
- Call the Control API directly
- Manage apps, schemas, functions, and data
Example:
Input: {
action: "generate_service_key",
name: "CI/CD Pipeline Key"
}
Output: {
key: "bb_sk_a1b2c3d4e5f6...",
key_id: "uuid-1234",
prefix: "bb_sk_a1b2c3",
name: "CI/CD Pipeline Key",
created_at: "2024-01-15T10:00:00Z"
}
IMPORTANT: The full key is only shown ONCE. Store it securely - it cannot be retrieved again.
Common errors:
- AUTH_INSUFFICIENT_PERMISSIONS: Only authenticated users can generate keys
Idempotency: Not idempotent - creates a new key each time.
Security notes:
- Keys have full access to all your apps and data
- Treat keys like passwords - never commit them to git
- Revoke keys immediately if compromised
- Use descript
Parameters (9)
actionstringrequired
The action to perform
app_idstring
The app ID (required for configure_auth_hook and update_jwt); also required for generate_service_key when key_scope === 'app'.
post_auth_functionstring | null
Name of deployed function to call after auth events, or null to remove (configure_auth_hook only)
accessTokenTtlstring
Access token TTL (e.g., "15m", "1h", "2h", "1d"). Supports: s (seconds), m (minutes), h (hours), d (days) (update_jwt only)
refreshTokenTtlDaysinteger
Refresh token TTL in days (e.g., 7, 30) (update_jwt only)
namestring
Descriptive name for the key (e.g., "Production Deploy Key") (generate_service_key only)
substrate_accessboolean
When true, the generated key works for BOTH app operations and substrate operations on the caller's substrate. Default false (app-only). (generate_service_key only)
key_scopestring
Whether the key is scoped to your whole account or to a single app. Use 'app' (required for calling auth:required functions via X-Butterbase-As-User impersonation). Use 'account' for platform APIs. Default 'account'. (generate_service_key only)
additional_scopesarray
Optional extra scope tokens to add. Allowed: 'ai:gateway', 'substrate'. Do NOT pass 'app:<id>' or '*' — use key_scope instead. (generate_service_key only)
manage_app
Manage app lifecycle: list, delete, pause/resume, get config, update access mode, secure, update CORS, clone, find templates, and migrate regions.
Actions:
- "list": List all backend apps with basic metadata (no app_id needed)
- "delete": Delete an app and ALL its resources permanently (IRREVERSIBLE)
- "pause": Pause or resume all data-plane traffic for an app (kill-switch)
- "get_config": Get detailed configuration for an app including CORS, storage settings, and metadata
- "set_visibility": Toggle the app's template visibility between "public" and "private"
- "update_access_mode": Toggle an app's access mode between "public" and "authenticated"
- "secure": Lock down an app: sets access_mode to "authenticated" and optionally enables RLS user isolation
- "update_cors": Update CORS allowed origins to control which frontend domains can access your API
- "preview_clone_env_vars": Preview which env vars a source app's functions need before cloning. Returns { functions: [{ fn_name, keys, conventions }] }. Call this before clone to decide what to supply via env_var_values or auto_mint_api_key.
- "clone": Create a clone of a public app. Returns { job_id, pending_env_vars }. The dest app is a fresh empty-DB app owned by the caller. Source must be public and have a repo snapshot. Supply env_var_values and/or auto_mint_api_key to pre-fill function env vars; pending_env_vars lists keys still needing values.
- "get_clone_job": Look up the status of a previously-started clone job. Returns { status, dest_app_id?, error_message? }.
- "find_templates": Search public templates by name, region, sort order, and pagination. Returns paginated list of public app templates.
- "set_clone_webhook": Set or clear a webhook that fires when someone clones this app. Pass webhook_url + webhook_secret to configure, or clear_webhook: true to remove.
- "link_substrate": Link this app to the caller's substrate. Once linked, the app's deployed functions receive ctx.substrate and its actions/entities flow into the caller's substrate ledger.
- "unlink_substrate": Unlink this app from substrate. ctx.substrate stops being injected; in-flight actions are unaffected.
- "set_substrate_autopropagate": Toggle per-event auto-mirroring of app activity into the linked owner's substrate. Currently supports 'users' (signup / email-verified / user-deleted). Requires the app to already be linked via 'link_substrate'.
- "move": Migrate an app to a different region. Returns migration_id + initial status "queued".
- "move_status": Get the current status of an in-progress migration.
- "teardown_source_replica": After a completed move, decommission the retained source-region replica.
Parameters by action:
list: { action: "list" }
delete: { action: "delete", app_id }
pause: { action: "pause", app_id, paused, reason? }
get_config: { action: "get_config", app_id }
set_visibility: { action: "set_visibility", app_id, visibility, listed? }
update_access_mode: { action: "update_access_mode", app_id, access_mode }
secure: { action: "secure", app_id, tables? }
update_cors: { action: "update_cors", app_id, allowed_origins }
preview_clone_env_vars: { action: "preview_clone_env_vars", source_app_id }
clone: { action: "clone", source_app_id, name?, region?, env_var_values?, auto_mint_api_key? }
get_clone_job: { action: "get_clone_job", job_id }
find_templates: { action: "find_templates", q?, region?, sort?, limit?, offset? }
set_clone_webhook: { action: "set_clone_webhook", app_id, webhook_url, webhook_secret } or { action: "set_clone_webhook", app_id, clear_webhook: true }
link_substrate: { action: "link_substrate", app_id }
unlink_substrate:
Parameters (25)
actionstringrequired
The action to perform
app_idstring
The app ID (e.g. app_abc123def456). Required for all actions except "list".
pausedboolean
Required for "pause". true to pause; false to resume.
reasonstring
Optional for "pause". Human-readable reason; surfaced in 503 responses while paused.
access_modestring
Required for "update_access_mode". "public" allows anonymous access, "authenticated" requires end-user JWT or API key.
visibilitystring
Required for "set_visibility". Template visibility.
listedboolean
Optional for "set_visibility". When false and visibility=public, app is clonable by direct ID but not in /v1/templates.
tablesarray
Optional for "secure". Tables to enable RLS user isolation on. Omit to only toggle access_mode.
allowed_originsarray
Required for "update_cors". Array of allowed origin URLs (e.g. ["http://localhost:3000", "https://myapp.com"])
source_app_idstring
Required for "clone" and "preview_clone_env_vars". The id of the public app to clone.
namestring
Optional for "clone". A name for the new app; defaults to `Clone of <source_app_id>`.
regionstring
Optional for "clone". The region for the new app; defaults to the source app's region.
env_var_valuesobject
Optional for "clone". Per-function env var values: { fn_name: { KEY: "value" } }. Use preview_clone_env_vars to see what keys the source needs.
auto_mint_api_keyarray
Optional for "clone". Keys to auto-mint a scoped bb_sk_* into, typically [{ fn_name: "agent-chat", key: "BUTTERBASE_API_KEY" }].
job_idstring
Required for "get_clone_job".
webhook_urlstring
Required for "set_clone_webhook" (unless clear_webhook is true). HTTPS URL to receive clone event POST requests.
webhook_secretstring
Required for "set_clone_webhook" (unless clear_webhook is true). Secret used to sign the HMAC-SHA256 webhook payload (16–256 characters).
clear_webhookboolean
Optional for "set_clone_webhook". Pass true to remove the clone webhook instead of setting one.
qstring
Optional for "find_templates". Search query to filter templates by name.
sortstring
Optional for "find_templates". Sort order: "recent" or "popular". Defaults to "recent".
limitinteger
Optional for "find_templates". Max results per page (default 20).
offsetinteger
Optional for "find_templates". Pagination offset (default 0).
usersboolean
Optional for "set_substrate_autopropagate". true to mirror user signup / email-verified / user-deleted events into the substrate; false to disable. At least one toggle key must be provided.
dest_regionstring
Required for "move". Target region slug (e.g. "us-west-2").
migration_idstring
Required for "move_status" and "teardown_source_replica". The migration ID returned by action: "move".
manage_repo
Manage an app's repo (content-addressed code snapshots).
Actions:
- "push": Push a small set of files (≤1 MB total over MCP — for larger repos shell out to `butterbase repo push`). Files are { path, content_base64 } pairs. Server computes sha and runs prepare → upload → commit.
- "pull_latest": Fetch the latest snapshot's manifest (does not write files locally). Returns { snapshot_id, files: [{ path, sha256, size, downloadUrl }] } — agents fetch each downloadUrl directly.
- "status": Returns { app_id, pinned_snapshot_id?, remote_latest_snapshot_id?, file_count }. No working-tree comparison (the server has no working tree).
- "list_snapshots": List snapshot history newest-first.
- "wipe": Delete every snapshot and blob, then null repo_latest_snapshot. Irreversible.
Parameters by action:
push: { action: "push", app_id, files: [{ path, content_base64 }], message? }
pull_latest: { action: "pull_latest", app_id }
status: { action: "status", app_id }
list_snapshots: { action: "list_snapshots", app_id }
wipe: { action: "wipe", app_id }
Auth matrix: writes (push, wipe) require app owner. Reads (pull_latest, status, list_snapshots) work for owner or anonymously on a public app; private+non-owner gets 404.
Parameters (4)
actionstringrequired
app_idstringrequired
App ID — required for every action.
filesarray
Required for "push".
messagestring
Optional snapshot message for "push".
prep_and_submit_hackathon_entry
Prep and submit your project to a Butterbase hackathon. Two-step flow.
The tool resolves which hackathon you mean from your submission_code (or, if
you've already submitted before, your existing binding). You do NOT pass a
slug — that's figured out for you. If resolution is ambiguous (no code, not
yet bound, and multiple hackathons are open), the tool returns the list of
open hackathons; ask the user which one they mean and re-run with a code.
STEP 1 — action: "prep"
Resolves the hackathon and returns its field_schema if exactly one is
identified. Pass submission_code when you have it. Otherwise the tool
will fall back to "user already bound" or "only one hackathon open".
Use the schema to:
• Show the user every field's "label" and "description" (never the internal "key").
• Propose a value for each field and wait for the user's explicit
confirmation before STEP 2.
Do NOT auto-fill values from guesses, prior context, or app metadata without
showing the user every field value first.
If resolution returns multiple open hackathons with no match, present
"open_hackathons" to the user and ask them to provide the submission_code.
STEP 2 — action: "submit"
Submits the confirmed "data" object. Pass hackathon_slug = matched.slug from
the prep response so submit always targets the same hackathon prep resolved
(matters when multiple are open). Re-running updates the existing submission
and bumps version. Closes after the hackathon's submission_deadline.
App scoring:
Always pass app_id on submit when you can. Hackathon scoring awards up to 50
points for a demo URL on butterbase.dev and up to 50 additional points for
Butterbase features measured on that specific app (database, functions,
deployed frontend, auth users, storage, OAuth, realtime, integrations, etc.).
Without app_id only the demo URL is scored, so entries without it almost
always rank lower. Including app_id also ties the submission to a real app,
which is much better for human judges.
Submission code:
On the FIRST submission you must include the submission_code provided by
the hackathon organizers. The same code is used to identify the hackathon
during prep, so pass it on prep too. After the first successful submission
the code is no longer required (the user is bound by user_id).
Recommended flow:
1. Get the submission_code from the user (skip if they've already submitted before).
2. Call with action: "prep", submission_code to resolve + retrieve the schema.
• If matched is null and open_hackathons has multiple entries, ask the user
which one and re-run with a code.
3. Show the user each field's label / description, propose values, and wait
for confirmation.
4. Call with action: "submit", hackathon_slug = matched.slug from prep,
data: {...confirmed values}, app_id, and submission_code (if provided in
step 1).
Returns:
prep → { matched: { slug, name, submission_deadline, ends_at, field_schema } | null,
match_reason, open_hackathons,
next_call?: { tool, arguments, instructions } }
When matched is non-null, next_call contains a fully-formed example
submit invocation with placeholders for each field. Use it as the
literal shape for STEP 2: replace each placeholder in arguments.data
with the user-confirmed value, then call this tool with those args.
submit → { submission: { id, hackathon_slug, version, data, app_id, ... }, participant_created }
Parameters (5)
actionstringrequired
"prep" resolves the hackathon and returns its schema. "submit" sends the confirmed data.
hackathon_slugstring
Slug of the hackathon. Optional. Pass on submit using the slug returned by prep (matched.slug) so submit targets the exact hackathon prep resolved. Ignored on prep.
submission_codestring
Per-hackathon code from the organizer. Used to identify which open hackathon the user means and to bind them on first submission. Required for prep when multiple hackathons are open and the user is not yet bound. Required on the FIRST submission. Ignored on submit after the user is already a participant.
app_idstring
Butterbase app id for the project being submitted (e.g. app_abc123). Strongly recommended on submit: scoring awards up to 50 extra points for Butterbase usage on this app. Ignored on prep.
dataany
Submission fields per the hackathon field_schema, as an object keyed by field "key". Required on submit; ignored on prep. Example: {"project_name":"My App","demo_url":"https://my-app.butterbase.app","description":"What it does"}. A JSON-encoded string is also accepted and will be parsed.
manage_durable_objects
Manage Durable Object (DO) classes for an app: register/update code, list/get/delete, view usage, and manage shared env vars.
DOs are stateful per-key actors that persist state in memory and built-in storage. Use them when
you need state for a single room/user/agent across requests (multiplayer games, chat rooms, rate
limiters, long-running agents). For stateless work, use a Function instead.
Actions:
- "deploy": Register or update a DO class (single TypeScript file, one exported class)
- "list": List all DO classes for the app
- "get": Get a single DO class — includes the source code and current status
- "delete": Delete a DO class — Cloudflare immediately deletes all instances and storage; cannot be undone
- "usage": Get current-month DO usage (do_requests, do_cpu_ms) — refreshed every 15 min, app-wide totals
- "list_env": List all env vars set on the app's DOs
- "set_env": Create or overwrite a single env var
- "delete_env": Remove a single env var
Parameters by action:
deploy: { app_id, action: "deploy", name, code, access_mode? }
list: { app_id, action: "list" }
get: { app_id, action: "get", name }
delete: { app_id, action: "delete", name }
usage: { app_id, action: "usage", name }
list_env: { app_id, action: "list_env" }
set_env: { app_id, action: "set_env", key, value }
delete_env: { app_id, action: "delete_env", key }
Deploy constraints:
- One TypeScript file, exporting exactly ONE class with fetch(req) and optional state.storage / state.acceptWebSocket
- No npm imports — only `import { ... } from 'cloudflare:workers'`
- Max 5 DO classes per app. Bundle (sum of all DO code per app) ≤ 10 MB compressed.
- Class name in code (PascalCase) is parsed automatically; URL "name" arg is kebab-case.
URL after deploy:
https://<subdomain>.butterbase.dev/_do/<name>/<instance-id> (HTTP and WebSocket)
access_mode (v1 — shape check only at the dispatcher; validate inside fetch() for strong auth):
- "public": open to anyone
- "authenticated": requires Authorization that looks like an end-user JWT (default)
- "service_key": requires Authorization starting with "Bearer bb_sk_"
Env vars are key-value pairs injected into every DO class at runtime, scoped to all DO classes within
the app. They are separate from function env vars. After set_env / delete_env, redeploy DOs for the
change to take effect.
Common errors:
- RESOURCE_NOT_FOUND: app_id or DO class doesn't exist
- AUTH_INSUFFICIENT_PERMISSIONS: must be app owner or collaborator
- VALIDATION_ERROR: env key must be alphanumeric + underscores; class code must export exactly one class
Idempotency: deploy/set_env/delete_env are safe to retry. delete is irreversible.
Parameters (7)
app_idstringrequired
The app ID (e.g. app_abc123)
actionstringrequired
The action to perform
namestring
DO class URL name (kebab-case). Required for deploy/get/delete/usage.
codestring
Required for deploy. TypeScript source — must export exactly one class.
access_modestring
Optional for deploy. Default: authenticated.
keystring
Env var name. Required for set_env and delete_env.
valuestring
Env var value. Required for set_env.
manage_ai
Use the app's AI gateway: chat, embeddings, list models, read/update config, read usage.
Actions:
- chat { app_id, messages, model?, temperature?, max_tokens? }
Synchronous (no streaming). Returns the full assistant response.
Default model is the app's configured default, or "openai/gpt-4o-mini".
- embed { app_id, input (string | string[]), model?, encoding_format? }
Returns OpenAI-shaped embedding response.
- list_models { app_id }
Returns { models: AiModel[] } — discover what the app can call.
- get_config { app_id }
Returns { defaultModel, allowedModels, maxTokensPerRequest, ... }
- update_config { app_id, config }
Set defaultModel, allowedModels, maxTokensPerRequest (1–100000), or rotate BYOK.
- get_usage { app_id, startDate?, endDate? }
Aggregate token counts and costs over a window.
- submit_video { app_id, model, prompt, duration?, resolution?, aspect_ratio?, generate_audio?, seed? }
Submits an async video-generation job. Returns { job_id, status, polling_url }.
Poll the returned URL until status is "completed".
- poll_video { app_id, job_id }
Returns current { status, model, content_urls?, error?, created_at }.
When status is "completed", content_urls contains absolute URLs (same origin
as the polling_url) that the caller can fetch() directly using the same
Authorization header. Use this to drive your own polling loop.
- start_meeting { app_id, meeting_url, transcript?, recording?, metadata?, bot_name?, automatic_leave? }
Spawn a meeting bot that joins a Zoom/Meet/Teams/Webex call.
recording: "mp4" (default), "audio_only", or false. transcript defaults to true.
bot_name (1–64 chars) sets the display name attendees see; defaults to "Butterbase Notetaker".
automatic_leave: per-bot overrides for the auto-leave timers (in seconds, positive ints, max 86400).
Any sub-field omitted inherits the provider default.
- waiting_room_timeout_sec — leave if still in the waiting room after N seconds
- no_one_joined_timeout_sec — leave if no participants joined within N seconds
- everyone_left_timeout_sec — leave N seconds after the last participant leaves
- in_call_not_recording_timeout_sec — leave if in-call but not recording for N seconds
Example: automatic_leave: { waiting_room_timeout_sec: 900 } makes the bot give up after 15 min in the waiting room.
Returns { id, status, botName, ... }. Save id to call get_meeting / stop_meeting later.
- get_meeting { app_id, meeting_id }
Current status + recordingUrl / transcriptUrl (populated when artifacts are ready).
- list_meetings { app_id, status?, limit?, cursor? }
Page through this app's bots. status filters to a lifecycle phase
(joining / waiting_room / in_call / recording / ended / done / fatal).
- stop_meeting { app_id, meeting_id }
Force the bot to leave the call. Returns 204 / no body on success.
- estimate_meeting { app_id, duration_minutes, transcript? }
Predict the USD charge for a hypothetical session at this duration.
- configure_meetings_webhook { app_id, forward_url, rotate_secret? }
Upsert the app's meetings webhook forward URL.
When rotate_secret is true (or no row exists), generates a new signing secret
(wsec_…) and returns it once — store it immediately. The secret is the
HMAC-SHA256 ke
Parameters (31)
app_idstringrequired
The app ID
actionstringrequired
The action to perform
messagesarray
Required for chat
modelstring
temperaturenumber
max_tokensinteger
inputany
Required for embed
encoding_formatstring
configobject
Required for update_config
startDatestring
endDatestring
promptstring
Required for submit_video
durationinteger
resolutionstring
aspect_ratiostring
generate_audioboolean
seedinteger
job_idstring
Required for poll_video
forward_urlstring
Required for configure_meetings_webhook
rotate_secretboolean
For configure_meetings_webhook — generate a new signing secret
meeting_urlstring
Required for start_meeting
meeting_idstring
Required for get_meeting / stop_meeting
transcriptboolean
For start_meeting / estimate_meeting (default true)
recordingstring | boolean
For start_meeting: "mp4" (default), "audio_only", or false to skip recording
metadataobject
For start_meeting — arbitrary string→string map; keys may not start with bb_
bot_namestring
For start_meeting — display name the bot uses when it joins (1–64 chars). Defaults to "Butterbase Notetaker".
automatic_leaveobject
For start_meeting — per-bot overrides for auto-leave timers (seconds). Any field omitted inherits the provider default. e.g. { waiting_room_timeout_sec: 900 } leaves if not admitted within 15 min.
statusstring
For list_meetings — filter to one lifecycle phase
limitinteger
For list_meetings (default 20)
cursorstring
For list_meetings pagination
duration_minutesinteger
Required for estimate_meeting
manage_billing
Manage billing, plans, usage, and spending caps for the Butterbase platform account.
This is a platform-scoped tool — it operates on the authenticated account, not on a specific app.
Actions:
- "status": Get current plan, usage summary, and spending cap in one call
- "portal": Generate a Stripe billing portal URL for payment method / invoice management
- "topup": Add credit to the account balance (prepaid top-up)
- "cap_get": Retrieve the current monthly spending cap
- "cap_raise": Raise the monthly spending cap by a given amount
- "plans": List all available subscription plans with pricing
- "usage": Query detailed metered usage for a date range and optional meter type
Parameters by action:
status: { action: "status" }
portal: { action: "portal" }
topup: { action: "topup", amount: <number in USD cents> }
cap_get: { action: "cap_get" }
cap_raise: { action: "cap_raise", raise_by: <number in USD cents> }
plans: { action: "plans" }
usage: { action: "usage", start_date?: "YYYY-MM-DD", end_date?: "YYYY-MM-DD", meter?: "compute" | "storage" | ... }
Examples:
Check current plan and balance:
Input: { action: "status" }
Output: { plan: "pro", balance_cents: 5000, spending_cap_cents: 20000, usage: { ... } }
Open billing portal:
Input: { action: "portal" }
Output: { url: "https://billing.stripe.com/session/..." }
Top up $25:
Input: { action: "topup", amount: 2500 }
Output: { success: true, new_balance_cents: 7500 }
Get current spending cap:
Input: { action: "cap_get" }
Output: { spending_cap_cents: 20000 }
Raise spending cap by $50:
Input: { action: "cap_raise", raise_by: 5000 }
Output: { spending_cap_cents: 25000 }
List available plans:
Input: { action: "plans" }
Output: [{ id: "free", name: "Free", ... }, { id: "pro", name: "Pro", ... }]
Query compute usage for April 2025:
Input: { action: "usage", start_date: "2025-04-01", end_date: "2025-04-30", meter: "compute" }
Output: { usage: [{ date: "2025-04-01", value: 1234 }, ...] }
Common errors:
- AUTH_INSUFFICIENT_PERMISSIONS: Must be authenticated as the account owner
- INSUFFICIENT_BALANCE: Account balance too low for top-up operation
- INVALID_AMOUNT: amount / raise_by must be a positive integer (cents)
Parameters (6)
actionstringrequired
The billing action to perform
amountinteger
Amount in USD cents (required for "topup")
raise_byinteger
Amount in USD cents to raise the spending cap by (required for "cap_raise")
start_datestring
Start date for usage query in YYYY-MM-DD format (used with "usage")
end_datestring
End date for usage query in YYYY-MM-DD format (used with "usage")
meterstring
Meter type to filter usage by, e.g. "compute", "storage" (used with "usage")
manage_api_keys
List and revoke API keys (service keys) for the Butterbase platform account.
This is a platform-scoped tool — it operates on the authenticated account, not on a specific app.
To generate a new API key, use manage_auth_config (action: "generate_service_key").
Actions:
- "list": List all active API keys on the account (key secrets are NOT returned — only metadata)
- "revoke": Permanently revoke a specific key by its ID
Parameters by action:
list: { action: "list" }
revoke: { action: "revoke", key_id: "<uuid>" }
Examples:
List all keys:
Input: { action: "list" }
Output: [
{
id: "uuid-1234",
prefix: "bb_sk_a1b2c3",
name: "CI/CD Pipeline Key",
created_at: "2025-01-15T10:00:00Z",
last_used_at: "2025-04-01T08:30:00Z"
},
...
]
Revoke a key:
Input: { action: "revoke", key_id: "uuid-1234" }
Output: { message: "API key revoked", key_id: "uuid-1234" }
Workflow — rotate a key:
1. Call "list" to identify the key by name or prefix
2. Call manage_auth_config (action: "generate_service_key") to create the replacement key (store the new secret immediately)
3. Update all consumers (CI, scripts, MCP config) with the new key
4. Call "revoke" with the old key_id to invalidate it
Common errors:
- AUTH_INSUFFICIENT_PERMISSIONS: Must be authenticated as the account owner
- RESOURCE_NOT_FOUND: key_id does not exist or belongs to a different account
Security notes:
- Revocation is immediate and irreversible
- If a key is compromised, revoke it before generating a replacement to minimise exposure window
Parameters (2)
actionstringrequired
The action to perform on API keys
key_idstring
The UUID of the API key to revoke (required for "revoke")
list_regions
List the regions an app can be created or moved to.
Returns the live set of supported regions. Use this before calling init_app or
manage_app (action: "move") to validate a region slug, or to present region choices to a user.
Returns: { regions: string[] } e.g. { regions: ["us-east-1", "us-west-2"] }
Idempotency: Safe to call anytime (read-only operation).
No parameters.
manage_migrations
Read and control in-flight app migrations.
This complements manage_app (actions: move / move_status / teardown_source_replica) with the four
operational routes those actions don't cover.
Actions:
- get_active : { app_id, action: "get_active" }
Returns the running migration for this app, or { migration: null }.
- abort : { app_id, migration_id, action: "abort" }
Cancel a migration that has NOT yet reached "flipping_routing".
Returns 409 if already past cutover; use "reverse" instead.
- reverse : { app_id, migration_id, action: "reverse" }
Roll a COMPLETED migration back to source. Only works while the
source replica is still retained (see list_source_replicas).
- list_source_replicas : { action: "list_source_replicas" }
Lists active retained source replicas for the caller's apps.
Use this before tearing down to discover what's still around.
Use list_regions + manage_app (action: "move") to start a move; manage_app (action: "move_status") to watch progress;
manage_app (action: "teardown_source_replica") when you're confident the move is stable.
Parameters (3)
app_idstring
Required for get_active / abort / reverse.
migration_idstring
Required for abort / reverse.
actionstringrequired
The action to perform
manage_agents
Manage agents for a Butterbase app: list, get, create, update, delete, and validate specs.
Actions:
- "list": List all agents defined for an app.
- "get": Get a single agent by name (full record incl. graph_spec).
- "create": Create a new agent. Requires name + graph_spec.
- "update": Patch an existing agent (partial update). Requires name.
- "delete": Permanently delete an agent and all its runs. Requires name.
- "validate": Validate a graph_spec without creating an agent. Requires graph_spec.
Parameters by action:
list: { action: "list", app_id }
get: { action: "get", app_id, name }
create: { action: "create", app_id, name, graph_spec, display_name?, description?, default_model?, ...access_fields? }
update: { action: "update", app_id, name, display_name?, description?, graph_spec?, default_model?, status?, ...access_fields? }
delete: { action: "delete", app_id, name }
validate: { action: "validate", app_id, graph_spec }
Common errors:
- 404: Agent or app not found
- 403: Not authorized
- 409: Agent name already exists (on create)
- 400: Invalid body or unsafe public agent (pass safety_acknowledged=true to override)
Parameters (15)
actionstringrequired
The action to perform
app_idstringrequired
The app ID (e.g. app_abc123)
namestring
Agent slug. Required for get/update/delete; required for create (must match /^[a-z0-9][a-z0-9_-]{0,63}$/)
graph_specany
Graph spec object. Required for create and validate; optional for update. Validated by graphSpecSchema except on validate (which returns structured issues instead of rejecting).
display_namestring
Human-readable name. Optional for create/update.
descriptionstring
What this agent does. Optional for create/update.
default_modelstring
Default LLM model identifier. Optional for create/update.
statusstring
Optional for update. Set to "disabled" to stop the agent accepting new runs.
visibilitystring
Who can invoke the agent
max_runs_per_user_per_hourinteger
Rate limit per authenticated user per hour (null = unlimited)
max_runs_per_ip_per_hourinteger
Rate limit per IP address per hour (null = unlimited)
max_runs_per_app_per_hourinteger
Rate limit across the whole app per hour (null = unlimited)
daily_budget_usdnumber
Daily spend cap in USD (null = unlimited)
max_concurrent_runsinteger
Maximum simultaneous active runs (null = unlimited)
safety_acknowledgedboolean
Set true to confirm you accept the risks of public write-capable agents
manage_substrate
Manage the entire Butterbase substrate: the action ledger, the entity graph,
source artifacts, institutional memory (decisions/commitments/learnings), outbox,
attention rules, snapshots, and settings.
Every substrate write goes through the action ledger via "propose". The ledger
captures the proposer, the policy verdict, and the result; nothing is written
to the substrate without an action_id you can trace.
Actions — Writes (action ledger):
- "propose": Propose a write. capability ∈ {upsert_entity, update_entity, patch_entity, record_decision, record_commitment, record_learning, upsert_source_artifact, merge_entities, delete_entity, bulk_revert_actions, record_principle, retire_principle, supersede_decision, send_email_draft, revert_action}. Body: { capability, payload, dangerously_skip_approval?, idempotency_key? }. When idempotency_key matches a prior action (per substrate user, no TTL), the response returns that prior action's verdict + result and includes replay: true.
- "approve": Approve a previously-proposed action that is awaiting review. { action_id }.
- "reject": Reject a previously-proposed action. { action_id, reason }.
Actions — Ledger reads:
- "list_actions": List action ledger rows. Optional: { status?, capability?, source_app_id?, source_rule_id?, limit?, before? }.
- "get_action": Get one ledger row by id. { action_id }.
Actions — Entity graph:
- "find_entities": List/fuzzy-search entities. Optional: { type?, q?, primary_email?, limit? (max 1000), cursor? }. type ∈ person|company|fund|workspace|team|project|event|agent|self. Returns { entities, next_cursor? }.
- "get_entity": Get one entity by id. { entity_id }.
- (writes use "propose" with capability=upsert_entity / update_entity / patch_entity / merge_entities / delete_entity)
Actions — Source artifacts (meeting transcripts, email threads, call recordings, etc.):
- "list_source_artifacts": List/search. Optional: { kind?, q?, limit?, count? }. q runs FTS over title+summary+content.
- "get_source_artifact": Get one artifact by id (incl. full content). { artifact_id }.
- (writes use "propose" with capability=upsert_source_artifact)
Actions — Institutional memory:
- "search_memory": FTS across decisions, commitments, learnings, and source_artifacts. { q?, kinds?, limit? }. q is optional — omit to list recent items. kinds = comma-separated subset of {decisions,commitments,learnings,source_artifacts}.
- "list_memory": Browse memory chronologically (no FTS). Filter by source_artifact_id, kinds, superseded. { kinds?, source_artifact_id?, superseded?, before?, limit? }.
- (writes use "propose" with capability=record_decision / record_commitment / record_learning / record_principle / supersede_decision / retire_principle)
Actions — Outbox (side-effect dispatch queue):
- "list_outbox": List rows. Optional: { status?, limit? }. status ∈ pending|in_flight|succeeded|failed|dead_letter|cancelled.
- "retry_outbox": Manually re-queue a failed/dead-letter row. { outbox_id }.
- "cancel_outbox": Mark a not-yet-completed row as cancelled. { outbox_id }.
Actions — Attention rules (cron-triggered automation):
- "list_rules": List all attention rules. Optional: { enabled? }.
- "get_rule": Get one rule by id. { rule_id }.
- "create_rule": Create a new rule. { rule: {name, description?, trigger_cron, condition_mode, condition, action_capability, action_payload_template, enabled?, max_fires_per_day?} }.
- "update_rule": Full replace by id. { rule_id, rule }.
- "delete_rule": Delete by id. { rule_id }.
- "enable_rule": Set enabled=true. { rule_id }.
- "disable_rule": Set enabled=false. { rule_id }.
- "list_rule_firings": History for one rule. { rule_id, status?, limit?, before? }.
Actions — Snapshots & settings:
- "snapshots": Daily aggregate snapshots. Optional: { days? }.
- "get_settings": Show substrate settings (e.g. yolo_mode).
- "set_yolo": Toggle YOLO mode. { yolo_mode: boolean }.
Cap
Parameters (30)
actionstringrequired
The substrate operation to perform.
action_idstring
Ledger action id (act_…) — for approve/reject/get_action.
entity_idstring
Entity id (ent_…) — for get_entity.
artifact_idstring
Source artifact id (art_…) — for get_source_artifact.
outbox_idstring
Outbox row id (obx_…) — for retry_outbox/cancel_outbox.
rule_idstring
Attention rule id (rule_…) — for *_rule actions.
capabilitystring
Capability name — required for "propose".
payloadobject
Capability payload — required for "propose".
dangerously_skip_approvalboolean
Owner-only YOLO override for propose. Principle conflicts still block.
idempotency_keystring
Optional client key to dedupe retries on propose.
reasonstring
Required reason for "reject". Appended to the audit trail.
statusstring
Status filter — list_actions/list_outbox/list_rule_firings.
Butterbase gives you the building blocks for AI-driven applications without lock-in: a Postgres-backed backend with row-level security, serverless functions, an LLM gateway, realtime subscriptions, key-value store, file storage, RAG, durable per-key actors, and a built-in Model Context Protocol (MCP) server so agents can operate your backend with tools instead of glue code.
Features
Data
Postgres data plane — per-app databases with declarative schema (/schema), automatic REST endpoints (/auto-api), and migrations.
Audit logs — structured request audit trail across KV and other surfaces (/audit-logs).
Webhooks — outbound webhooks for app events (/webhooks).
Multi-region app moves — relocate an app across regions with retained source replicas (scripts/move-app/).
Agent surface
MCP server — every capability above is exposed as MCP tools at /mcp (HTTP) or via stdio (@butterbase/mcp — npx @butterbase/mcp).
Claude Code plugin — packages/plugin (submodule of butterbase-skills) ships 30+ guided skills (idea → plan → schema → auth → functions → deploy → submit) for agentic app building.
Open-source vs. managed
This repo ships the runtime data plane — everything required to self-host a fully featured Butterbase instance. The managed offering at butterbase.ai adds multi-region orchestration, billing, upstream AI router adapters, lease-based quota enforcement, and ops dashboards (those live in a private repo that consumes this one as a submodule).
When you self-host, the AI gateway runs without upstream router adapters, billing uses a no-op provider, and quotas are unlimited. Wire your own implementations via the BillingProvider, QuotaEnforcer, and RouterAdapter interfaces in packages/shared.
Quickstart (self-host)
Requirements: Docker, Node 22+, npm.
1. Clone (with submodules)
The Claude Code plugin containing skills (packages/plugin) is a git submodule (butterbase-skills). A plain clone leaves packages/plugin/ empty and npm install silently skips that workspace.
bash
git clone --recurse-submodules https://github.com/butterbase-ai/butterbase.git
cd butterbase
If you already cloned without submodules:
bash
git submodule update --init --recursive
Optional — keep submodules updated on every pull:
bash
git config --global submodule.recurse true
2. Install dependencies and configure env
bash
npm ci
cp .env.example .env
docker-compose.local.yml sets KV_REDIS_URL_US_EAST_1 for you. Edit .env only if you override defaults (e.g. run control-api on the host — use redis://localhost:6379).
3. Start the stack
First run builds images and can take several minutes.
bash
docker compose -f docker-compose.local.yml up -d
Wait until control-api is healthy:
bash
curl -sf http://localhost:4000/health/ready
4. Run database migrations
Schema is not applied automatically on container start. From the repo root (with the stack running):
bash
export NEON_PLATFORM_PRIMARY_URL=postgresql://butterbase:butterbase_dev@localhost:5433/butterbase_control
export NEON_RUNTIME_PROJECT_ID_US_EAST_1=postgresql://butterbase:butterbase_dev@localhost:5437/butterbase_runtime_us
export BUTTERBASE_REGIONS=us-east-1
npm run migrate:all
5. Seed the local dev user
With AUTH_ENABLED=false, the API uses DEV_OWNER_ID from compose. That user must exist in platform_users (fresh volumes start empty):
bash
export NEON_PLATFORM_PRIMARY_URL=postgresql://butterbase:butterbase_dev@localhost:5433/butterbase_control
npm run seed:dev
6. Smoke test
Auth is disabled in the local compose profile (AUTH_ENABLED=false):
Docs site (local): http://localhost:4321 after docker compose up
Project status
Latest release: v0.2.0 (2026-05-25) — adds the KV store across SDK / REST / CLI / MCP. The data plane is production-tested by the managed offering; the OSS distribution is young — please file self-host issues and we'll tighten docs and defaults from feedback. See CHANGELOG.md for the full history.
See CONTRIBUTING.md. The boundary between OSS and the managed offering is intentional — please read the scope section before opening a PR that touches billing, quota math, or upstream router adapters.
Security
See SECURITY.md. Report vulnerabilities to security@butterbase.ai.