# Services

> Web services, private services, workers and cron jobs.

Web services, private services, workers and cron jobs. A service is defined once per project and has an instance in each environment.

| Operation | Method and path |
| --- | --- |
| [List services (project-level definitions)](#list-services-project-level-definitions) | `GET /v1/projects/{project}/services` |
| [Create a service](#create-a-service) | `POST /v1/projects/{project}/services` |
| [Get a service's base settings](#get-a-services-base-settings) | `GET /v1/projects/{project}/services/{service}` |
| [Update a service's base settings](#update-a-services-base-settings) | `PATCH /v1/projects/{project}/services/{service}` |
| [Delete a service from every environment](#delete-a-service-from-every-environment) | `DELETE /v1/projects/{project}/services/{service}` |
| [Services as they run in an environment](#services-as-they-run-in-an-environment) | `GET /v1/projects/{project}/environments/{environment}/services` |
| [A service in one environment (effective settings, status, URLs)](#a-service-in-one-environment-effective-settings-status-urls) | `GET /v1/projects/{project}/environments/{environment}/services/{service}` |
| [Set this environment's overrides for a service](#set-this-environments-overrides-for-a-service) | `PATCH /v1/projects/{project}/environments/{environment}/services/{service}` |
| [Restart the running deployment](#restart-the-running-deployment) | `POST /v1/projects/{project}/environments/{environment}/services/{service}/restart` |

## List services (project-level definitions)

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

- **Operation:** `listServices`
- **Scopes:** `projects:read`
- **Paginated:** `?limit=` and `?cursor=`

**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 |
| --- | --- | --- |
| `200` OK | A page of Service objects. | [ServiceList](/docs/api/objects/service-list/) |

**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/services \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
      "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
      "name": "web",
      "kind": "web",
      "managed_by_manifest": true,
      "source": {
        "kind": "git",
        "repo": "primecarers/platform",
        "root": "apps/web",
        "watch": [
          "apps/web/**",
          "packages/ui/**",
          "pnpm-lock.yaml"
        ]
      },
      "build": {
        "builder": "railpack"
      },
      "start": "pnpm --filter web start",
      "pre_deploy": "pnpm --filter db migrate deploy",
      "port": 3000,
      "healthcheck": {
        "type": "http",
        "path": "/healthz",
        "timeout_seconds": 10
      },
      "resources": {
        "cpu": 2,
        "memory_mb": 2048
      },
      "replicas": {
        "min": 2,
        "max": 6,
        "scale_on": {
          "cpu_percent": 70,
          "concurrency": 80
        }
      },
      "sleep_when_idle": false,
      "private": false,
      "cron": null,
      "created_at": "2026-10-22T09:30:00Z",
      "updated_at": "2026-10-23T10:00:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## Create a service

```http
POST /v1/projects/{project}/services
```

Creates the definition and an instance in every environment. Nothing runs until the first deployment.

- **Operation:** `createService`
- **Scopes:** `projects: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). |

**Request body** (`application/json`, required): [ServiceCreate](/docs/api/objects/service-create/)

| Field | Type | Description |
| --- | --- | --- |
| `name` * | string |  |
| `source` | [ServiceSource](/docs/api/objects/service-source/) |  |
| `build` | [BuildSettings](/docs/api/objects/build-settings/) |  |
| `start` | string or null | Start command. Detected when empty. |
| `pre_deploy` | string or null | Runs once per deployment in a separate VM before traffic moves; failure stops the rollout. |
| `port` | integer or null |  |
| `healthcheck` | [Healthcheck](/docs/api/objects/healthcheck/) or any or null |  |
| `resources` | [ServiceResources](/docs/api/objects/service-resources/) |  |
| `replicas` | [Replicas](/docs/api/objects/replicas/) |  |
| `sleep_when_idle` | boolean | Scale to zero after 10 idle minutes; the first request wakes it in 1–3 s. |
| `private` | boolean | No public domain; reachable only on the environment's private network. |
| `cron` | [CronSettings](/docs/api/objects/cron-settings/) or any or null |  |
| `restart_policy` | object |  |
| `deploy` | object |  |
| `volumes` | array of [VolumeMount](/docs/api/objects/volume-mount/) |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `201` Created | Created. | [Service](/docs/api/objects/service/) |

**Errors:** `400`, `401`, `402`, `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/services \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "source": {
    "kind": "git",
    "repo": "string",
    "root": "apps/web",
    "watch": [
      "apps/web/**",
      "packages/ui/**",
      "pnpm-lock.yaml"
    ],
    "image": "ghcr.io/acme/api:1.4.2",
    "registry_credentials_secret": "string"
  },
  "build": {
    "builder": "railpack",
    "dockerfile": "services/api/Dockerfile",
    "context": "string"
  },
  "start": "pnpm --filter web start",
  "pre_deploy": "pnpm --filter db migrate deploy",
  "port": 3000,
  "healthcheck": {
    "type": "http",
    "path": "/healthz",
    "port": 1,
    "timeout_seconds": 10,
    "interval_seconds": 10,
    "start_timeout_seconds": 300
  },
  "resources": {
    "cpu": 1,
    "memory_mb": 1024
  },
  "replicas": {
    "min": 1,
    "max": 1,
    "scale_on": {
      "cpu_percent": 10,
      "memory_percent": 10,
      "concurrency": 1
    }
  },
  "sleep_when_idle": false,
  "private": false,
  "cron": {
    "schedule": "0 2 * * *",
    "timezone": "Europe/London",
    "timeout_seconds": 1,
    "concurrency_policy": "forbid"
  },
  "restart_policy": {
    "mode": "on_failure",
    "max_retries": 10
  },
  "deploy": {
    "overlap_seconds": 20,
    "auto_rollback": false
  },
  "volumes": [
    {
      "volume": "string",
      "mount_path": "/data"
    }
  ]
}'
```

**Example response**

```json
{
  "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "name": "web",
  "kind": "web",
  "managed_by_manifest": true,
  "source": {
    "kind": "git",
    "repo": "primecarers/platform",
    "root": "apps/web",
    "watch": [
      "apps/web/**",
      "packages/ui/**",
      "pnpm-lock.yaml"
    ]
  },
  "build": {
    "builder": "railpack"
  },
  "start": "pnpm --filter web start",
  "pre_deploy": "pnpm --filter db migrate deploy",
  "port": 3000,
  "healthcheck": {
    "type": "http",
    "path": "/healthz",
    "timeout_seconds": 10
  },
  "resources": {
    "cpu": 2,
    "memory_mb": 2048
  },
  "replicas": {
    "min": 2,
    "max": 6,
    "scale_on": {
      "cpu_percent": 70,
      "concurrency": 80
    }
  },
  "sleep_when_idle": false,
  "private": false,
  "cron": null,
  "created_at": "2026-10-22T09:30:00Z",
  "updated_at": "2026-10-23T10:00:00Z"
}
```

## Get a service's base settings

```http
GET /v1/projects/{project}/services/{service}
```

- **Operation:** `getService`
- **Scopes:** `projects: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). |
| `service` * | path | string | Service id (`svc_…`) or name. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [Service](/docs/api/objects/service/) |

**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/services/web \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "name": "web",
  "kind": "web",
  "managed_by_manifest": true,
  "source": {
    "kind": "git",
    "repo": "primecarers/platform",
    "root": "apps/web",
    "watch": [
      "apps/web/**",
      "packages/ui/**",
      "pnpm-lock.yaml"
    ]
  },
  "build": {
    "builder": "railpack"
  },
  "start": "pnpm --filter web start",
  "pre_deploy": "pnpm --filter db migrate deploy",
  "port": 3000,
  "healthcheck": {
    "type": "http",
    "path": "/healthz",
    "timeout_seconds": 10
  },
  "resources": {
    "cpu": 2,
    "memory_mb": 2048
  },
  "replicas": {
    "min": 2,
    "max": 6,
    "scale_on": {
      "cpu_percent": 70,
      "concurrency": 80
    }
  },
  "sleep_when_idle": false,
  "private": false,
  "cron": null,
  "created_at": "2026-10-22T09:30:00Z",
  "updated_at": "2026-10-23T10:00:00Z"
}
```

## Update a service's base settings

```http
PATCH /v1/projects/{project}/services/{service}
```

JSON Merge Patch. Changes apply on the next deployment of each environment. Projects managed by guv.yaml return a `manifest_patch` fix suggesting a PR instead, unless `?force=true`.

- **Operation:** `updateService`
- **Scopes:** `projects:write`
- **CLI:** `guv services update`
- **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`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `service` * | path | string | Service id (`svc_…`) or name. |
| `force` | query | boolean | Edit even though the project is managed by guv.yaml (creates drift). |

**Request body** (`application/json`, required): [ServiceConfig](/docs/api/objects/service-config/)

| Field | Type | Description |
| --- | --- | --- |
| `source` | [ServiceSource](/docs/api/objects/service-source/) |  |
| `build` | [BuildSettings](/docs/api/objects/build-settings/) |  |
| `start` | string or null | Start command. Detected when empty. |
| `pre_deploy` | string or null | Runs once per deployment in a separate VM before traffic moves; failure stops the rollout. |
| `port` | integer or null |  |
| `healthcheck` | [Healthcheck](/docs/api/objects/healthcheck/) or any or null |  |
| `resources` | [ServiceResources](/docs/api/objects/service-resources/) |  |
| `replicas` | [Replicas](/docs/api/objects/replicas/) |  |
| `sleep_when_idle` | boolean | Scale to zero after 10 idle minutes; the first request wakes it in 1–3 s. |
| `private` | boolean | No public domain; reachable only on the environment's private network. |
| `cron` | [CronSettings](/docs/api/objects/cron-settings/) or any or null |  |
| `restart_policy` | object |  |
| `deploy` | object |  |
| `volumes` | array of [VolumeMount](/docs/api/objects/volume-mount/) |  |

**Responses**

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

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

**Example**

```bash
curl -X PATCH https://api.guv.dev/v1/projects/shopfront/services/web \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "source": {
    "kind": "git",
    "repo": "string",
    "root": "apps/web",
    "watch": [
      "apps/web/**",
      "packages/ui/**",
      "pnpm-lock.yaml"
    ],
    "image": "ghcr.io/acme/api:1.4.2",
    "registry_credentials_secret": "string"
  },
  "build": {
    "builder": "railpack",
    "dockerfile": "services/api/Dockerfile",
    "context": "string"
  },
  "start": "pnpm --filter web start",
  "pre_deploy": "pnpm --filter db migrate deploy",
  "port": 3000,
  "healthcheck": {
    "type": "http",
    "path": "/healthz",
    "port": 1,
    "timeout_seconds": 10,
    "interval_seconds": 10,
    "start_timeout_seconds": 300
  },
  "resources": {
    "cpu": 1,
    "memory_mb": 1024
  },
  "replicas": {
    "min": 1,
    "max": 1,
    "scale_on": {
      "cpu_percent": 10,
      "memory_percent": 10,
      "concurrency": 1
    }
  },
  "sleep_when_idle": false,
  "private": false,
  "cron": {
    "schedule": "0 2 * * *",
    "timezone": "Europe/London",
    "timeout_seconds": 1,
    "concurrency_policy": "forbid"
  },
  "restart_policy": {
    "mode": "on_failure",
    "max_retries": 10
  },
  "deploy": {
    "overlap_seconds": 20,
    "auto_rollback": false
  },
  "volumes": [
    {
      "volume": "string",
      "mount_path": "/data"
    }
  ]
}'
```

**Example response**

```json
{
  "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "name": "web",
  "kind": "web",
  "managed_by_manifest": true,
  "source": {
    "kind": "git",
    "repo": "primecarers/platform",
    "root": "apps/web",
    "watch": [
      "apps/web/**",
      "packages/ui/**",
      "pnpm-lock.yaml"
    ]
  },
  "build": {
    "builder": "railpack"
  },
  "start": "pnpm --filter web start",
  "pre_deploy": "pnpm --filter db migrate deploy",
  "port": 3000,
  "healthcheck": {
    "type": "http",
    "path": "/healthz",
    "timeout_seconds": 10
  },
  "resources": {
    "cpu": 2,
    "memory_mb": 2048
  },
  "replicas": {
    "min": 2,
    "max": 6,
    "scale_on": {
      "cpu_percent": 70,
      "concurrency": 80
    }
  },
  "sleep_when_idle": false,
  "private": false,
  "cron": null,
  "created_at": "2026-10-22T09:30:00Z",
  "updated_at": "2026-10-23T10:00:00Z"
}
```

## Delete a service from every environment

```http
DELETE /v1/projects/{project}/services/{service}
```

- **Operation:** `deleteService`
- **Scopes:** `projects:delete`
- **Approval:** Always needs a human approval with a passkey, whoever asks, including agents with full scopes.
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `service` * | path | string | Service id (`svc_…`) or name. |
| `confirm` | query | string | Typed confirmation for destructive actions. Must equal the resource name (or slug/hostname). Missing or wrong values return `confirmation_required`. |

**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`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X DELETE https://api.guv.dev/v1/projects/shopfront/services/web \
  -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"
  }
}
```

## Services as they run in an environment

```http
GET /v1/projects/{project}/environments/{environment}/services
```

- **Operation:** `listServiceInstances`
- **Scopes:** `projects:read`
- **Paginated:** `?limit=` and `?cursor=`

**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` * | path | string | Environment id (`env_…`) or name. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | A page of ServiceInstance objects. | [ServiceInstanceList](/docs/api/objects/service-instance-list/) |

**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/environments/production/services \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "service_id": "string",
      "environment_id": "string",
      "name": "string",
      "kind": "web",
      "status": "no_deployment",
      "status_text": "Live. 2 of 2 copies healthy.",
      "effective": {
        "source": {
          "kind": "git",
          "repo": "string",
          "root": "apps/web",
          "watch": [
            "apps/web/**",
            "packages/ui/**",
            "pnpm-lock.yaml"
          ],
          "image": "ghcr.io/acme/api:1.4.2",
          "registry_credentials_secret": "string"
        },
        "build": {
          "builder": "railpack",
          "dockerfile": "services/api/Dockerfile",
          "context": "string"
        },
        "start": "pnpm --filter web start",
        "pre_deploy": "pnpm --filter db migrate deploy",
        "port": 3000,
        "healthcheck": {
          "type": "http",
          "path": "/healthz",
          "port": 1,
          "timeout_seconds": 10,
          "interval_seconds": 10,
          "start_timeout_seconds": 300
        },
        "resources": {
          "cpu": 1,
          "memory_mb": 1024
        },
        "replicas": {
          "min": 1,
          "max": 1,
          "scale_on": {}
        },
        "sleep_when_idle": false,
        "private": false,
        "cron": {
          "schedule": "0 2 * * *",
          "timezone": "Europe/London",
          "timeout_seconds": 1,
          "concurrency_policy": "forbid"
        },
        "restart_policy": {
          "mode": "on_failure",
          "max_retries": 10
        },
        "deploy": {
          "overlap_seconds": 20,
          "auto_rollback": false
        },
        "volumes": [
          {}
        ]
      },
      "overrides": {
        "source": {
          "kind": "git",
          "repo": "string",
          "root": "apps/web",
          "watch": [
            "apps/web/**",
            "packages/ui/**",
            "pnpm-lock.yaml"
          ],
          "image": "ghcr.io/acme/api:1.4.2",
          "registry_credentials_secret": "string"
        },
        "build": {
          "builder": "railpack",
          "dockerfile": "services/api/Dockerfile",
          "context": "string"
        },
        "start": "pnpm --filter web start",
        "pre_deploy": "pnpm --filter db migrate deploy",
        "port": 3000,
        "healthcheck": {
          "type": "http",
          "path": "/healthz",
          "port": 1,
          "timeout_seconds": 10,
          "interval_seconds": 10,
          "start_timeout_seconds": 300
        },
        "resources": {
          "cpu": 1,
          "memory_mb": 1024
        },
        "replicas": {
          "min": 1,
          "max": 1,
          "scale_on": {}
        },
        "sleep_when_idle": false,
        "private": false,
        "cron": {
          "schedule": "0 2 * * *",
          "timezone": "Europe/London",
          "timeout_seconds": 1,
          "concurrency_policy": "forbid"
        },
        "restart_policy": {
          "mode": "on_failure",
          "max_retries": 10
        },
        "deploy": {
          "overlap_seconds": 20,
          "auto_rollback": false
        },
        "volumes": [
          {}
        ]
      },
      "current_deployment": {
        "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
        "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
        "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
        "service_id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
        "service_name": "web",
        "environment_name": "production",
        "status": "active",
        "status_text": "Live since 14:05",
        "trigger": {
          "kind": "git_push",
          "actor": {
            "type": "github",
            "id": "stewartwebb",
            "display": "stewartwebb pushed to main"
          }
        },
        "source": {
          "kind": "git",
          "commit": {
            "sha": "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678",
            "branch": "main",
            "message": "Fix rota export",
            "author": "Stewart Webb"
          }
        },
        "build_id": "bld_01jbx2m9k7q4r8t5v6w3y0z1bd",
        "image": {
          "digest": "sha256:4b1f0e9d8c7b6a5948372615f4e3d2c1b0a9f8e7d6c5b4a3928170f6e5d4c3b2",
          "signed": true
        },
        "rollback_of": null,
        "operation_id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
        "url": "https://www.primecarers.co.uk",
        "failure": null,
        "created_at": "2026-10-23T14:02:00Z",
        "started_at": "2026-10-23T14:02:01Z",
        "active_at": "2026-10-23T14:05:12Z",
        "finished_at": "2026-10-23T14:05:12Z",
        "duration_ms": 191000
      },
      "urls": {
        "public": [
          "https://web-primecarers-k7q4.guv.run",
          "https://www.primecarers.co.uk"
        ],
        "internal_host": "web.guv.internal"
      },
      "replicas": {
        "desired": 1,
        "running": 1
      },
      "last_error": {
        "message": "string",
        "explanation_url": "string"
      },
      "cost_today": {
        "amount": "7.42",
        "currency": "GBP",
        "display": "£7.42"
      },
      "updated_at": "2026-10-22T13:05:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## A service in one environment (effective settings, status, URLs)

```http
GET /v1/projects/{project}/environments/{environment}/services/{service}
```

- **Operation:** `getServiceInstance`
- **Scopes:** `projects: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` * | path | string | Environment id (`env_…`) or name. |
| `service` * | path | string | Service id (`svc_…`) or name. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [ServiceInstance](/docs/api/objects/service-instance/) |

**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/environments/production/services/web \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "service_id": "string",
  "environment_id": "string",
  "name": "string",
  "kind": "web",
  "status": "no_deployment",
  "status_text": "Live. 2 of 2 copies healthy.",
  "effective": {
    "source": {
      "kind": "git",
      "repo": "string",
      "root": "apps/web",
      "watch": [
        "apps/web/**",
        "packages/ui/**",
        "pnpm-lock.yaml"
      ],
      "image": "ghcr.io/acme/api:1.4.2",
      "registry_credentials_secret": "string"
    },
    "build": {
      "builder": "railpack",
      "dockerfile": "services/api/Dockerfile",
      "context": "string"
    },
    "start": "pnpm --filter web start",
    "pre_deploy": "pnpm --filter db migrate deploy",
    "port": 3000,
    "healthcheck": {
      "type": "http",
      "path": "/healthz",
      "port": 1,
      "timeout_seconds": 10,
      "interval_seconds": 10,
      "start_timeout_seconds": 300
    },
    "resources": {
      "cpu": 1,
      "memory_mb": 1024
    },
    "replicas": {
      "min": 1,
      "max": 1,
      "scale_on": {
        "cpu_percent": 10,
        "memory_percent": 10,
        "concurrency": 1
      }
    },
    "sleep_when_idle": false,
    "private": false,
    "cron": {
      "schedule": "0 2 * * *",
      "timezone": "Europe/London",
      "timeout_seconds": 1,
      "concurrency_policy": "forbid"
    },
    "restart_policy": {
      "mode": "on_failure",
      "max_retries": 10
    },
    "deploy": {
      "overlap_seconds": 20,
      "auto_rollback": false
    },
    "volumes": [
      {
        "volume": "string",
        "mount_path": "/data"
      }
    ]
  },
  "overrides": {
    "source": {
      "kind": "git",
      "repo": "string",
      "root": "apps/web",
      "watch": [
        "apps/web/**",
        "packages/ui/**",
        "pnpm-lock.yaml"
      ],
      "image": "ghcr.io/acme/api:1.4.2",
      "registry_credentials_secret": "string"
    },
    "build": {
      "builder": "railpack",
      "dockerfile": "services/api/Dockerfile",
      "context": "string"
    },
    "start": "pnpm --filter web start",
    "pre_deploy": "pnpm --filter db migrate deploy",
    "port": 3000,
    "healthcheck": {
      "type": "http",
      "path": "/healthz",
      "port": 1,
      "timeout_seconds": 10,
      "interval_seconds": 10,
      "start_timeout_seconds": 300
    },
    "resources": {
      "cpu": 1,
      "memory_mb": 1024
    },
    "replicas": {
      "min": 1,
      "max": 1,
      "scale_on": {
        "cpu_percent": 10,
        "memory_percent": 10,
        "concurrency": 1
      }
    },
    "sleep_when_idle": false,
    "private": false,
    "cron": {
      "schedule": "0 2 * * *",
      "timezone": "Europe/London",
      "timeout_seconds": 1,
      "concurrency_policy": "forbid"
    },
    "restart_policy": {
      "mode": "on_failure",
      "max_retries": 10
    },
    "deploy": {
      "overlap_seconds": 20,
      "auto_rollback": false
    },
    "volumes": [
      {
        "volume": "string",
        "mount_path": "/data"
      }
    ]
  },
  "current_deployment": {
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
    "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
    "service_id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
    "service_name": "web",
    "environment_name": "production",
    "status": "active",
    "status_text": "Live since 14:05",
    "trigger": {
      "kind": "git_push",
      "actor": {
        "type": "github",
        "id": "stewartwebb",
        "display": "stewartwebb pushed to main"
      }
    },
    "source": {
      "kind": "git",
      "commit": {
        "sha": "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678",
        "branch": "main",
        "message": "Fix rota export",
        "author": "Stewart Webb"
      }
    },
    "build_id": "bld_01jbx2m9k7q4r8t5v6w3y0z1bd",
    "image": {
      "digest": "sha256:4b1f0e9d8c7b6a5948372615f4e3d2c1b0a9f8e7d6c5b4a3928170f6e5d4c3b2",
      "signed": true
    },
    "rollback_of": null,
    "operation_id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "url": "https://www.primecarers.co.uk",
    "failure": null,
    "created_at": "2026-10-23T14:02:00Z",
    "started_at": "2026-10-23T14:02:01Z",
    "active_at": "2026-10-23T14:05:12Z",
    "finished_at": "2026-10-23T14:05:12Z",
    "duration_ms": 191000
  },
  "urls": {
    "public": [
      "https://web-primecarers-k7q4.guv.run",
      "https://www.primecarers.co.uk"
    ],
    "internal_host": "web.guv.internal"
  },
  "replicas": {
    "desired": 1,
    "running": 1
  },
  "last_error": {
    "message": "string",
    "explanation_url": "string"
  },
  "cost_today": {
    "amount": "7.42",
    "currency": "GBP",
    "display": "£7.42"
  },
  "updated_at": "2026-10-22T13:05:00Z"
}
```

## Set this environment's overrides for a service

```http
PATCH /v1/projects/{project}/environments/{environment}/services/{service}
```

- **Operation:** `updateServiceInstance`
- **Scopes:** `projects: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`
- **Concurrency:** send the `ETag` you read as `If-Match`

**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` * | path | string | Environment id (`env_…`) or name. |
| `service` * | path | string | Service id (`svc_…`) or name. |

**Request body** (`application/json`, required): [ServiceInstanceUpdate](/docs/api/objects/service-instance-update/)

| Field | Type | Description |
| --- | --- | --- |
| `overrides` | [ServiceConfig](/docs/api/objects/service-config/) |  |

**Responses**

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

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

**Example**

```bash
curl -X PATCH https://api.guv.dev/v1/projects/shopfront/environments/production/services/web \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "overrides": {
    "source": {
      "kind": "git",
      "repo": "string",
      "root": "apps/web",
      "watch": [
        "apps/web/**",
        "packages/ui/**",
        "pnpm-lock.yaml"
      ],
      "image": "ghcr.io/acme/api:1.4.2",
      "registry_credentials_secret": "string"
    },
    "build": {
      "builder": "railpack",
      "dockerfile": "services/api/Dockerfile",
      "context": "string"
    },
    "start": "pnpm --filter web start",
    "pre_deploy": "pnpm --filter db migrate deploy",
    "port": 3000,
    "healthcheck": {
      "type": "http",
      "path": "/healthz",
      "port": 1,
      "timeout_seconds": 10,
      "interval_seconds": 10,
      "start_timeout_seconds": 300
    },
    "resources": {
      "cpu": 1,
      "memory_mb": 1024
    },
    "replicas": {
      "min": 1,
      "max": 1,
      "scale_on": {
        "cpu_percent": 10,
        "memory_percent": 10,
        "concurrency": 1
      }
    },
    "sleep_when_idle": false,
    "private": false,
    "cron": {
      "schedule": "0 2 * * *",
      "timezone": "Europe/London",
      "timeout_seconds": 1,
      "concurrency_policy": "forbid"
    },
    "restart_policy": {
      "mode": "on_failure",
      "max_retries": 10
    },
    "deploy": {
      "overlap_seconds": 20,
      "auto_rollback": false
    },
    "volumes": [
      {
        "volume": "string",
        "mount_path": "/data"
      }
    ]
  }
}'
```

**Example response**

```json
{
  "service_id": "string",
  "environment_id": "string",
  "name": "string",
  "kind": "web",
  "status": "no_deployment",
  "status_text": "Live. 2 of 2 copies healthy.",
  "effective": {
    "source": {
      "kind": "git",
      "repo": "string",
      "root": "apps/web",
      "watch": [
        "apps/web/**",
        "packages/ui/**",
        "pnpm-lock.yaml"
      ],
      "image": "ghcr.io/acme/api:1.4.2",
      "registry_credentials_secret": "string"
    },
    "build": {
      "builder": "railpack",
      "dockerfile": "services/api/Dockerfile",
      "context": "string"
    },
    "start": "pnpm --filter web start",
    "pre_deploy": "pnpm --filter db migrate deploy",
    "port": 3000,
    "healthcheck": {
      "type": "http",
      "path": "/healthz",
      "port": 1,
      "timeout_seconds": 10,
      "interval_seconds": 10,
      "start_timeout_seconds": 300
    },
    "resources": {
      "cpu": 1,
      "memory_mb": 1024
    },
    "replicas": {
      "min": 1,
      "max": 1,
      "scale_on": {
        "cpu_percent": 10,
        "memory_percent": 10,
        "concurrency": 1
      }
    },
    "sleep_when_idle": false,
    "private": false,
    "cron": {
      "schedule": "0 2 * * *",
      "timezone": "Europe/London",
      "timeout_seconds": 1,
      "concurrency_policy": "forbid"
    },
    "restart_policy": {
      "mode": "on_failure",
      "max_retries": 10
    },
    "deploy": {
      "overlap_seconds": 20,
      "auto_rollback": false
    },
    "volumes": [
      {
        "volume": "string",
        "mount_path": "/data"
      }
    ]
  },
  "overrides": {
    "source": {
      "kind": "git",
      "repo": "string",
      "root": "apps/web",
      "watch": [
        "apps/web/**",
        "packages/ui/**",
        "pnpm-lock.yaml"
      ],
      "image": "ghcr.io/acme/api:1.4.2",
      "registry_credentials_secret": "string"
    },
    "build": {
      "builder": "railpack",
      "dockerfile": "services/api/Dockerfile",
      "context": "string"
    },
    "start": "pnpm --filter web start",
    "pre_deploy": "pnpm --filter db migrate deploy",
    "port": 3000,
    "healthcheck": {
      "type": "http",
      "path": "/healthz",
      "port": 1,
      "timeout_seconds": 10,
      "interval_seconds": 10,
      "start_timeout_seconds": 300
    },
    "resources": {
      "cpu": 1,
      "memory_mb": 1024
    },
    "replicas": {
      "min": 1,
      "max": 1,
      "scale_on": {
        "cpu_percent": 10,
        "memory_percent": 10,
        "concurrency": 1
      }
    },
    "sleep_when_idle": false,
    "private": false,
    "cron": {
      "schedule": "0 2 * * *",
      "timezone": "Europe/London",
      "timeout_seconds": 1,
      "concurrency_policy": "forbid"
    },
    "restart_policy": {
      "mode": "on_failure",
      "max_retries": 10
    },
    "deploy": {
      "overlap_seconds": 20,
      "auto_rollback": false
    },
    "volumes": [
      {
        "volume": "string",
        "mount_path": "/data"
      }
    ]
  },
  "current_deployment": {
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
    "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
    "service_id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
    "service_name": "web",
    "environment_name": "production",
    "status": "active",
    "status_text": "Live since 14:05",
    "trigger": {
      "kind": "git_push",
      "actor": {
        "type": "github",
        "id": "stewartwebb",
        "display": "stewartwebb pushed to main"
      }
    },
    "source": {
      "kind": "git",
      "commit": {
        "sha": "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678",
        "branch": "main",
        "message": "Fix rota export",
        "author": "Stewart Webb"
      }
    },
    "build_id": "bld_01jbx2m9k7q4r8t5v6w3y0z1bd",
    "image": {
      "digest": "sha256:4b1f0e9d8c7b6a5948372615f4e3d2c1b0a9f8e7d6c5b4a3928170f6e5d4c3b2",
      "signed": true
    },
    "rollback_of": null,
    "operation_id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "url": "https://www.primecarers.co.uk",
    "failure": null,
    "created_at": "2026-10-23T14:02:00Z",
    "started_at": "2026-10-23T14:02:01Z",
    "active_at": "2026-10-23T14:05:12Z",
    "finished_at": "2026-10-23T14:05:12Z",
    "duration_ms": 191000
  },
  "urls": {
    "public": [
      "https://web-primecarers-k7q4.guv.run",
      "https://www.primecarers.co.uk"
    ],
    "internal_host": "web.guv.internal"
  },
  "replicas": {
    "desired": 1,
    "running": 1
  },
  "last_error": {
    "message": "string",
    "explanation_url": "string"
  },
  "cost_today": {
    "amount": "7.42",
    "currency": "GBP",
    "display": "£7.42"
  },
  "updated_at": "2026-10-22T13:05:00Z"
}
```

## Restart the running deployment

```http
POST /v1/projects/{project}/environments/{environment}/services/{service}/restart
```

- **Operation:** `restartService`
- **Scopes:** `deploy:{environment}`
- **CLI:** `guv restart`
- **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). |
| `environment` * | path | string | Environment id (`env_…`) or name. |
| `service` * | path | string | Service id (`svc_…`) or name. |

**Request body** (`application/json`, required): [ServiceRestart](/docs/api/objects/service-restart/)

| Field | Type | Description |
| --- | --- | --- |
| `reason` | string |  |

**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/environments/production/services/web/restart \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "string"
}'
```

**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"
  }
}
```
