Platform API
MCP over HTTP
Section titled “MCP over HTTP”The same MCP tool surface is available over HTTP:
| Method | Path | Purpose |
|---|---|---|
| GET, POST, DELETE | /mcp | Streamable HTTP MCP session |
Send Authorization: Bearer {platform_api_key} so requests run as your account. Use this when your assistant or automation can’t use stdio MCP but can call HTTPS.
Apps & regions
Section titled “Apps & regions”See the Regions concept guide for an overview.
List supported regions
Section titled “List supported regions”GET /v1/regionsPublic — no API key required.
{ "regions": ["us-east-1", "us-west-2"] }Create an app
Section titled “Create an app”POST /initAuthorization: Bearer {api_key}
{ "name": "my-app", "region": "us-west-2"}| Field | Required | Notes |
|---|---|---|
name | Yes | App display name; also used to derive the subdomain. |
region | No | One of the regions returned by GET /v1/regions. Defaults to the platform default if omitted. |
Returns { app_id, api_base_url, region, ... }.
Move an app to another region
Section titled “Move an app to another region”POST /v1/apps/{app_id}/moveAuthorization: Bearer {api_key}
{ "dest_region": "us-east-1" }Returns { migration_id, status: "queued" }. The app stays available for reads during the move; writes pause briefly during the cutover and resume automatically when the move completes.
Check move status
Section titled “Check move status”GET /v1/apps/{app_id}/migrations/{migration_id}Authorization: Bearer {api_key}Returns the current step, source and destination regions, and timing.
Agent guidance
Section titled “Agent guidance”| Method | Path | Purpose |
|---|---|---|
| GET | /llms.txt | Plain-text guidance for LLM agents |
Provides quick start patterns, common patterns, error shape, and response metadata.
App access mode
Section titled “App access mode”Control whether anonymous (unauthenticated) requests can reach the data API and realtime WebSocket. See Access modes for the conceptual overview.
| Method | Path | Purpose |
|---|---|---|
| PATCH | /v1/{app_id}/config/access-mode | Toggle between public and authenticated |
| POST | /v1/{app_id}/secure | Set access_mode = "authenticated" and create user-isolation RLS policies in one call |
Toggle access mode
Section titled “Toggle access mode”PATCH /v1/{app_id}/config/access-modeAuthorization: Bearer {token}
{ "access_mode": "authenticated" }Secure (composite)
Section titled “Secure (composite)”POST /v1/{app_id}/secureAuthorization: Bearer {token}
{ "tables": [ { "table_name": "posts", "user_column": "author_id" }, { "table_name": "comments", "user_column": "user_id", "public_read_column": "is_published" } ]}Pass an empty body or omit tables to flip access mode only. Response includes tables_secured and a table_errors array — failures on individual tables don’t roll back the whole call.
App visibility
Section titled “App visibility”Control whether other Butterbase users can discover and clone your app as a template. This is separate from access mode, which controls whether anonymous requests reach the data API. An app can be visibility="public" (template-shareable) and access_mode="authenticated" (no anonymous data reads) at the same time.
visibility defaults to "private". Set it to "public" to make the app appear in the dashboard Templates browser and GET /v1/templates. Combine with listed: false to keep the app clonable by direct id but hidden from browse listings.
| Method | Path | Purpose |
|---|---|---|
| PATCH | /v1/{app_id}/config/visibility | Set visibility and optionally listed |
Toggle visibility
Section titled “Toggle visibility”PATCH /v1/{app_id}/config/visibilityAuthorization: Bearer {token}
{ "visibility": "public" }Response:
{ "message": "Visibility updated to \"public\"", "app_id": "{app_id}", "visibility": "public", "listed": true }listed is optional. When true (the default for public apps), the app will appear in the upcoming public templates browser. Pass "listed": false to keep the app accessible by direct link while hiding it from the browse list.
visibility accepts "public" or "private". Defaults to "private".
Read current visibility
Section titled “Read current visibility”GET /v1/{app_id}/config returns visibility and listed alongside the other app settings.
Templates
Section titled “Templates”Clone a public app to get a copy of its repo snapshot as a starting point for your own project.
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/templates | Browse public app templates (anonymous) |
| GET | /v1/templates/{app_id} | Detail for a single public app template |
| POST | /v1/templates/{source_app_id}/clone | Start a clone of a public app. Returns { job_id } |
| GET | /v1/clone-jobs/{job_id} | Status of a clone job |
| POST | /v1/clone-jobs/{job_id}/retry | Retry a failed clone job |
Discover public templates
Section titled “Discover public templates”GET /v1/templates returns the catalog of public, listed apps across all regions. No authentication required. Supports q (name prefix), region, sort=recent|popular, limit (max 50), offset. Response: { items: [{ app_id, name, owner_display_name, region, created_at, fork_count, has_repo, schema_summary: { table_count, function_count } }], total, limit, offset }.
GET /v1/templates/{app_id} returns full detail for a single template, including tables, functions, and forks_sample (5 most recent clones). Private or unlisted apps return 404 (no existence leak).
Clone a public app
Section titled “Clone a public app”POST /v1/templates/{source_app_id}/clone creates a new app owned by the caller and copies the source into it. Body: { name?: string, region?: string } — region defaults to the source’s region. Source must be public; private apps return 404 (no existence leak). Returns { job_id, status: "pending" }.
Poll GET /v1/clone-jobs/{job_id} until status is "completed" (response includes the new dest_app_id) or "failed" (response includes error_message). Failed jobs can be retried via POST /v1/clone-jobs/{job_id}/retry.
What the clone copies: the source app’s database schema (tables, columns, indexes), row-level security policies, function code, repo files (latest snapshot at clone time), non-secret configuration (storage settings, allowed origins, OAuth provider and URLs, AI model defaults), and any rows in tables the template author marked as seed data.
What it does not copy: end-user accounts and sessions, OAuth client credentials, function environment variables, bring-your-own-key (BYOK) AI provider keys, custom domains, billing, function invocation history, and audit logs. The clone owner must set these up themselves. See What a clone copies for the full breakdown.
App repo
Section titled “App repo”Push a codebase to your app as a content-addressed snapshot. Useful as a cross-device backup / source of truth for your app’s files. Snapshots are content-addressed: re-pushing an unchanged file does not re-upload it. The last 5 snapshots are retained.
Repo content is stored in your app’s object storage under a reserved prefix and is not counted as a normal storage object — uploads go through a separate two-phase flow.
Push has two phases:
prepare— send the manifest (file paths + their sha256 + sizes). Server validates and returns presigned PUT URLs for any blobs it doesn’t already have.commit— after uploading the listed blobs to S3 with the returned URLs, send the manifest again. Server verifies every blob landed at the declared size, writes the manifest, and pointslatestat the new snapshot.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/{app_id}/repo/snapshots/prepare | Validate a manifest; receive presigned PUTs for missing blobs |
| POST | /v1/{app_id}/repo/snapshots/commit | Finalize the snapshot after all blobs are uploaded |
| GET | /v1/{app_id}/repo/snapshots | List snapshot history (newest first) |
| GET | /v1/{app_id}/repo/snapshots/latest | Fetch the current snapshot manifest |
| GET | /v1/{app_id}/repo/snapshots/{snapshot_id} | Fetch a specific snapshot’s manifest |
| GET | /v1/{app_id}/repo/blobs/{sha256} | Receive a presigned GET URL for a single blob |
| POST | /v1/{app_id}/repo/blobs/batch | Presign multiple blob download URLs in a single call |
| DELETE | /v1/{app_id}/repo | Wipe the entire repo |
Reads (GET) on a public app are anonymous. On a private app, only the owner can read or write.
Manifest shape
Section titled “Manifest shape”{ "files": [ { "path": "src/index.ts", "sha256": "<64 hex>", "size": 1234 } ], "message": "optional push message"}Paths must be relative, ASCII-safe, contain no .. segments, no leading /, no backslashes, no null bytes, and be at most 4 KB. Hard caps: 10 MB per file, 100 MB per snapshot.
Prepare
Section titled “Prepare”POST /v1/{app_id}/repo/snapshots/prepareAuthorization: Bearer {token}
{ "files": [ { "path": "a.txt", "sha256": "...", "size": 5 } ] }Response:
{ "snapshot_id": "<64 hex>", "total_bytes": 5, "file_count": 1, "missing_blobs": [ { "sha256": "...", "uploadUrl": "https://..." } ]}Upload each missing_blob.uploadUrl with PUT. The presigned URL expires after 10 minutes.
Commit
Section titled “Commit”POST /v1/{app_id}/repo/snapshots/commitAuthorization: Bearer {token}
{ "manifest": { "files": [ ... ], "message": "..." } }If any blob is still missing or its uploaded size doesn’t match the manifest, the response is 409 with details.missing_shas and details.size_mismatches. Re-upload the listed blobs and re-call commit.
GET /v1/{app_id}/repo/snapshots/latestReturns { snapshot_id, manifest }. For each file in the manifest, request GET /v1/{app_id}/repo/blobs/{sha256} to receive a presigned GET URL (1 hour expiry), then fetch.
DELETE /v1/{app_id}/repo removes every snapshot, blob, and the latest pointer. Cannot be undone.
Snapshot history
Section titled “Snapshot history”GET /v1/{app_id}/repo/snapshots returns { snapshots: [{ snapshot_id, created_at }] } sorted newest-first. Visibility rules are the same as latest: anonymous on a public app, owner-only on a private one (returns 404 to non-owners — no existence leak).
Batch blob presign
Section titled “Batch blob presign”POST /v1/{app_id}/repo/blobs/batch accepts { shas: string[] } (max 1000 entries) and returns { blobs: [{ sha256, size, downloadUrl, expiresIn }] }. Missing or pruned shas are omitted from the response, so the response length can be less than the request length. Visibility rules match the single-blob endpoint: anonymous on a public app, owner-only on a private one.
This is useful when you have a manifest with many files and want to fetch all blobs without N round trips. The MCP manage_repo pull_latest action uses this internally.
Per-app subdomains
Section titled “Per-app subdomains”When subdomain routing is enabled, each app has a subdomain derived from its name. Traffic to https://{subdomain}.{base_domain} resolves the app from the Host header, so you omit {app_id} from paths.
| Subdomain path | Equivalent purpose |
|---|---|
| /data/{table} | Data API CRUD |
| /fn/{function_name} | Invoke function |
| /auth/signup, /auth/login, … | End-user auth |
| /storage/upload, /storage/objects, … | File storage |
| /schema, /schema/apply, /migrations | Schema management |
Product suggestions
Section titled “Product suggestions”MCP tool: submit_suggestion
HTTP:
POST /suggestionsAuthorization: Bearer {api_key}
{ "category": "feature_request", "description": "Add support for GraphQL subscriptions", "severity": "medium", "source": "human_prompted"}| Field | Required | Values |
|---|---|---|
category | Yes | bug_report, feature_request, improvement, documentation |
description | Yes | Description text |
severity | No | low, medium, high, critical |
affected_tool | No | Tool name if applicable |
proposed_solution | No | Suggested fix |
source | No | agent or human_prompted |
Health checks
Section titled “Health checks”| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness check |
| GET | /health/ready | Readiness check (database connectivity) |
Error format
Section titled “Error format”All errors include structured objects:
{ "error": { "code": "RESOURCE_NOT_FOUND", "message": "Table 'nonexistent' does not exist", "remediation": "Check the table name and ensure it exists in your schema", "documentation_url": "https://docs.butterbase.ai/api-reference/data-api" }}Follow the remediation field before retrying.
Rate limiting
Section titled “Rate limiting”Sensitive routes (especially auth) have strict per-route rate limits. Other routes may have additional limits depending on deployment configuration.