# Services

### List the services in an environment

`GET /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services`

**Required role:** reader

**Parameters**

- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Returns**

- `200` · `ServiceDto[]`

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services \
  -H "Authorization: Bearer kc_your_api_token"
```

**Example response** (`200`)

```json
[
  {
    "id": "svc_01example0000000000000000x",
    "name": "my-app",
    "type": "web",
    "cpu": 1,
    "memory": 1,
    "exposed": true,
    "source": {
      "type": "git",
      "git": {
        "provider": "github",
        "repoUrl": "https://example.com",
        "branch": "example",
        "autoDeploy": true
      },
      "image": {
        "ref": "example"
      }
    },
    "imagePullCredentialsSet": true,
    "runtime": {
      "port": 8080,
      "protocol": "http",
      "healthCheckPath": "example",
      "entrypoint": {
        "command": "example",
        "args": [
          "example"
        ]
      }
    },
    "serverless": {
      "enabled": true
    },
    "cron": {
      "enabled": true,
      "schedule": "example"
    },
    "volume": {
      "id": "svc_01example0000000000000000x",
      "mountPath": "example",
      "sizeGb": 0,
      "name": "my-app"
    },
    "locations": [
      {
        "name": "my-app",
        "replicas": 1
      }
    ],
    "domains": [
      {
        "hostname": "example",
        "mode": "cname",
        "status": "pending",
        "statusReason": "example",
        "dnsRecords": [
          {
            "type": "TXT",
            "name": "my-app",
            "value": "example"
          }
        ]
      }
    ],
    "status": {
      "applied": "pending",
      "build": "pending",
      "health": "starting",
      "errors": [
        {
          "source": "applied",
          "code": "dns_propagating",
          "resource": "example",
          "message": "example",
          "at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "warnings": [
        {
          "source": "applied",
          "code": "dns_propagating",
          "resource": "example",
          "message": "example",
          "at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "url": "https://example.com",
      "lastDeployment": {
        "id": "svc_01example0000000000000000x",
        "version": 3
      },
      "appliedAt": "2026-01-01T00:00:00.000Z",
      "statusObservedAt": "2026-01-01T00:00:00.000Z"
    },
    "internalHost": "example",
    "connection": {
      "key": "example"
    }
  }
]
```

### Create a service in an environment

`POST /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services`

**Required role:** editor

Creates the service. A deployable web service (git/image source) starts rolling out immediately — create is deploy.

**Parameters**

- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Request body**

- `name` · `string` · required — Service name — lowercase letters, digits and dashes (3–16 chars, starts with a letter). The name is the service identity within the environment: renaming creates a new service.
- `type` · `"web" | "postgres" | "redis"` · required — Service kind: `web` runs your own code; `postgres`/`redis` are managed addons. Immutable per name.
- `cpu` · `integer` · required — CPU limit in millicores (250, 500, or whole-vCPU multiples of 1000).
- `memory` · `integer` · required — Memory limit in MiB (256, 512, or whole-GiB multiples of 1024).
- `exposed` · `boolean` · optional — Expose the service publicly with its own URL (web services only).
- `addonCredentials` · `object` · optional — Optional credential overrides for a managed database addon (postgres/redis), applied at create time. Ignored for web services.
- `imageCredentials` · `object` · optional — Registry credentials for a private image source (requires an image source with a ref). Write-only — a read returns only `imagePullCredentialsSet`.
- `variables` · `string[]` · optional — Variables to apply atomically with the service create (same shape as POST …/variables).
- `source` · `object` · optional — Where the deployable artifact comes from.
- `runtime` · `object` · optional — Runtime configuration (port, protocol, health check, entrypoint). Variables are managed separately via the /variables endpoints.
- `serverless` · `object` · optional — Run as a scale-to-zero serverless service (web only; mutually exclusive with cron/volume).
- `cron` · `object` · optional — Run as a scheduled cron job (web only; mutually exclusive with serverless/volume).
- `volume` · `object` · optional — Attach, create, or detach a persistent volume (web only; mutually exclusive with serverless/cron).
- `locations` · `object[]` · optional — Locations this service runs in, each with its replica count.
- `domains` · `string[]` · optional — The full desired set of custom hostnames routed to this exposed service. Omit to leave unchanged; send `[]` to remove all. Re-read the service to see each hostname’s DNS records + live status.

**Returns**

- `201` · `ServiceDto`

Attributes of `ServiceDto`:

- `id` · `string` · read-only — Unique identifier of the service (read-only).
- `name` · `string` · required — Service name — lowercase letters, digits and dashes (3–16 chars, starts with a letter). The name is the service identity within the environment: renaming creates a new service.
- `type` · `"web" | "postgres" | "redis"` · required — Service kind: `web` runs your own code; `postgres`/`redis` are managed addons.
- `cpu` · `integer` · required — CPU limit in millicores (250, 500, or whole-vCPU multiples of 1000).
- `memory` · `integer` · required — Memory limit in MiB (256, 512, or whole-GiB multiples of 1024).
- `exposed` · `boolean` · required — Whether the service is publicly reachable with its own URL (web services only).
- `source` · `object` · optional — Where the deployable artifact comes from.
- `imagePullCredentialsSet` · `boolean` · read-only — Read-only: true when private-image registry credentials are stored for this service. The credentials themselves are never returned.
- `runtime` · `object` · optional — Runtime configuration (port, protocol, health check, entrypoint). Variables are managed separately.
- `serverless` · `object` · optional — Present when the service runs in scale-to-zero serverless mode.
- `cron` · `object` · optional — Present when the service runs as a scheduled cron job.
- `volume` · `object` · optional — Present when the service has an attached persistent volume.
- `locations` · `object[]` · required — Locations this service runs in, each with its replica count.
- `domains` · `object[]` · required — Read-only: the custom domains routing to this service, with DNS records + live status.
- `status` · `object` · read-only — Read-only: live status of the service.
- `internalHost` · `string` · read-only — Read-only: the internal DNS host other services reach this one at, for service-to-service wiring.
- `connection` · `Record<string, string>` · read-only — Read-only: non-secret connection details for a managed addon (postgres/redis).

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services \
  -X POST \
  -H "Authorization: Bearer kc_your_api_token" \
  -H "Content-Type: application/json" \
  -d '{
      "name": "my-app",
      "type": "web",
      "cpu": 1,
      "memory": 1
    }'
```

**Example response** (`201`)

```json
{
  "id": "svc_01example0000000000000000x",
  "name": "my-app",
  "type": "web",
  "cpu": 1,
  "memory": 1,
  "exposed": true,
  "source": {
    "type": "git",
    "git": {
      "provider": "github",
      "repoUrl": "https://example.com",
      "branch": "example",
      "autoDeploy": true
    },
    "image": {
      "ref": "example"
    }
  },
  "imagePullCredentialsSet": true,
  "runtime": {
    "port": 8080,
    "protocol": "http",
    "healthCheckPath": "example",
    "entrypoint": {
      "command": "example",
      "args": [
        "example"
      ]
    }
  },
  "serverless": {
    "enabled": true
  },
  "cron": {
    "enabled": true,
    "schedule": "example"
  },
  "volume": {
    "id": "svc_01example0000000000000000x",
    "mountPath": "example",
    "sizeGb": 0,
    "name": "my-app"
  },
  "locations": [
    {
      "name": "my-app",
      "replicas": 1
    }
  ],
  "domains": [
    {
      "hostname": "example",
      "mode": "cname",
      "status": "pending",
      "statusReason": "example",
      "dnsRecords": [
        {
          "type": "TXT",
          "name": "my-app",
          "value": "example"
        }
      ]
    }
  ],
  "status": {
    "applied": "pending",
    "build": "pending",
    "health": "starting",
    "errors": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "warnings": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "url": "https://example.com",
    "lastDeployment": {
      "id": "svc_01example0000000000000000x",
      "version": 3
    },
    "appliedAt": "2026-01-01T00:00:00.000Z",
    "statusObservedAt": "2026-01-01T00:00:00.000Z"
  },
  "internalHost": "example",
  "connection": {
    "key": "example"
  }
}
```

### Get a service

`GET /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}`

**Required role:** reader

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Returns**

- `200` · `ServiceDto`

Attributes of `ServiceDto`:

- `id` · `string` · read-only — Unique identifier of the service (read-only).
- `name` · `string` · required — Service name — lowercase letters, digits and dashes (3–16 chars, starts with a letter). The name is the service identity within the environment: renaming creates a new service.
- `type` · `"web" | "postgres" | "redis"` · required — Service kind: `web` runs your own code; `postgres`/`redis` are managed addons.
- `cpu` · `integer` · required — CPU limit in millicores (250, 500, or whole-vCPU multiples of 1000).
- `memory` · `integer` · required — Memory limit in MiB (256, 512, or whole-GiB multiples of 1024).
- `exposed` · `boolean` · required — Whether the service is publicly reachable with its own URL (web services only).
- `source` · `object` · optional — Where the deployable artifact comes from.
- `imagePullCredentialsSet` · `boolean` · read-only — Read-only: true when private-image registry credentials are stored for this service. The credentials themselves are never returned.
- `runtime` · `object` · optional — Runtime configuration (port, protocol, health check, entrypoint). Variables are managed separately.
- `serverless` · `object` · optional — Present when the service runs in scale-to-zero serverless mode.
- `cron` · `object` · optional — Present when the service runs as a scheduled cron job.
- `volume` · `object` · optional — Present when the service has an attached persistent volume.
- `locations` · `object[]` · required — Locations this service runs in, each with its replica count.
- `domains` · `object[]` · required — Read-only: the custom domains routing to this service, with DNS records + live status.
- `status` · `object` · read-only — Read-only: live status of the service.
- `internalHost` · `string` · read-only — Read-only: the internal DNS host other services reach this one at, for service-to-service wiring.
- `connection` · `Record<string, string>` · read-only — Read-only: non-secret connection details for a managed addon (postgres/redis).

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name \
  -H "Authorization: Bearer kc_your_api_token"
```

**Example response** (`200`)

```json
{
  "id": "svc_01example0000000000000000x",
  "name": "my-app",
  "type": "web",
  "cpu": 1,
  "memory": 1,
  "exposed": true,
  "source": {
    "type": "git",
    "git": {
      "provider": "github",
      "repoUrl": "https://example.com",
      "branch": "example",
      "autoDeploy": true
    },
    "image": {
      "ref": "example"
    }
  },
  "imagePullCredentialsSet": true,
  "runtime": {
    "port": 8080,
    "protocol": "http",
    "healthCheckPath": "example",
    "entrypoint": {
      "command": "example",
      "args": [
        "example"
      ]
    }
  },
  "serverless": {
    "enabled": true
  },
  "cron": {
    "enabled": true,
    "schedule": "example"
  },
  "volume": {
    "id": "svc_01example0000000000000000x",
    "mountPath": "example",
    "sizeGb": 0,
    "name": "my-app"
  },
  "locations": [
    {
      "name": "my-app",
      "replicas": 1
    }
  ],
  "domains": [
    {
      "hostname": "example",
      "mode": "cname",
      "status": "pending",
      "statusReason": "example",
      "dnsRecords": [
        {
          "type": "TXT",
          "name": "my-app",
          "value": "example"
        }
      ]
    }
  ],
  "status": {
    "applied": "pending",
    "build": "pending",
    "health": "starting",
    "errors": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "warnings": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "url": "https://example.com",
    "lastDeployment": {
      "id": "svc_01example0000000000000000x",
      "version": 3
    },
    "appliedAt": "2026-01-01T00:00:00.000Z",
    "statusObservedAt": "2026-01-01T00:00:00.000Z"
  },
  "internalHost": "example",
  "connection": {
    "key": "example"
  }
}
```

### Update a service spec

`PATCH /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}`

**Required role:** editor

A deployable source change rolls out automatically.

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Request body**

- `cpu` · `integer` · optional — CPU limit in millicores (250, 500, or whole-vCPU multiples of 1000).
- `memory` · `integer` · optional — Memory limit in MiB (256, 512, or whole-GiB multiples of 1024).
- `exposed` · `boolean` · optional — Expose the service publicly with its own URL (web services only).
- `confirmDataLoss` · `boolean` · optional — Acknowledge a change that loses access to persistent-volume data: moving a volume service to a different location (destroys the disk), or attaching an existing volume to a service running in a different location than the disk (mounts a new, empty disk — the data stays with the disk). Required for those specific changes.
- `confirmAppImpact` · `boolean` · optional — Acknowledge that unexposing this service degrades an active published app that points at it (the listing loses its Visit URL). Required for that specific change.
- `imageCredentials` · `object | null` · optional — Credentials for pulling a private container image (write-only).
- `source` · `object` · optional — Where the deployable artifact comes from.
- `runtime` · `object` · optional — Runtime configuration (port, protocol, health check, entrypoint). Variables are managed separately via the /variables endpoints.
- `serverless` · `object` · optional — Run as a scale-to-zero serverless service (web only; mutually exclusive with cron/volume).
- `cron` · `object` · optional — Run as a scheduled cron job (web only; mutually exclusive with serverless/volume).
- `volume` · `object` · optional — Attach, create, or detach a persistent volume (web only; mutually exclusive with serverless/cron).
- `locations` · `object[]` · optional — Locations this service runs in, each with its replica count.
- `domains` · `string[]` · optional — The full desired set of custom hostnames routed to this exposed service. Omit to leave unchanged; send `[]` to remove all. Re-read the service to see each hostname’s DNS records + live status.

**Returns**

- `200` · `ServiceDto`

Attributes of `ServiceDto`:

- `id` · `string` · read-only — Unique identifier of the service (read-only).
- `name` · `string` · required — Service name — lowercase letters, digits and dashes (3–16 chars, starts with a letter). The name is the service identity within the environment: renaming creates a new service.
- `type` · `"web" | "postgres" | "redis"` · required — Service kind: `web` runs your own code; `postgres`/`redis` are managed addons.
- `cpu` · `integer` · required — CPU limit in millicores (250, 500, or whole-vCPU multiples of 1000).
- `memory` · `integer` · required — Memory limit in MiB (256, 512, or whole-GiB multiples of 1024).
- `exposed` · `boolean` · required — Whether the service is publicly reachable with its own URL (web services only).
- `source` · `object` · optional — Where the deployable artifact comes from.
- `imagePullCredentialsSet` · `boolean` · read-only — Read-only: true when private-image registry credentials are stored for this service. The credentials themselves are never returned.
- `runtime` · `object` · optional — Runtime configuration (port, protocol, health check, entrypoint). Variables are managed separately.
- `serverless` · `object` · optional — Present when the service runs in scale-to-zero serverless mode.
- `cron` · `object` · optional — Present when the service runs as a scheduled cron job.
- `volume` · `object` · optional — Present when the service has an attached persistent volume.
- `locations` · `object[]` · required — Locations this service runs in, each with its replica count.
- `domains` · `object[]` · required — Read-only: the custom domains routing to this service, with DNS records + live status.
- `status` · `object` · read-only — Read-only: live status of the service.
- `internalHost` · `string` · read-only — Read-only: the internal DNS host other services reach this one at, for service-to-service wiring.
- `connection` · `Record<string, string>` · read-only — Read-only: non-secret connection details for a managed addon (postgres/redis).

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name \
  -X PATCH \
  -H "Authorization: Bearer kc_your_api_token" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**Example response** (`200`)

```json
{
  "id": "svc_01example0000000000000000x",
  "name": "my-app",
  "type": "web",
  "cpu": 1,
  "memory": 1,
  "exposed": true,
  "source": {
    "type": "git",
    "git": {
      "provider": "github",
      "repoUrl": "https://example.com",
      "branch": "example",
      "autoDeploy": true
    },
    "image": {
      "ref": "example"
    }
  },
  "imagePullCredentialsSet": true,
  "runtime": {
    "port": 8080,
    "protocol": "http",
    "healthCheckPath": "example",
    "entrypoint": {
      "command": "example",
      "args": [
        "example"
      ]
    }
  },
  "serverless": {
    "enabled": true
  },
  "cron": {
    "enabled": true,
    "schedule": "example"
  },
  "volume": {
    "id": "svc_01example0000000000000000x",
    "mountPath": "example",
    "sizeGb": 0,
    "name": "my-app"
  },
  "locations": [
    {
      "name": "my-app",
      "replicas": 1
    }
  ],
  "domains": [
    {
      "hostname": "example",
      "mode": "cname",
      "status": "pending",
      "statusReason": "example",
      "dnsRecords": [
        {
          "type": "TXT",
          "name": "my-app",
          "value": "example"
        }
      ]
    }
  ],
  "status": {
    "applied": "pending",
    "build": "pending",
    "health": "starting",
    "errors": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "warnings": [
      {
        "source": "applied",
        "code": "dns_propagating",
        "resource": "example",
        "message": "example",
        "at": "2026-01-01T00:00:00.000Z"
      }
    ],
    "url": "https://example.com",
    "lastDeployment": {
      "id": "svc_01example0000000000000000x",
      "version": 3
    },
    "appliedAt": "2026-01-01T00:00:00.000Z",
    "statusObservedAt": "2026-01-01T00:00:00.000Z"
  },
  "internalHost": "example",
  "connection": {
    "key": "example"
  }
}
```

### Delete a service

`DELETE /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}`

**Required role:** editor

Deleting a service that backs an active published app degrades its listing; pass confirmAppImpact to proceed (412 otherwise).

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Request body**

- `confirmAppImpact` · `boolean` · optional — Must be true to proceed when this service backs an active published app: deleting it leaves the listing degraded.

**Returns**

- `204`

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name \
  -X DELETE \
  -H "Authorization: Bearer kc_your_api_token" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Reveal a managed service’s connection details (incl. password)

`GET /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}/connection`

**Required role:** editor

The host/port/user/db + the connection URL and password for a postgres/redis service. Editor+ and audited; web services have no connection details (422).

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Returns**

- `200` · `ServiceConnectionDto`

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name/connection \
  -H "Authorization: Bearer kc_your_api_token"
```

**Example response** (`200`)

```json
{
  "key": "example"
}
```

### Get the data to open an interactive shell into a running replica

`GET /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}/exec-connection`

**Required role:** editor

Returns everything the browser needs to open an interactive shell DIRECTLY against the replica’s terminal WebSocket — there is no Koo-side proxy: the per-location endpoint, the opaque connection coordinates the client passes through in the handshake, container choices, and a connect-only per-environment token (minted lazily; rotated on membership changes). List replicas via exec-replicas. Editor+ and audited; not available to OAuth authorizations.

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Returns**

- `200` · `ExecConnectionDto`

Attributes of `ExecConnectionDto`:

- `location` · `string` · read-only — The location whose replicas the shell targets.
- `containers` · `string[]` · read-only — Container names in the service (the shell target choices).
- `remote` · `string` · read-only — The per-location terminal endpoint (https://…) — the browser opens the shell WebSocket at wss://{host}/remote.
- `org` · `string` · read-only — Opaque account-level connection coordinate — pass it through verbatim in the terminal handshake.
- `gvc` · `string` · read-only — Opaque environment-level connection coordinate — pass it through verbatim in the terminal handshake.
- `workload` · `string` · read-only — Opaque service-level connection coordinate — pass it through verbatim in the terminal handshake.
- `token` · `string` · read-only — A connect-only credential scoped to this environment — sent inside the binary exec-WebSocket handshake (never in the URL). Minted lazily per environment; revoked and re-minted when a member is removed or downgraded below editor.

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name/exec-connection \
  -H "Authorization: Bearer kc_your_api_token"
```

**Example response** (`200`)

```json
{
  "location": "example",
  "containers": [
    "example"
  ],
  "remote": "example",
  "org": "example",
  "gvc": "example",
  "workload": "example",
  "token": "example"
}
```

### List running replicas for an interactive shell

`GET /accounts/{accountId}/projects/{projectId}/environments/{environmentId}/services/{name}/exec-replicas`

**Required role:** editor

Lists running replicas, resolved server-side from live hosting state. Editor+.

**Parameters**

- `name` _(path)_ · `string` · required — Service name within the environment.
- `environmentId` _(path)_ · `string` · required
- `projectId` _(path)_ · `string` · required
- `accountId` _(path)_ · `string` · required

**Returns**

- `200` · `ExecReplicasDto`

Attributes of `ExecReplicasDto`:

- `items` · `string[]` · read-only — Running replica (pod) names available to open a shell into.

**Example request**

```bash
curl https://api.koo.io/accounts/:accountId/projects/:projectId/environments/:environmentId/services/:name/exec-replicas \
  -H "Authorization: Bearer kc_your_api_token"
```

**Example response** (`200`)

```json
{
  "items": [
    "example"
  ]
}
```
