---
title: "Drive a blue/green rollout (platform admin)"
method: POST
path: "/v1/admin/inference/deployments/{id}/rollout"
tags: ["Internal"]
---

# Drive a blue/green rollout (platform admin)

`POST /v1/admin/inference/deployments/{id}/rollout`

The rollout control verb, targeting the lane's green. pause freezes the ramp at its current share while leaving traffic alone; resume clears it. rollback marks the green's backend unhealthy, which removes it from routing entirely. cutover arms a supervised override of the capacity ceiling and the parity bar (readiness, the per-pass step limit and the two-pass separation are never overridden), requires a reason and a typed confirmation, and expires. finalize is valid once the lane has cut over: it drains the demoted blues and returns the green to an ordinary independent deployment, which also ends the instant-rollback window. Every action is recorded in the admin audit log with the actor and reason. All of these are rejected on a deployment that is not a rolling green, so they can never touch an independent deployment by accident.

## Path parameters

- `id` string, required

## Request body

- ServingRolloutActionRequest
  - `action` 'pause' | 'resume' | 'rollback' | 'cutover' | 'finalize', required — pause freezes the ramp at its current share without touching traffic. resume clears the freeze. rollback marks the green's backend unhealthy, which is the one true rollback lever: weight 0 keeps a backend as the overflow tail and would route to the broken version exactly when the lane saturates. cutover arms a supervised force of the capacity gates. finalize drains the demoted blues and returns the lane to steady state.
  - `confirm` string — Typed confirmation for the actions that cannot be undone cheaply (cutover, finalize): must equal the deployment id.
  - `reason` string — Why. Required for cutover, recorded in the admin audit log with the actor for every action.

## Response `200`

The refreshed green

- ServingDeployment — A self-hosted serving deployment: the durable intent to run N replicas of an inference engine for one public catalog model, plus the runtime state the reconcile loop maintains while converging on it.
  - `backendCatalogWeight` integer — Live catalog routing weight of this deployment's backend. Only present when backendRegistered is true. May differ from backendWeight (the row mirror) while a rollout ramp is in flight.
  - `backendHealthy` boolean — Live catalog health of this deployment's backend (the kill switch). Only present when backendRegistered is true.
  - `backendRegistered` boolean — Whether this deployment's pool:// backend is currently present on the catalog model row. Absent when the live catalog state could not be read; false until the first replica passes readiness.
  - `backendVersion` string — The rollout tag this deployment's backend reports to the gateway as the `version` metric label on ig_model_requests_total and ig_stream_ttft_seconds. Use it to separate the two sides of a rollout in a dashboard query. Only present when backendRegistered is true.
  - `backendWeight` integer, required — Routing weight of the catalog backend. 0 = standby (blue/green starts here)
  - `changeCause` string — Operator-supplied reason for the revision that created this deployment
  - `createdAt` string, required
  - `dtype` string
  - `engine` string, required — Inference engine, e.g. vllm or sglang
  - `engineImage` string — Engine container image override; absent = the engine's default
  - `env` object
  - `extraArgs` string[]
  - `generation` integer — 1 for a hand-created deployment, parent+1 for each revision. History ordering only: the controller never reads it, because rollout adoption is the explicit rolloutRole opt-in and nothing else.
  - `gpuCountPerReplica` integer, required
  - `gpuModelId` string, required
  - `id` string, required
  - `isolated` boolean, required — True when replicas and their private endpoint pool are for direct operator testing only. Isolated deployments never register a catalog backend, never participate in blue/green rollout, and cannot receive gateway traffic.
  - `maxConcurrencyPerReplica` integer
  - `maxModelLen` integer
  - `maxPromptBytes` integer — Exclusive text-request byte ceiling for routing eligibility; 0 or absent = unbounded. Routing hint only, not a context limit.
  - `minPromptBytes` integer — Inclusive text-request byte floor for routing eligibility; 0 or absent = unbounded. Routing hint only, not a context limit.
  - `nativeFamilies` string[]
  - `nodePool` string — Stable logical capacity lane within the public model. Blue/green only replaces versions in the same node pool; empty preserves legacy model-wide grouping.
  - `organizationId` string, required — Org that owns the managed replicas (and their metering)
  - `parentDeploymentId` string — The deployment this one was revised from. Set only by /revise, so an absent value means this deployment was created by hand.
  - `poolId` string — Endpoint pool the deployment's replicas register in
  - `publicModelId` string, required — Catalog model this deployment serves capacity for
  - `quantization` string
  - `readyReplicas` integer, required
  - `replicaRuntime` '' | 'vm' | 'pod' — How each replica is placed. Absent or "" or "vm" = a QEMU/VFIO VM on the baremetal fleet (the legacy shape; every pre-existing deployment reads as this). "pod" = a hardened GPU pod on a provider-onboarded runc node. One deployment is one runtime; a public model mixes runtimes through lanes.
  - `replicaVmIds` string[]
  - `replicasDesired` integer, required
  - `replicasMax` integer, required
  - `replicasMin` integer, required
  - `rollout` ServingRolloutState — DERIVED rollout state for this deployment's lane (public model + node pool). Computed by the same code that drives the rollout, so an operator surface can never narrate a different story than the controller will execute. Absent when the deployment is isolated, terminal, or the live catalog could not be read. forced / forceExpired / forcedBy / forcedReason are the ONLY read of a force-cutover: the arm expires, so the raw stamp on the row answers "is a force in effect" wrong roughly as often as it answers it right, and it is deliberately not on the wire.
    - `bluePoolPolicy` string — Member-selection policy on the blue side's endpoint pool. Absent when the lane has no blue with a pool.
    - `blueSlots` integer — The blues' combined ready serving capacity, same unit
    - `cutoverReady` boolean — Whether green has the capacity parity cutover requires (>=95% of blue's slots)
    - `forceExpired` boolean — Force-cutover was armed but its window has elapsed, so it is inert. Set instead of forced, so a stale arm reads as expired rather than as an override nobody notices is still in effect.
    - `forced` boolean — Force-cutover is armed AND still inside its window, so the slot-derived gates (the weight ceiling and the parity bar) are being overridden this pass. Readiness, the step limit, and the two-pass separation are never overridden.
    - `forcedBy` string — Actor who armed force-cutover
    - `forcedReason` string — Why force-cutover was armed
    - `greenPoolPolicy` string — Member-selection policy on the green's endpoint pool
    - `greenSlots` integer — Green's ready serving capacity: readyReplicas x maxConcurrencyPerReplica
    - `lane` string, required — The lane's label, publicModelId/nodePool
    - `laneMemberIds` string[] — Every live deployment in this lane, oldest first
    - `laneWeight` integer — The lane's full weight: the largest weight any blue carries
    - `nextAction` string — One sentence naming what the controller will do next and why, written by the deciding code itself.
    - `paused` boolean — The green row carries an operator pause. The ramp is frozen at its current share; traffic is untouched.
    - `peerId` string — The other side of the roll: for a green, the newest blue it replaces; for a blue, the green replacing it. Absent when the lane is not rolling.
    - `phase` 'none' | 'waiting_for_replicas' | 'ramping' | 'capacity_hold' | 'at_full_weight' | 'cut_over' | 'rolled_back' | 'paused', required — Where a roll is. capacity_hold is the state the capacity guard produces: green is ramped as far as its serving capacity justifies and will not advance until it is scaled up. paused is an operator hold: the ramp is frozen at its current share and traffic is untouched, which is categorically weaker than rolled_back.
    - `policyMismatch` boolean — The two sides run different member-selection policies, so the rollout is not a controlled comparison: whatever the new version measures includes the balancing change.
    - `role` 'independent' | 'blue' | 'green', required — A deployment's part in its lane RIGHT NOW. independent = carries its own weight and is not in a rollout (also what every lane member is when no green exists). blue = an older member a green is ramping against. green = the opted-in successor.
    - `weightCeiling` integer — The highest weight green may carry right now under the capacity guard (its share of the lane's ready serving slots, scaled onto laneWeight).
  - `rolloutPaused` boolean — True while an operator has paused the ramp. Traffic is untouched: the deployment keeps the weight it holds and keeps serving, and the controller takes no rollout action until resume. The pause is also on rollout.paused; this raw column is what a surface falls back to when the lane could not be read at all.
  - `rolloutRole` '' | 'green' — Opt-in blue/green role. Empty (default) = an INDEPENDENT weighted capacity member of its lane, never blue or green. "green" = the lane's successor generation: the controller ramps it against the older members under a capacity guard and cuts them over once it reaches capacity parity. Rollouts are never implicit.
  - `servedModelId` string, required — Model id the engine serves; must equal the catalog backend ref
  - `status` 'pending' | 'deploying' | 'ready' | 'degraded' | 'scaling' | 'draining' | 'stopped' | 'failed', required
  - `statusReason` string — Human-readable cause for degraded/failed states
  - `updatedAt` string, required
  - `version` string — Generation marker for blue/green within one public model and node pool
  - `weightsRef` string — Staged-checkpoint reference; absent = the engine loads from its own source

## Other responses

- `400` — The request is invalid
- `401` — Missing or invalid API key
- `403` — API key lacks the required scope
- `404` — Resource not found
- `409` — The request conflicts with existing state
- `503` — A required integration (e.g. payments) is not configured

---

[API](https://skmtc.net/openrelay/apis/openrelay-api.md) · [All operations](https://skmtc.net/openrelay/apis/openrelay-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/openrelay/openrelay-api/revisions/ed76b41e8006/schema)
