---
id: w2/m12/t001
title: spec.replicas semantics + REST POST /v1/services/{id}/scale
worker: worker1
status: done
estimate: 30m
depends_on: []
---

## Objective

Confirm `spec.replicas` semantics (it exists — `lego/types/v1alpha1/app_types.go`; the operator's `effectiveReplicas` already arbitrates it against `suspended`) and expose Render's manual-scaling verb: `POST /v1/services/{id}/scale`, body `{"numInstances": N}` — shape verified against Render's OpenAPI before coding (status code, response body, bounds).

## Context

- Existing lifecycle verbs pattern: `lego/backend/internal/apps/{service,rest}.go` (suspend/resume/restart, PATCH plan).
- Interaction contract to preserve: `suspended: true` ⇒ 0 pods regardless of replicas; resume returns to `spec.replicas`.
- Validate bounds (N ≥ 1; upper bound from the tier or a sane cap — document the choice).

## Steps

1. Pull Render's scale endpoint shape from `render-public-api-1.json`.
2. Service method `Scale(ctx, id, n)` (authorize first) → spec patch; REST fragment.
3. docs/ADR006-bex-api.md table row + ADR007-restart-suspend-and-resume.md cross-note.

## Files

- `lego/backend/internal/apps/service.go`, `lego/backend/internal/apps/rest.go`
- `docs/ADR006-bex-api.md`

## Acceptance criteria

- [ ] `POST …/scale {"numInstances":3}` patches `spec.replicas: 3` and returns Render's expected status/shape; out-of-bounds N rejected with a clear 400.

## Out of scope

- Autoscaling (m3) and scale-to-zero (m4) — but leave their interaction documented in t003.
