# Safety

> The Safety Centre grade (A to E), its checks and fixes, and the public badge.

The Safety Centre grade (A to E), its checks and fixes, and the public badge.

| Operation | Method and path |
| --- | --- |
| [Safety Centre grade and checks](#safety-centre-grade-and-checks) | `GET /v1/projects/{project}/safety` |
| [Re-run every safety check now](#re-run-every-safety-check-now) | `POST /v1/projects/{project}/safety/scan` |
| [Apply a finding's one-click fix (or open an AI fix PR)](#apply-a-findings-one-click-fix-or-open-an-ai-fix-pr) | `POST /v1/projects/{project}/safety/checks/{check_id}/fix` |
| [Acknowledge a finding (accept the risk)](#acknowledge-a-finding-accept-the-risk) | `POST /v1/projects/{project}/safety/checks/{check_id}/acknowledge` |
| [Public badge summary](#public-badge-summary) | `GET /v1/public/safety/{badge_id}` |
| [Public badge image](#public-badge-image) | `GET /v1/public/safety/{badge_id}/badge.svg` |

## Safety Centre grade and checks

```http
GET /v1/projects/{project}/safety
```

- **Operation:** `getSafetyReport`
- **Scopes:** `safety:read`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` | query | string | Defaults to production. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [SafetyReport](/docs/api/objects/safety-report/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/projects/shopfront/safety \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "project_id": "string",
  "environment": "string",
  "grade": "A",
  "score": 0,
  "summary": "Grade B. One thing to fix: members without passkeys.",
  "counts": {
    "pass": 1,
    "warn": 1,
    "fail": 1
  },
  "checks": [
    {
      "id": "chk_01jbx2m9k7q4r8t5v6w3y0z1ck",
      "category": "secrets_in_bundle",
      "status": "fail",
      "severity": "critical",
      "title": "Secret visible in your website's code",
      "finding": "Your SUPABASE_SERVICE_ROLE_KEY is visible to anyone visiting your site.",
      "environment": "production",
      "resource": {
        "type": "service",
        "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
        "name": "web"
      },
      "blocks_deploy": true,
      "fix": [
        {
          "kind": "manual",
          "label": "Rotate the key in Supabase, then update it here",
          "command": "guv secrets rotate SUPABASE_SERVICE_ROLE_KEY --env production"
        },
        {
          "kind": "open_pr",
          "label": "Ask AI to move the key server-side (opens a PR)"
        }
      ],
      "acknowledged": null,
      "detected_at": "2026-10-23T14:03:00Z",
      "resolved_at": null
    }
  ],
  "badge": {
    "enabled": false,
    "url": "https://guv.dev/",
    "svg_url": "https://guv.dev/"
  },
  "checked_at": "2026-10-22T13:05:00Z"
}
```

## Re-run every safety check now

```http
POST /v1/projects/{project}/safety/scan
```

- **Operation:** `runSafetyScan`
- **Scopes:** `safety:read`
- **Idempotency-Key:** required

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/safety/scan \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```

## Apply a finding's one-click fix (or open an AI fix PR)

```http
POST /v1/projects/{project}/safety/checks/{check_id}/fix
```

- **Operation:** `applySafetyFix`
- **Scopes:** `safety:write`
- **Approval:** In environments with protection turned on (usually production), changes by agents and by roles outside `approver_roles` return `approval_required`.
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `check_id` * | path | string | Safety check id. |
| `action` | query | integer | Index into the check's `fix` array. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/safety/checks/che_01jbx2m9k7q4r8t5v6w3y0z1pr/fix \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```

## Acknowledge a finding (accept the risk)

```http
POST /v1/projects/{project}/safety/checks/{check_id}/acknowledge
```

Acknowledged findings still count towards the grade. Findings that block deploys cannot be acknowledged.

- **Operation:** `acknowledgeSafetyCheck`
- **Scopes:** `safety:write`
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `check_id` * | path | string | Safety check id. |

**Request body** (`application/json`, required): [SafetyAcknowledge](/docs/api/objects/safety-acknowledge/)

| Field | Type | Description |
| --- | --- | --- |
| `reason` * | string |  |
| `until` | string (date-time) |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. With `dry_run=true`: a Plan (application/vnd.guv.plan+json) and nothing changes. | [SafetyCheck](/docs/api/objects/safety-check/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/safety/checks/che_01jbx2m9k7q4r8t5v6w3y0z1pr/acknowledge \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "string",
  "until": "2026-10-22T13:05:00Z"
}'
```

**Example response**

```json
{
  "id": "chk_01jbx2m9k7q4r8t5v6w3y0z1ck",
  "category": "secrets_in_bundle",
  "status": "fail",
  "severity": "critical",
  "title": "Secret visible in your website's code",
  "finding": "Your SUPABASE_SERVICE_ROLE_KEY is visible to anyone visiting your site.",
  "environment": "production",
  "resource": {
    "type": "service",
    "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
    "name": "web"
  },
  "blocks_deploy": true,
  "fix": [
    {
      "kind": "manual",
      "label": "Rotate the key in Supabase, then update it here",
      "command": "guv secrets rotate SUPABASE_SERVICE_ROLE_KEY --env production"
    },
    {
      "kind": "open_pr",
      "label": "Ask AI to move the key server-side (opens a PR)"
    }
  ],
  "acknowledged": null,
  "detected_at": "2026-10-23T14:03:00Z",
  "resolved_at": null
}
```

## Public badge summary

```http
GET /v1/public/safety/{badge_id}
```

- **Operation:** `getPublicSafetySummary`
- **Scopes:** none (public)

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `badge_id` * | path | string | Public, unguessable badge id of a project. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [SafetyBadgeSummary](/docs/api/objects/safety-badge-summary/) |

**Errors:** `400`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/public/safety/bad_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "project_name": "string",
  "grade": "A",
  "checked_at": "2026-10-22T13:05:00Z",
  "data_location": "London, UK",
  "categories": [
    {
      "category": "secrets_in_code",
      "label": "string",
      "status": "pass"
    }
  ]
}
```

## Public badge image

```http
GET /v1/public/safety/{badge_id}/badge.svg
```

- **Operation:** `getPublicSafetyBadge`
- **Scopes:** none (public)

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `badge_id` * | path | string | Public, unguessable badge id of a project. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK | string |

**Errors:** `400`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/public/safety/bad_01jbx2m9k7q4r8t5v6w3y0z1pr/badge.svg \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
"string"
```
