{
  "openapi": "3.1.0",
  "info": {
    "title": "SafeGrd API",
    "version": "1",
    "description": "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."
  },
  "servers": [
    {
      "url": "https://safegrd.dev"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "paths": {
    "/api/v1/plans": {
      "get": {
        "operationId": "listPlans",
        "summary": "The plan catalogue: prices, quotas, drill cadence, hosted storage",
        "security": [],
        "responses": {
          "200": {
            "description": "Every plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/storage/bucket-setup": {
      "get": {
        "operationId": "bucketSetup",
        "summary": "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",
        "security": [],
        "parameters": [
          {"name": "provider", "in": "query", "required": true, "schema": {"type": "string", "enum": ["aws", "wasabi", "minio", "b2", "r2", "other"]}},
          {"name": "bucket", "in": "query", "required": true, "schema": {"type": "string"}},
          {"name": "expiry", "in": "query", "required": false, "description": "1 to add what opt-in expiry in your own bucket needs", "schema": {"type": "string", "enum": ["0", "1"]}}
        ],
        "responses": {
          "200": {"description": "The setup", "content": {"application/json": {"schema": {"type": "object"}}}},
          "400": {"description": "An unknown provider or a bucket name S3 does not allow"}
        }
      }
    },
    "/api/v1/orgs": {
      "get": {
        "operationId": "listOrganizations",
        "summary": "Organizations the caller belongs to, with plan and quotas",
        "responses": {
          "200": {
            "description": "Organizations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orgs/{org_id}/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "An organization's projects",
        "parameters": [
          {
            "name": "org_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orgs/{org_id}/storage": {
      "get": {
        "operationId": "storageUsage",
        "summary": "Hosted storage: whether it is offered, the quota, and how much is locked",
        "parameters": [
          {
            "name": "org_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "offered": {
                      "type": "boolean"
                    },
                    "quota_bytes": {
                      "type": "integer",
                      "description": "Hosted storage the plan includes"
                    },
                    "used_bytes": {
                      "type": "integer",
                      "description": "Locked, or granted to an upload in flight"
                    },
                    "regime": {
                      "type": "string",
                      "enum": [
                        "quota",
                        "metered",
                        "lapsed"
                      ],
                      "description": "quota: refused at quota_bytes (free plan, trial). metered: never refused for size; the period's average above quota_bytes is billed per GB-month. lapsed: refused past limit_bytes"
                    },
                    "limit_bytes": {
                      "type": "integer",
                      "description": "Where uploads are refused, when that differs from quota_bytes"
                    },
                    "overage_cents_per_gb_month": {
                      "type": "integer",
                      "description": "Metered plans: the price of a GB-month above the quota, in US cents"
                    },
                    "period_start": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "period_overage_gb_months": {
                      "type": "integer",
                      "description": "Metered plans: GB-months above the quota this period so far, rounded down"
                    },
                    "period_overage_cents": {
                      "type": "integer",
                      "description": "Metered plans: what this period's overage comes to so far, in US cents"
                    },
                    "lapsed_plan": {
                      "type": "string",
                      "description": "Lapsed: the last plan paid for, whose quota sets limit_bytes"
                    },
                    "last_lock_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the last byte held stops being locked"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orgs/{org_id}/drill-stats": {
      "get": {
        "operationId": "drillStats",
        "summary": "Fire Drill statistics across an organization",
        "parameters": [
          {
            "name": "org_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Statistics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orgs/{org_id}/recovery": {
      "get": {
        "operationId": "recovery",
        "summary": "Each surface's recovery point and restore time, measured from its snapshots and drills",
        "parameters": [
          {
            "name": "org_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "One row per surface, with the window and sample counts behind each figure",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/nodes": {
      "get": {
        "operationId": "listSurfaces",
        "summary": "Protected surfaces and hosts, with last backup, drill and alert state. Each carries last_snapshot, its newest backup's summary",
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this organization, which must be one of the caller's"
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this project's surfaces"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "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."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one."
          }
        ],
        "responses": {
          "200": {
            "description": "Surfaces",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/Page"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/nodes/{node_id}": {
      "get": {
        "operationId": "getSurface",
        "summary": "One surface or host",
        "parameters": [
          {
            "name": "node_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "The surface",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/nodes/{node_id}/backup-now": {
      "post": {
        "operationId": "requestBackup",
        "summary": "Ask for a backup of the surface now",
        "description": "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).",
        "parameters": [
          {
            "name": "node_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "202": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "200": {
            "description": "Already requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/nodes/{node_id}/drill-now": {
      "post": {
        "operationId": "requestDrill",
        "summary": "Ask for a Fire Drill of the surface's latest snapshot now",
        "description": "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.",
        "parameters": [
          {
            "name": "node_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "202": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "200": {
            "description": "Already requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/snapshots": {
      "get": {
        "operationId": "listSnapshots",
        "summary": "A surface's snapshots: when, how big, locked until when, whether a drill verified them",
        "parameters": [
          {
            "name": "node_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The surface. Without paging this is its whole history, which `safegrd verify` walks to check the attestation chain."
          },
          {
            "name": "org_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this organization, which must be one of the caller's"
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this project's surfaces"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "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."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one."
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshots",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/Page"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/snapshots/{snapshot_id}": {
      "get": {
        "operationId": "getSnapshot",
        "summary": "One snapshot's recorded metadata: digests, counts, lock, Threat Shield verdict",
        "parameters": [
          {
            "name": "snapshot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "The snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/verifications": {
      "get": {
        "operationId": "listDrills",
        "summary": "A surface's Fire Drill reports, with assertions and signatures",
        "parameters": [
          {
            "name": "node_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The surface. Without paging this is its whole history, which `safegrd verify` walks to check the attestation chain."
          },
          {
            "name": "org_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this organization, which must be one of the caller's"
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this project's surfaces"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "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."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page. Opaque; a key, not an offset, so rows arriving meanwhile never repeat or skip one."
          }
        ],
        "responses": {
          "200": {
            "description": "Fire Drill reports",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/Page"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/verifications/{verification_id}": {
      "get": {
        "operationId": "getDrill",
        "summary": "One Fire Drill's whole record, the surface it drilled, and its public certificate if one is published",
        "parameters": [
          {
            "name": "verification_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "The drill",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/certificates/{certificate_id}": {
      "get": {
        "operationId": "getPublicCertificate",
        "summary": "A published drill certificate: a statement, and an Ed25519 signature over the statement's bytes. Needs no token.",
        "security": [],
        "parameters": [
          {
            "name": "certificate_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The id in the certificate's /c/ link"
          }
        ],
        "responses": {
          "200": {
            "description": "The signed statement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "statement": {
                      "type": "string",
                      "description": "JSON, signed as these exact bytes"
                    },
                    "signature": {
                      "type": "string",
                      "description": "Hex Ed25519 signature, checked against /api/v1/attestations/public-key"
                    },
                    "algorithm": {
                      "type": "string"
                    },
                    "signing_key_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/summary": {
      "get": {
        "operationId": "getSummary",
        "summary": "Counts of surfaces, snapshots, drills and open anomalies for the caller's scope",
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this organization, which must be one of the caller's"
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this project's surfaces"
          }
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Summary"
                }
              }
            }
          },
          "401": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. The body says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A personal access token (sg_pat_...)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Page": {
        "type": "object",
        "description": "One page of a list, newest first. next_cursor is absent on the last page.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "next_cursor": {
            "type": "string"
          }
        },
        "required": [
          "items"
        ]
      },
      "Summary": {
        "type": "object",
        "description": "Counts for the caller's scope, as the console's tiles show them.",
        "properties": {
          "surfaces": {
            "type": "integer"
          },
          "nodes": {
            "type": "integer"
          },
          "flagged_surfaces": {
            "type": "integer"
          },
          "open_anomalies": {
            "type": "integer"
          },
          "snapshots": {
            "type": "integer"
          },
          "drills": {
            "type": "integer"
          },
          "drills_passed": {
            "type": "integer"
          }
        }
      }
    }
  }
}
