API reference
The part of SafeGrd's /api/v1 an agent needs: read backups and Fire Drills, and ask for a backup or a drill. Authenticate with a personal access token from the console (Tokens). A personal access token cannot delete, disable, re-route or re-key anything, on any route. The same operations are served as MCP tools at https://safegrd.dev/mcp; see https://safegrd.dev/docs/mcp.
This page is rendered from /openapi.json, the document an agent
reads, so the two cannot disagree. Requests go to https://safegrd.dev, with
Authorization: Bearer sg_pat_… from Tokens in the console.
Every other /api/v1 route (sign-in, billing, members, sinks, keys, node
enrolment) is internal: it changes between releases, and a personal access token is
refused on it.
Operations
The plan catalogue: prices, quotas, drill cadence, hosted storage
GET /api/v1/plans (no token needed)
| Response | Meaning | Body |
| 200 | Every plan | array |
What a backup host's key needs in your own bucket, per provider: steps, least-privilege policy, and for AWS a CloudFormation and a Terraform template that create the bucket with Object Lock
GET /api/v1/storage/bucket-setup (no token needed)
| Parameter | In | Meaning |
| provider (required) | query | string |
| bucket (required) | query | string |
| expiry | query | 1 to add what opt-in expiry in your own bucket needs |
| Response | Meaning | Body |
| 200 | The setup | object |
| 400 | An unknown provider or a bucket name S3 does not allow | |
Organizations the caller belongs to, with plan and quotas
GET /api/v1/orgs
| Response | Meaning | Body |
| 200 | Organizations | array |
| 401 | Refused. The body says why. | Error |
Fire Drill statistics across an organization
GET /api/v1/orgs/{org_id}/drill-stats
| Parameter | In | Meaning |
| org_id (required) | path | string |
| Response | Meaning | Body |
| 200 | Statistics | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
An organization's projects
GET /api/v1/orgs/{org_id}/projects
| Parameter | In | Meaning |
| org_id (required) | path | string |
| Response | Meaning | Body |
| 200 | Projects | array |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
Each surface's recovery point and restore time, measured from its snapshots and drills
GET /api/v1/orgs/{org_id}/recovery
| Parameter | In | Meaning |
| org_id (required) | path | string |
| Response | Meaning | Body |
| 200 | One row per surface, with the window and sample counts behind each figure | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
Hosted storage: whether it is offered, the quota, and how much is locked
GET /api/v1/orgs/{org_id}/storage
| Parameter | In | Meaning |
| org_id (required) | path | string |
| Response | Meaning | Body |
| 200 | Usage | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
Protected surfaces and hosts, with last backup, drill and alert state. Each carries last_snapshot, its newest backup's summary
GET /api/v1/nodes
| Parameter | In | Meaning |
| org_id | query | Only this organization, which must be one of the caller's |
| project_id | query | Only this project's surfaces |
| limit | query | Ask for a page of this many, newest first (default 50, at most 200). With limit or cursor the response is a Page; without either it is the whole list as a bare array. |
| cursor | query | next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one. |
| Response | Meaning | Body |
| 200 | Surfaces | |
| 400 | Refused. The body says why. | Error |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
One surface or host
GET /api/v1/nodes/{node_id}
| Parameter | In | Meaning |
| node_id (required) | path | string |
| Response | Meaning | Body |
| 200 | The surface | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
Ask for a backup of the surface now
POST /api/v1/nodes/{node_id}/backup-now
The host's daemon runs it at its next check-in. A surface SafeGrd backs up is queued on SafeGrd instead (202 with a job_id). Every backup is locked and cannot be deleted before its retention expires, so a request within an hour of the last backup is refused with 429. A host that does not run the daemon cannot pick a request up (409).
| Parameter | In | Meaning |
| node_id (required) | path | string |
| Response | Meaning | Body |
| 200 | Already requested | object |
| 202 | Requested | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
| 409 | Refused. The body says why. | Error |
| 429 | Refused. The body says why. | Error |
Ask for a Fire Drill of the surface's latest snapshot now
POST /api/v1/nodes/{node_id}/drill-now
The host's daemon runs it at its next check-in. A surface set to drill on SafeGrd is queued there instead (202 with a job_id) and needs no daemon. Counted against the plan's drill cadence: refused with 429 until the next drill is due. Refused with 409 when nothing would run it: no daemon on the host, or no backup yet.
| Parameter | In | Meaning |
| node_id (required) | path | string |
| Response | Meaning | Body |
| 200 | Already requested | object |
| 202 | Requested | object |
| 401 | Refused. The body says why. | Error |
| 402 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
| 409 | Refused. The body says why. | Error |
| 429 | Refused. The body says why. | Error |
A surface's snapshots: when, how big, locked until when, whether a drill verified them
GET /api/v1/snapshots
| Parameter | In | Meaning |
| node_id | query | The surface. Without paging this is its whole history, which `safegrd verify` walks to check the attestation chain. |
| org_id | query | Only this organization, which must be one of the caller's |
| project_id | query | Only this project's surfaces |
| limit | query | Ask for a page of this many, newest first (default 50, at most 200). With limit or cursor the response is a Page; without either it is the whole list as a bare array. |
| cursor | query | next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one. |
| Response | Meaning | Body |
| 200 | Snapshots | |
| 400 | Refused. The body says why. | Error |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
One snapshot's recorded metadata: digests, counts, lock, Threat Shield verdict
GET /api/v1/snapshots/{snapshot_id}
| Parameter | In | Meaning |
| snapshot_id (required) | path | string |
| Response | Meaning | Body |
| 200 | The snapshot | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
A surface's Fire Drill reports, with assertions and signatures
GET /api/v1/verifications
| Parameter | In | Meaning |
| node_id | query | The surface. Without paging this is its whole history, which `safegrd verify` walks to check the attestation chain. |
| org_id | query | Only this organization, which must be one of the caller's |
| project_id | query | Only this project's surfaces |
| limit | query | Ask for a page of this many, newest first (default 50, at most 200). With limit or cursor the response is a Page; without either it is the whole list as a bare array. |
| cursor | query | next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one. |
| Response | Meaning | Body |
| 200 | Fire Drill reports | |
| 400 | Refused. The body says why. | Error |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
One Fire Drill's whole record, the surface it drilled, and its public certificate if one is published
GET /api/v1/verifications/{verification_id}
| Parameter | In | Meaning |
| verification_id (required) | path | string |
| Response | Meaning | Body |
| 200 | The drill | object |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
| 404 | Refused. The body says why. | Error |
A published drill certificate: a statement, and an Ed25519 signature over the statement's bytes. Needs no token.
GET /api/v1/certificates/{certificate_id} (no token needed)
| Parameter | In | Meaning |
| certificate_id (required) | path | The id in the certificate's /c/ link |
| Response | Meaning | Body |
| 200 | The signed statement | object |
| 404 | Refused. The body says why. | Error |
Counts of surfaces, snapshots, drills and open anomalies for the caller's scope
GET /api/v1/summary
| Parameter | In | Meaning |
| org_id | query | Only this organization, which must be one of the caller's |
| project_id | query | Only this project's surfaces |
| Response | Meaning | Body |
| 200 | Counts | Summary |
| 401 | Refused. The body says why. | Error |
| 403 | Refused. The body says why. | Error |
Bodies
Lists come back newest first. With limit or cursor a list is a
Page; without either it is the whole list as a bare array.
Error
| Field | Type | Meaning |
| error (required) | string | |
Page
One page of a list, newest first. next_cursor is absent on the last page.
| Field | Type | Meaning |
| items (required) | array | |
| next_cursor | string | |
Summary
Counts for the caller's scope, as the console's tiles show them.
| Field | Type | Meaning |
| drills | integer | |
| drills_passed | integer | |
| flagged_surfaces | integer | |
| nodes | integer | |
| open_anomalies | integer | |
| snapshots | integer | |
| surfaces | integer | |
The same operations are MCP tools (AI agents). Everything an
agent can do from the command line is in the command reference.