---
title: "Mint the next generation of a serving deployment (platform admin)"
method: POST
path: "/v1/admin/inference/deployments/{id}/revise"
tags: ["Internal"]
---

# Mint the next generation of a serving deployment (platform admin)

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

Server-side "next generation from this one": every spec field in the body overrides the source, every absent field is inherited, and the result is created as a weight-0 green in the source's lane. Structural overrides (organizationId, publicModelId, nodePool) are rejected: they would make a different lane or tenant. A delta touching only routing or sizing fields is also rejected, naming PATCH or /scale, because no new replicas are needed and a new generation would be waste. The new green and its lane lock are written in one transaction, so two simultaneous revises of one lane cannot both become green. The response carries the field diff and the rollout capacity warning for running both generations at once.

## Path parameters

- `id` string, required

## Request body

- ReviseServingDeploymentRequest — The next generation's spec, expressed as overrides on the source deployment: a field that is present overrides, a field that is absent is inherited. Rollout mechanics (backendWeight, isolated, rolloutRole, acknowledgeLaneJoin) are deliberately absent from this body: the server owns them, and a revision is always a weight-0 green in the source's lane. Structural fields ARE accepted by the schema so the server can reject them by name: changing one would make a different lane or a different tenant, which is a create, not a revision.
  - `changeCause` string — Why this revision exists. Stored on the row and shown in the revision history.
  - `dtype` string
  - `engine` string
  - `engineImage` string
  - `env` object
  - `extraArgs` string[]
  - `gpuCountPerReplica` integer
  - `gpuModelId` string
  - `maxConcurrencyPerReplica` integer
  - `maxModelLen` integer
  - `maxPromptBytes` integer
  - `minPromptBytes` integer
  - `nativeFamilies` string[]
  - `nodePool` string — Structural: rejected. A different node pool is a different lane.
  - `organizationId` string — Structural: rejected. A different org is a different tenant.
  - `publicModelId` string — Structural: rejected. A different model is a different lane.
  - `quantization` string
  - `replicaRuntime` '' | 'vm' | 'pod' — Changing the runtime is a replica-class change like any other, so it rolls out rather than mutating in place.
  - `replicasDesired` integer
  - `replicasMax` integer
  - `replicasMin` integer
  - `servedModelId` string
  - `version` string — Rollout tag for the new generation: the low-cardinality `version` metric label the gateway reports, which is what separates the two sides of the roll. ABSENT CLEARS IT rather than inheriting the source's, because a green carrying blue's tag registers the identical label and collapses both sides into one series. Cleared, the green reports as "green" against blue's "blue". Present sets it explicitly.
  - `weightsRef` string

## Response `200`

The new generation

- ServingRevisionResult — The outcome of a revise: the new generation's deployment row, what changed against its source, and the rollout capacity warning for running both generations at once.
  - `laneCommittedGpus` integer — GPUs the target lane's existing live deployments already commit for this GPU model.
  - `rolloutOk` boolean — Whether rolloutPeakGpus fits the serving budget. A WARNING, not a blocker: running a tight rollout and draining the old side as the new one ramps is a legitimate plan.
  - `rolloutPeakGpus` integer — Peak GPU demand while the rollout is in flight: the new generation's footprint plus laneCommittedGpus, because both generations hold real hardware until the old one is drained.
  - `rolloutReason` string — Why the rollout peak does not fit, in lane terms, when rolloutOk is false
  - `deployment` ServingDeployment, required — 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
  - `diff` ServingSpecDiffEntry[], required — Every field that differs from the source generation
    - `class` 'structural' | 'routing' | 'sizing' | 'replica', required — How a spec field may be changed. structural makes a different lane or tenant and is rejected everywhere. routing is safe to mutate under a running replica (PATCH). sizing is mutable by adding or removing replicas (/scale). replica requires new processes on new hardware, which is the blue/green trigger, and is the default for any field not listed.
    - `field` string, required
    - `from` string — The source generation's value, stringified; absent when unset
    - `to` string — The new generation's value, stringified; absent when unset

## 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/7e2fe85577f4/schema)
