Skip to main content
/
API

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.

REST27 endpointsOAuth 2.0RFC 8707 audience-bound

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/min on all /api/* endpoints by default
  • 10 req/min on /api/backup/run and /api/restore/* mutations
  • 20 req/min on /api/auth/send-code and /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.

GET/api/backup/listview backups

Paginated, 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 | failed
searchstringFree-text over connection name, error, resource types.
fromISO 8601 timestamp
toISO 8601 timestamp
resourceTypesstring (csv)
minSizeinteger (bytes)
maxSizeinteger (bytes)
limitintegerDefault 100.
offsetintegerDefault 0.
includeStatsbooleanReturns aggregate counts when true.

Response shape

{ backups: Backup[], total: number, stats: { totalBackups, completedBackups, failedBackups, successRate, totalSize, resourceTypes[] } | null }
GET/api/backup/[id]view backups

Fetch one backup by UUID, including resource counts, storage key, and owning connection.

Response shape

{ backup: Backup }
DELETE/api/backup/[id]run backups

Delete a backup record and its R2 snapshot. Irreversible.

Response shape

{ ok: true }
GET/api/backup/statusview backups

Poll the lifecycle of an in-flight backup. Use after POST /backup/run.

Query parameters

backupIdstring (uuid)required

Response shape

{ status, progress_pct, progress_message, error_message?, completed_at? }
GET/api/backup/activeview backups

List backups currently running for the calling team.

Response shape

{ active: Backup[] }
POST/api/backup/runrun backups

Kick off a backup for one connection. Returns immediately with a backupId; poll /api/backup/status for progress.

Request body (JSON)

connectionIdstring (uuid)required
includestring[]Resource categories. Omit for a full snapshot.
includeWorkflowsbooleanOkta only.
forceFullBackupbooleanSkip 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.

GET/api/connectionsmanage connections

List every connection on the calling team across all supported providers.

Response shape

{ connections: Connection[] }
POST/api/connections/createmanage connections

Create a new Okta connection from an OIN-installed API Service Integration. See /docs for other providers.

Request body (JSON)

org_urlstring (https URL)required
client_idstringrequired
client_secretstringrequired
org_namestring

Response shape

{ connection: Connection }
GET/api/connections/[id]manage connections

Fetch one connection (credentials redacted).

Response shape

{ connection: Connection }
PATCH/api/connections/[id]manage connections

Update a connection's metadata or credentials.

Request body (JSON)

org_namestring
client_idstring
client_secretstring

Response shape

{ connection: Connection }
DELETE/api/connections/[id]manage connections

Remove a connection. Existing backups remain available until retention expires.

Response shape

{ ok: true }
GET/api/connections/[id]/schedulemanage connections

Fetch 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 }
PATCH/api/connections/[id]/schedulemanage connections

Update the scheduled-backup configuration.

Request body (JSON)

enabledboolean
frequencyhourly | daily | weekly | monthly
hourinteger 0-23
day_of_weekinteger 0-6weekly only
day_of_monthinteger 1-28monthly only
timezonestring (IANA)
incremental_enabledboolean
full_backup_dayinteger 0-6

Response shape

{ schedule: Schedule }

Diff

Structured comparison between two backups, or between a backup and the live identity provider.

POST/api/diffview backups

Compare two backups by ID. Returns categorized changes by resource type.

Request body (JSON)

fromBackupIdstring (uuid)required
toBackupIdstring (uuid)required
resourceTypesstring[]Optional filter.

Response shape

{ changes: { resourceType, action: 'added' | 'modified' | 'removed', resource, severity }[], summary }
GET/api/diffview backups

Same as POST /api/diff, with parameters supplied as query string.

Query parameters

fromBackupIdstring (uuid)required
toBackupIdstring (uuid)required

Response 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.

GET/api/restorerestore

Dry-run preview of a restore. Returns per-resource-type counts of what would be created, updated, or skipped. Never writes.

Query parameters

backupIdstring (uuid)required
resourceTypesstring (csv)

Response shape

{ preview: { resourceType, create, update, skip }[], sourceOrgUrl, collectedAt }
POST/api/restorerestore

Execute a restore against the live identity provider. Supports selective restore via resourceTypes and resourceIds.

Request body (JSON)

backupIdstring (uuid)required
modefull | selectiverequired
resourceTypesstring[]Required when mode is selective.
resourceIdsstring[]Optional, narrows selective restore.
confirmtruerequiredMust be the literal boolean true.

Response shape

{ restoreId, status: 'started' }

Compliance

Evaluate a connection against one of six compliance frameworks.

POST/api/compliance/checkcompliance

Run a compliance check for a connection against SOC 2, HIPAA, PCI DSS, NIST, ISO 27001, or CIS Controls.

Request body (JSON)

connectionIdstring (uuid)required
frameworksoc2 | hipaa | pci_dss | nist | iso27001 | cis_controlsrequired

Response shape

{ score, findings: { control, status, severity, evidence }[], generatedAt }

Scores

Numeric resilience and restore-readiness scores out of 100, with the contributing factors.

GET/api/scores/resilienceview backups

Resilience score for one connection (0-100) with five weighted factors and actionable recommendations.

Query parameters

connectionIdstring (uuid)required

Response shape

{ score, factors: { name, weight, value, notes }[], recommendations: string[] }
GET/api/scores/readinessview backups

Restore-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.

POST/api/export/reportexport

Generate a PDF report for a backup, a compliance check, or a date range. Returns a signed download URL.

Request body (JSON)

typebackup | compliance | activityrequired
backupIdstring (uuid)Required when type is backup.
frameworkstringRequired when type is compliance.
fromISO 8601 timestampRequired when type is activity.
toISO 8601 timestampRequired when type is activity.

Response shape

{ url, expiresAt }
POST/api/export/audit-packexport

Bundle backup history, restore drills, and compliance evidence into a single ZIP for auditors.

Request body (JSON)

fromISO 8601 timestamprequired
toISO 8601 timestamprequired
connectionIdsstring[]

Response shape

ZIP file (application/zip).

Notifications

Read in-app notifications for the calling user.

GET/api/notificationsview notifications

List notifications for the calling user, newest first.

Query parameters

limitintegerDefault 50.
unreadOnlyboolean

Response shape

{ notifications: Notification[] }

Resource timeline

Per-resource change history derived from sequential backups.

GET/api/resource-timelineview backups

Reconstruct the change history for one resource (user, group, app, policy) across the last N backups.

Query parameters

connectionIdstring (uuid)required
resourceTypestringrequired
resourceIdstringrequired
limitintegerDefault 20 backups.

Response shape

{ events: { backupId, timestamp, change: 'created' | 'modified' | 'deleted', diff }[] }

Newsletter

Public subscription endpoints. CAN-SPAM compliant.

POST/api/newsletter/subscribepublic

Subscribe an email address to the Butterfly Security newsletter.

Request body (JSON)

emailstringrequired

Response shape

{ ok: true }
GET/api/newsletter/unsubscribepublic

One-click unsubscribe link target (also accepts POST).

Query parameters

tokenstringrequired

Response shape

302 redirect to /unsubscribed.

Health

Liveness probe for monitoring.

GET/api/healthpublic

Returns 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

StatusMeaningWhen
401UnauthorizedMissing, expired, or invalid bearer token.
402Plan limit reachedFree or trial limits exhausted; upgrade required.
403ForbiddenRole lacks the required permission.
404Not foundResource does not exist or is not visible to the team.
429Rate limitedPer-IP rate-limit window exceeded.
500Internal errorUnhandled 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