Butterfly Security REST API
Every public endpoint lives under butterflysecurity.org/api/*. Authenticate with an OAuth 2.0 bearer token issued by the authorization server (see the MCP docs) or a per-user Supabase session for in-dashboard calls.
Authentication
All /api/* requests authenticate via an OAuth 2.0 bearer token issued by the Butterfly authorization server, or via a per-user session cookie when the request originates from butterflysecurity.org itself.
Discovery
OAuth metadata is published at /.well-known/oauth-authorization-server. Both Dynamic Client Registration and Client ID Metadata Document flows are supported.
Audience binding
Per RFC 8707 Resource Indicators, tokens must carry the audience https://butterflysecurity.org/api/. Tokens minted for any other resource will be rejected.
Rate limits
120 req/minon all/api/*endpoints by default10 req/minon/api/backup/runand/api/restore/*mutations20 req/minon/api/auth/send-codeand/api/auth/verify-code
Limits are per-IP and reset every 60 seconds. Exceeding a limit returns 429 Too Many Requests.
Authorization header
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...Endpoint reference
27endpoints grouped by resource. Permissions are enforced server-side against the calling user's team role.
Backups
List, inspect, start, and poll backup runs. A backup captures the full configuration of one connected identity provider into encrypted R2 storage.
/api/backup/listview backupsPaginated, filterable list of backups across every connection the calling team can see.
Query parameters
connectionIdstring (uuid)— Filter to one connection.connectionIdsstring (csv of uuid)— Filter to several connections.statusrunning | completed | failedsearchstring— Free-text over connection name, error, resource types.fromISO 8601 timestamptoISO 8601 timestampresourceTypesstring (csv)minSizeinteger (bytes)maxSizeinteger (bytes)limitinteger— Default 100.offsetinteger— Default 0.includeStatsboolean— Returns aggregate counts when true.Response shape
{ backups: Backup[], total: number, stats: { totalBackups, completedBackups, failedBackups, successRate, totalSize, resourceTypes[] } | null }/api/backup/[id]view backupsFetch one backup by UUID, including resource counts, storage key, and owning connection.
Response shape
{ backup: Backup }/api/backup/[id]run backupsDelete a backup record and its R2 snapshot. Irreversible.
Response shape
{ ok: true }/api/backup/statusview backupsPoll the lifecycle of an in-flight backup. Use after POST /backup/run.
Query parameters
backupIdstring (uuid)requiredResponse shape
{ status, progress_pct, progress_message, error_message?, completed_at? }/api/backup/activeview backupsList backups currently running for the calling team.
Response shape
{ active: Backup[] }/api/backup/runrun backupsKick off a backup for one connection. Returns immediately with a backupId; poll /api/backup/status for progress.
Request body (JSON)
connectionIdstring (uuid)requiredincludestring[]— Resource categories. Omit for a full snapshot.includeWorkflowsboolean— Okta only.forceFullBackupboolean— Skip incremental logic.Response shape
{ backupId, instanceId, status: 'started' }Connections
Identity-provider connections (Okta, Okta Workflows, Auth0). Credentials are encrypted at rest and never returned by the API.
/api/connectionsmanage connectionsList every connection on the calling team across all supported providers.
Response shape
{ connections: Connection[] }/api/connections/createmanage connectionsCreate a new Okta connection from an OIN-installed API Service Integration. See /docs for other providers.
Request body (JSON)
org_urlstring (https URL)requiredclient_idstringrequiredclient_secretstringrequiredorg_namestringResponse shape
{ connection: Connection }/api/connections/[id]manage connectionsFetch one connection (credentials redacted).
Response shape
{ connection: Connection }/api/connections/[id]manage connectionsUpdate a connection's metadata or credentials.
Request body (JSON)
org_namestringclient_idstringclient_secretstringResponse shape
{ connection: Connection }/api/connections/[id]manage connectionsRemove a connection. Existing backups remain available until retention expires.
Response shape
{ ok: true }/api/connections/[id]/schedulemanage connectionsFetch the current scheduled-backup configuration for a connection.
Response shape
{ enabled, frequency, hour, day_of_week?, day_of_month?, timezone, incremental_enabled, full_backup_day }/api/connections/[id]/schedulemanage connectionsUpdate the scheduled-backup configuration.
Request body (JSON)
enabledbooleanfrequencyhourly | daily | weekly | monthlyhourinteger 0-23day_of_weekinteger 0-6— weekly onlyday_of_monthinteger 1-28— monthly onlytimezonestring (IANA)incremental_enabledbooleanfull_backup_dayinteger 0-6Response shape
{ schedule: Schedule }Diff
Structured comparison between two backups, or between a backup and the live identity provider.
/api/diffview backupsCompare two backups by ID. Returns categorized changes by resource type.
Request body (JSON)
fromBackupIdstring (uuid)requiredtoBackupIdstring (uuid)requiredresourceTypesstring[]— Optional filter.Response shape
{ changes: { resourceType, action: 'added' | 'modified' | 'removed', resource, severity }[], summary }/api/diffview backupsSame as POST /api/diff, with parameters supplied as query string.
Query parameters
fromBackupIdstring (uuid)requiredtoBackupIdstring (uuid)requiredResponse shape
Same as POST.Restore
Two-phase restore: dry-run preview, then execute. Restore writes to the live identity provider, so execute requires explicit confirmation in code.
/api/restorerestoreDry-run preview of a restore. Returns per-resource-type counts of what would be created, updated, or skipped. Never writes.
Query parameters
backupIdstring (uuid)requiredresourceTypesstring (csv)Response shape
{ preview: { resourceType, create, update, skip }[], sourceOrgUrl, collectedAt }/api/restorerestoreExecute a restore against the live identity provider. Supports selective restore via resourceTypes and resourceIds.
Request body (JSON)
backupIdstring (uuid)requiredmodefull | selectiverequiredresourceTypesstring[]— Required when mode is selective.resourceIdsstring[]— Optional, narrows selective restore.confirmtruerequired— Must be the literal boolean true.Response shape
{ restoreId, status: 'started' }Compliance
Evaluate a connection against one of six compliance frameworks.
/api/compliance/checkcomplianceRun a compliance check for a connection against SOC 2, HIPAA, PCI DSS, NIST, ISO 27001, or CIS Controls.
Request body (JSON)
connectionIdstring (uuid)requiredframeworksoc2 | hipaa | pci_dss | nist | iso27001 | cis_controlsrequiredResponse shape
{ score, findings: { control, status, severity, evidence }[], generatedAt }Scores
Numeric resilience and restore-readiness scores out of 100, with the contributing factors.
/api/scores/resilienceview backupsResilience score for one connection (0-100) with five weighted factors and actionable recommendations.
Query parameters
connectionIdstring (uuid)requiredResponse shape
{ score, factors: { name, weight, value, notes }[], recommendations: string[] }/api/scores/readinessview backupsRestore-readiness score (0-100) across every connection on the team.
Response shape
{ score, perConnection: { connectionId, score, lastBackupAt, drift }[] }Export
Generate downloadable reports and auditor-ready evidence bundles.
/api/export/reportexportGenerate a PDF report for a backup, a compliance check, or a date range. Returns a signed download URL.
Request body (JSON)
typebackup | compliance | activityrequiredbackupIdstring (uuid)— Required when type is backup.frameworkstring— Required when type is compliance.fromISO 8601 timestamp— Required when type is activity.toISO 8601 timestamp— Required when type is activity.Response shape
{ url, expiresAt }/api/export/audit-packexportBundle backup history, restore drills, and compliance evidence into a single ZIP for auditors.
Request body (JSON)
fromISO 8601 timestamprequiredtoISO 8601 timestamprequiredconnectionIdsstring[]Response shape
ZIP file (application/zip).Notifications
Read in-app notifications for the calling user.
/api/notificationsview notificationsList notifications for the calling user, newest first.
Query parameters
limitinteger— Default 50.unreadOnlybooleanResponse shape
{ notifications: Notification[] }Resource timeline
Per-resource change history derived from sequential backups.
/api/resource-timelineview backupsReconstruct the change history for one resource (user, group, app, policy) across the last N backups.
Query parameters
connectionIdstring (uuid)requiredresourceTypestringrequiredresourceIdstringrequiredlimitinteger— Default 20 backups.Response shape
{ events: { backupId, timestamp, change: 'created' | 'modified' | 'deleted', diff }[] }Health
Liveness probe for monitoring.
/api/healthpublicReturns 200 when the worker is reachable.
Response shape
{ status: 'ok', uptime, version }Errors
Errors return a JSON envelope with a human-readable error string and, where applicable, a machinecode:
{
"error": "Plan limit reached: upgrade to run additional backups",
"code": "plan_limit_exceeded"
}Standard codes
| Status | Meaning | When |
|---|---|---|
| 401 | Unauthorized | Missing, expired, or invalid bearer token. |
| 402 | Plan limit reached | Free or trial limits exhausted; upgrade required. |
| 403 | Forbidden | Role lacks the required permission. |
| 404 | Not found | Resource does not exist or is not visible to the team. |
| 429 | Rate limited | Per-IP rate-limit window exceeded. |
| 500 | Internal error | Unhandled server-side failure; safe to retry idempotent GETs. |
MCP client integration
Want to call these endpoints from a chat host instead of curl? The mcp-butterfly server wraps the most common API calls as Model Context Protocol tools with audience-bound JWT auth. See the MCP server docs for the full tool catalog and OAuth setup.
Add as a Claude custom connector in four steps:
1. Claude → Settings → Connectors → Add custom connector
2. URL: https://mcp-butterfly.<account>.workers.dev/mcp
3. Complete the OAuth redirect to Butterfly
4. Start a chat — list_backups, diff_backups, run_backup, etc.Code samples
Three ways to call the most-used endpoint, GET /api/backup/list.
curl
curl -sS https://butterflysecurity.org/api/backup/list?limit=10 \
-H "Authorization: Bearer $BUTTERFLY_TOKEN" \
-H "Accept: application/json"Node (fetch)
const res = await fetch(
"https://butterflysecurity.org/api/backup/list?limit=10",
{
headers: {
Authorization: `Bearer ${process.env.BUTTERFLY_TOKEN}`,
Accept: "application/json",
},
},
);
const { backups, total } = await res.json();Python (requests)
import os, requests
r = requests.get(
"https://butterflysecurity.org/api/backup/list",
params={"limit": 10},
headers={
"Authorization": f"Bearer {os.environ['BUTTERFLY_TOKEN']}",
"Accept": "application/json",
},
timeout=30,
)
r.raise_for_status()
data = r.json()Bug, missing endpoint, or feature request? support@butterflysecurity.org