---
title: Versions and builds | Runware Docs
url: https://runware.ai/docs/serverless/api-versions
description: List an app's versions, follow the builds that produced them, and remove the ones no longer needed.
relatedDocuments:
  - https://runware.ai/docs/serverless/api
  - https://runware.ai/docs/serverless/versions
  - https://runware.ai/docs/serverless/deploying
---
## Introduction

Every change to an app records a **version**, including a change that only touches configuration. A version is what a deploy activates, and rolling back is activating an earlier one.

A **build** is the work that produces a version's image. A version names the build it came from in `buildId`, and a version created by a configuration update carries the previous image, and therefore the previous build, forward. That is why an app can end up with more versions than builds.

> [!NOTE]
> Deploying a version is `POST /v1/apps/{appId}/deploy`, documented with the other app operations. This page covers reading and removing them.

## List versions

GETapi.serverless.runware.ai/v1/apps/{appId}/versions

### [Request](#list-versions-request)

Path

### [appId](#list-versions-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

Query

### [limit](#list-versions-query-limit)

- **Type**: `integer`
- **Format**: `int32`
- **Min**: `1`
- **Max**: `100`
- **Default**: `20`

Maximum number of items to return.

### [cursor](#list-versions-query-cursor)

- **Type**: `string`

Opaque pagination cursor returned by a previous call, as `nextCursor` or, on the operations that offer one, `prevCursor`.

### [Response](#list-versions-response)

### [nextCursor](#list-versions-response-nextcursor)

- **Type**: `string (nullable)`

Cursor for the next page. Null when there are no more items.

### [data](#list-versions-response-data)

- **Type**: `object[] (29 properties)`
- **Required**: true

#### [id](#list-versions-response-data-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [appId](#list-versions-response-data-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier. Unique among the authenticated organization's live apps: it cannot be changed after creation, and it becomes available again once the app it named reaches `deleted`.

#### [versionNumber](#list-versions-response-data-versionnumber)

- **Type**: `integer`
- **Required**: true
- **Format**: `int32`

Monotonically increasing per app.

#### [buildId](#list-versions-response-data-buildid)

- **Type**: `string (uuid) (nullable)`
- **Format**: `UUID v4`

The build that produced this version's image. Both source types build. A `code` source bakes an image around a submitted codebase, a `container` source builds the customer's own wrapper Dockerfile. A version created by an update carries the image, and therefore this build, forward.

#### [gpuType](#list-versions-response-data-gputype)

- **Type**: `string (nullable)`
- **Min**: `1`
- **Max**: `64`

Preferred GPU type of this version, taken from the config snapshot.

#### [author](#list-versions-response-data-author)

- **Type**: `object (3 properties, nullable)`

Who created this version: the authenticated user (JWT) or the API key. `displayName` is the name captured at write time.

##### [kind](#list-versions-response-data-author-kind)

- **Type**: `string`
- **Required**: true

Whether the actor is a user (JWT) or an API key.

**Possible values**: `user` `apiKey`

##### [id](#list-versions-response-data-author-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

##### [displayName](#list-versions-response-data-author-displayname)

- **Type**: `string`
- **Required**: true

#### [buildDurationMs](#list-versions-response-data-builddurationms)

- **Type**: `integer (nullable)`
- **Format**: `int64`

Wall-clock milliseconds of the named build (`completedAt - createdAt` on that build). Null while the build is running or when no completion time is available. Bytes and units are not included. This is a raw millisecond count.

#### [changes](#list-versions-response-data-changes)

- **Type**: `object (17 properties, nullable)`

Structured diff against the previous version. Null on version 1.

##### [imageChanged](#list-versions-response-data-changes-imagechanged)

- **Type**: `boolean`
- **Required**: true

True when this version names a different image than the previous one (`buildId` or `imageRef` changed). False for a config-only update, which carries the image forward.

##### [workerConfig](#list-versions-response-data-changes-workerconfig)

- **Type**: `object[] (3 properties)`

##### [field](#list-versions-response-data-changes-workerconfig-field)

- **Type**: `string`
- **Required**: true

##### [from](#list-versions-response-data-changes-workerconfig-from)

- **Type**: `string (nullable)`
- **Required**: true

##### [to](#list-versions-response-data-changes-workerconfig-to)

- **Type**: `string (nullable)`
- **Required**: true

##### [environmentVariables](#list-versions-response-data-changes-environmentvariables)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#list-versions-response-data-changes-environmentvariables-added)

- **Type**: `string[]`

##### [removed](#list-versions-response-data-changes-environmentvariables-removed)

- **Type**: `string[]`

##### [changed](#list-versions-response-data-changes-environmentvariables-changed)

- **Type**: `string[]`

##### [endpoints](#list-versions-response-data-changes-endpoints)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#list-versions-response-data-changes-endpoints-added)

- **Type**: `string[]`

##### [removed](#list-versions-response-data-changes-endpoints-removed)

- **Type**: `string[]`

##### [changed](#list-versions-response-data-changes-endpoints-changed)

- **Type**: `string[]`

##### [volumes](#list-versions-response-data-changes-volumes)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#list-versions-response-data-changes-volumes-added)

- **Type**: `string[]`

##### [removed](#list-versions-response-data-changes-volumes-removed)

- **Type**: `string[]`

##### [changed](#list-versions-response-data-changes-volumes-changed)

- **Type**: `string[]`

#### [createdAt](#list-versions-response-data-createdat)

- **Type**: `string (date-time)`
- **Required**: true
- **Format**: `date-time`

### [summary](#list-versions-response-summary)

- **Type**: `object (1 property)`
- **Required**: true

Collection totals for a paged list. Independent of the page: `total` is the COUNT of items in the collection and is the same value on every page, including a cursor that seeks past the last row.

#### [total](#list-versions-response-summary-total)

- **Type**: `integer`
- **Required**: true
- **Format**: `int64`
- **Min**: `0`

Number of items in the collection this page was drawn from.

### [Errors](#list-versions-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Get a version

GETapi.serverless.runware.ai/v1/apps/{appId}/versions/{versionNumber}

### [Request](#get-version-request)

Path

### [appId](#get-version-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

### [versionNumber](#get-version-path-versionnumber)

- **Type**: `integer`
- **Required**: true
- **Format**: `int32`

### [Response](#get-version-response)

### [id](#get-version-response-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [appId](#get-version-response-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier. Unique among the authenticated organization's live apps: it cannot be changed after creation, and it becomes available again once the app it named reaches `deleted`.

### [versionNumber](#get-version-response-versionnumber)

- **Type**: `integer`
- **Required**: true
- **Format**: `int32`

Monotonically increasing per app.

### [buildId](#get-version-response-buildid)

- **Type**: `string (uuid) (nullable)`
- **Format**: `UUID v4`

The build that produced this version's image. Both source types build. A `code` source bakes an image around a submitted codebase, a `container` source builds the customer's own wrapper Dockerfile. A version created by an update carries the image, and therefore this build, forward.

### [gpuType](#get-version-response-gputype)

- **Type**: `string (nullable)`
- **Min**: `1`
- **Max**: `64`

Preferred GPU type of this version, taken from the config snapshot.

### [author](#get-version-response-author)

- **Type**: `object (3 properties, nullable)`

Who created this version: the authenticated user (JWT) or the API key. `displayName` is the name captured at write time.

#### [kind](#get-version-response-author-kind)

- **Type**: `string`
- **Required**: true

Whether the actor is a user (JWT) or an API key.

**Possible values**: `user` `apiKey`

#### [id](#get-version-response-author-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [displayName](#get-version-response-author-displayname)

- **Type**: `string`
- **Required**: true

### [buildDurationMs](#get-version-response-builddurationms)

- **Type**: `integer (nullable)`
- **Format**: `int64`

Wall-clock milliseconds of the named build (`completedAt - createdAt` on that build). Null while the build is running or when no completion time is available. Bytes and units are not included. This is a raw millisecond count.

### [changes](#get-version-response-changes)

- **Type**: `object (17 properties, nullable)`

Structured diff against the previous version. Null on version 1.

#### [imageChanged](#get-version-response-changes-imagechanged)

- **Type**: `boolean`
- **Required**: true

True when this version names a different image than the previous one (`buildId` or `imageRef` changed). False for a config-only update, which carries the image forward.

#### [workerConfig](#get-version-response-changes-workerconfig)

- **Type**: `object[] (3 properties)`

##### [field](#get-version-response-changes-workerconfig-field)

- **Type**: `string`
- **Required**: true

##### [from](#get-version-response-changes-workerconfig-from)

- **Type**: `string (nullable)`
- **Required**: true

##### [to](#get-version-response-changes-workerconfig-to)

- **Type**: `string (nullable)`
- **Required**: true

#### [environmentVariables](#get-version-response-changes-environmentvariables)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#get-version-response-changes-environmentvariables-added)

- **Type**: `string[]`

##### [removed](#get-version-response-changes-environmentvariables-removed)

- **Type**: `string[]`

##### [changed](#get-version-response-changes-environmentvariables-changed)

- **Type**: `string[]`

#### [endpoints](#get-version-response-changes-endpoints)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#get-version-response-changes-endpoints-added)

- **Type**: `string[]`

##### [removed](#get-version-response-changes-endpoints-removed)

- **Type**: `string[]`

##### [changed](#get-version-response-changes-endpoints-changed)

- **Type**: `string[]`

#### [volumes](#get-version-response-changes-volumes)

- **Type**: `object (3 properties)`

Keys added, removed, or changed between consecutive versions. For environment variables, `changed` is keys whose *value* changed (the value itself is never returned). For endpoints the key is the path. For volumes it is `mountPath`. `changed` is omitted on those two (a path either exists or does not).

##### [added](#get-version-response-changes-volumes-added)

- **Type**: `string[]`

##### [removed](#get-version-response-changes-volumes-removed)

- **Type**: `string[]`

##### [changed](#get-version-response-changes-volumes-changed)

- **Type**: `string[]`

### [createdAt](#get-version-response-createdat)

- **Type**: `string (date-time)`
- **Required**: true
- **Format**: `date-time`

### [Errors](#get-version-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Delete a version

DELETEapi.serverless.runware.ai/v1/apps/{appId}/versions/{versionNumber}

Deletes an unused version while retaining its immutable history. Deleted versions are omitted from version lists, return `404` from version reads, and cannot be deployed. Returns `409` while the app is deleting, or when the version is active, is the app's only remaining version, has a non-stopped worker, or is targeted by a live rollout. Deleting an already deleted version returns `404`. This operation does not remove the version's OCI image.

### [Request](#delete-version-request)

Path

### [appId](#delete-version-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

### [versionNumber](#delete-version-path-versionnumber)

- **Type**: `integer`
- **Required**: true
- **Format**: `int32`

### [Errors](#delete-version-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## List builds

GETapi.serverless.runware.ai/v1/apps/{appId}/builds

> [!NOTE]
> The `summary` counts the app's builds and its versions, whichever page you asked for, so the same two numbers come back on every cursor. A configuration-only update raises `versions` without raising `total`.

### [Request](#list-builds-request)

Path

### [appId](#list-builds-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

Query

### [limit](#list-builds-query-limit)

- **Type**: `integer`
- **Format**: `int32`
- **Min**: `1`
- **Max**: `100`
- **Default**: `20`

Maximum number of items to return.

### [cursor](#list-builds-query-cursor)

- **Type**: `string`

Opaque pagination cursor returned by a previous call, as `nextCursor` or, on the operations that offer one, `prevCursor`.

### [Response](#list-builds-response)

### [nextCursor](#list-builds-response-nextcursor)

- **Type**: `string (nullable)`

Cursor for the next page. Null when there are no more items.

### [data](#list-builds-response-data)

- **Type**: `object[] (22 properties)`
- **Required**: true

#### [id](#list-builds-response-data-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [appId](#list-builds-response-data-appid)

- **Type**: `string`
- **Min**: `6`
- **Max**: `30`

Immutable app identifier. Unique among the authenticated organization's live apps: it cannot be changed after creation, and it becomes available again once the app it named reaches `deleted`.

#### [source](#list-builds-response-data-source)

- **Type**: `object (1 property)`
- **Required**: true

Where a build's source came from: `code` for a customer code submission, `container` for a customer wrapper Dockerfile. `repository` and `revision` are not yet recorded (both sources are zip uploads) and will be added when the platform stores that provenance.

##### [type](#list-builds-response-data-source-type)

- **Type**: `string`
- **Required**: true

The kind of source that produced this build: `code` or `container`, straight from the build record. Build phases differ by kind, so clients must not assume a fixed phase list. Not modeled as an enum so adding a kind later does not churn the existing source-type constants this API shares.

#### [triggeredBy](#list-builds-response-data-triggeredby)

- **Type**: `object (3 properties)`

Who performed an action: the authenticated user (JWT) or the API key. `kind` says which, so `id` (that actor's UUID) is resolvable to the right thing. `displayName` is its name captured at write time, so it renders as it was even if the user or key is later renamed. Used as `Build.triggeredBy` and `Version.author`.

##### [kind](#list-builds-response-data-triggeredby-kind)

- **Type**: `string`
- **Required**: true

Whether the actor is a user (JWT) or an API key.

**Possible values**: `user` `apiKey`

##### [id](#list-builds-response-data-triggeredby-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

##### [displayName](#list-builds-response-data-triggeredby-displayname)

- **Type**: `string`
- **Required**: true

#### [status](#list-builds-response-data-status)

- **Type**: `string`
- **Required**: true

Build/validation lifecycle status.

**Possible values**: `queued` `building` `ready` `failed` `superseded`

#### [versionId](#list-builds-response-data-versionid)

- **Type**: `string (uuid) (nullable)`
- **Format**: `UUID v4`

The earliest version that still references this build. A deleted version does not count. A code build's version is created together with the build, so this is populated in every status. It stays set while any version that has not been deleted references the build, and it is null once none does. A code update carries an image, and so its build, forward onto a new version, so a build may be named by several versions. This is the earliest of those that remain.

#### [versionNumber](#list-builds-response-data-versionnumber)

- **Type**: `integer (nullable)`
- **Format**: `int32`

Version number of `versionId`. Null when `versionId` is null.

#### [error](#list-builds-response-data-error)

- **Type**: `string (nullable)`

Error message or failure reason. Null when the build is still running or succeeded.

#### [exitCode](#list-builds-response-data-exitcode)

- **Type**: `integer (nullable)`
- **Format**: `int32`

Exit code of the build container. 0 only when the build succeeded. On failure, that container's non-zero code. Null when it exited 0 or never ran, and when the builder could no longer read the code. In both of those the failure belongs to another phase, named by `phases` and `logTail`. Null while the build is running.

#### [logTail](#list-builds-response-data-logtail)

- **Type**: `string (nullable)`

The last lines of the build output that belong to the app's own code: what the model file printed and raised when the build imported it, the build's messages about the codebase and its requirements, and the output of the steps that install the declared requirements or, for a container source, run the Dockerfile. Output of the platform's own build steps is never included, on successful and failed builds alike. A failed build's tail always says why. When the platform ended the build, it ends with one line stating the cause: out of memory, an eviction for memory or disk, preemption, a build machine that was drained or became unavailable, its time limit, or a kill the machine gave no reason for; when the build was taken off its machine and nothing is left to say how, the line says that the platform took it off the build machine, as happens on a drain, preemption or a lost machine. This line comes even after output that already explains the failure. Otherwise, when none of that output explains the failure, it ends with one line that names the platform step the build stopped in, or says that the build was stopped, most often at its time limit. When the build stopped during the image build and its output can still be read, a line before the last one names the step it stopped in and says whether the step's command was still running or had finished while the step's files were saved into the image. After the time limit, or when the platform took the build off its machine, the tail keeps the output from while the build ran and adds the cause with no step line. After an eviction for disk, the output is gone and the tail states only the cause. For a container source the step is the Dockerfile instruction as written, shortened when long, and a step the platform adds after the Dockerfile is named only as one; for a code source only the steps that install the declared requirements or check the base image's Python are named. At most 100 lines and 16 KiB; the cause is the last line and stays within those bounds. While the build runs it is the output so far under the same rules, refreshed as the build proceeds, and it carries no line about a failure. Null until the build has written output of that kind.

#### [durationMs](#list-builds-response-data-durationms)

- **Type**: `integer (nullable)`
- **Format**: `int64`

Wall-clock milliseconds from `createdAt` to `completedAt`. Null while the build is still running or when no completion time was recorded.

#### [phases](#list-builds-response-data-phases)

- **Type**: `object[] (4 properties)`
- **Required**: true

Observed build phases with their timings, recorded once the build is terminal. Empty until then. Phase lists differ by `source.type`. Never assume a fixed list.

##### [name](#list-builds-response-data-phases-name)

- **Type**: `string`
- **Required**: true

Phase identifier, e.g. `prepare`, `build`, `index`. Deliberately not an enum. New build kinds report new phases without a contract change.

##### [startedAt](#list-builds-response-data-phases-startedat)

- **Type**: `string (date-time)`
- **Required**: true
- **Format**: `date-time`

##### [completedAt](#list-builds-response-data-phases-completedat)

- **Type**: `string (date-time) (nullable)`
- **Format**: `date-time`

Null while the phase is still running.

##### [outcome](#list-builds-response-data-phases-outcome)

- **Type**: `string`
- **Required**: true

**Possible values**: `running` `succeeded` `failed`

#### [createdAt](#list-builds-response-data-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

#### [completedAt](#list-builds-response-data-completedat)

- **Type**: `string (date-time) (nullable)`
- **Format**: `date-time`

When the build reached a terminal status (`ready`/`failed`/`superseded`). Null while running or for terminal builds recorded before this was tracked.

### [summary](#list-builds-response-summary)

- **Type**: `object (2 properties)`
- **Required**: true

Collection totals for this app's builds list. Independent of the page: the same values on every cursor, including a seek past the last row. `total` counts builds. `versions` counts versions on the same app, the same predicate as `listVersions` `summary.total`, excluding soft-deleted versions.

#### [total](#list-builds-response-summary-total)

- **Type**: `integer`
- **Required**: true
- **Format**: `int64`
- **Min**: `0`

Number of builds recorded for this app.

#### [versions](#list-builds-response-summary-versions)

- **Type**: `integer`
- **Required**: true
- **Format**: `int64`
- **Min**: `0`

Number of versions recorded for this app. Same value as `listVersions` `summary.total`. A config-only update increments this without incrementing `total`.

### [Errors](#list-builds-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Get a build

GETapi.serverless.runware.ai/v1/apps/{appId}/builds/{buildId}

> [!NOTE]
> A build moves through `queued`, `building` and then one of `ready`, `failed` or `superseded`. `error` is set only on `failed`: a `superseded` build was canceled because a newer version took over, which the status already says. The phases a build reports depend on whether it built a codebase or your own Dockerfile, so read them as a list rather than a fixed sequence.

### [Request](#get-build-request)

Path

### [appId](#get-build-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

### [buildId](#get-build-path-buildid)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [Response](#get-build-response)

### [id](#get-build-response-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [appId](#get-build-response-appid)

- **Type**: `string`
- **Min**: `6`
- **Max**: `30`

Immutable app identifier. Unique among the authenticated organization's live apps: it cannot be changed after creation, and it becomes available again once the app it named reaches `deleted`.

### [source](#get-build-response-source)

- **Type**: `object (1 property)`
- **Required**: true

Where a build's source came from: `code` for a customer code submission, `container` for a customer wrapper Dockerfile. `repository` and `revision` are not yet recorded (both sources are zip uploads) and will be added when the platform stores that provenance.

#### [type](#get-build-response-source-type)

- **Type**: `string`
- **Required**: true

The kind of source that produced this build: `code` or `container`, straight from the build record. Build phases differ by kind, so clients must not assume a fixed phase list. Not modeled as an enum so adding a kind later does not churn the existing source-type constants this API shares.

### [triggeredBy](#get-build-response-triggeredby)

- **Type**: `object (3 properties)`

Who performed an action: the authenticated user (JWT) or the API key. `kind` says which, so `id` (that actor's UUID) is resolvable to the right thing. `displayName` is its name captured at write time, so it renders as it was even if the user or key is later renamed. Used as `Build.triggeredBy` and `Version.author`.

#### [kind](#get-build-response-triggeredby-kind)

- **Type**: `string`
- **Required**: true

Whether the actor is a user (JWT) or an API key.

**Possible values**: `user` `apiKey`

#### [id](#get-build-response-triggeredby-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [displayName](#get-build-response-triggeredby-displayname)

- **Type**: `string`
- **Required**: true

### [status](#get-build-response-status)

- **Type**: `string`
- **Required**: true

Build/validation lifecycle status.

**Possible values**: `queued` `building` `ready` `failed` `superseded`

### [versionId](#get-build-response-versionid)

- **Type**: `string (uuid) (nullable)`
- **Format**: `UUID v4`

The earliest version that still references this build. A deleted version does not count. A code build's version is created together with the build, so this is populated in every status. It stays set while any version that has not been deleted references the build, and it is null once none does. A code update carries an image, and so its build, forward onto a new version, so a build may be named by several versions. This is the earliest of those that remain.

### [versionNumber](#get-build-response-versionnumber)

- **Type**: `integer (nullable)`
- **Format**: `int32`

Version number of `versionId`. Null when `versionId` is null.

### [error](#get-build-response-error)

- **Type**: `string (nullable)`

Error message or failure reason. Null when the build is still running or succeeded.

### [exitCode](#get-build-response-exitcode)

- **Type**: `integer (nullable)`
- **Format**: `int32`

Exit code of the build container. 0 only when the build succeeded. On failure, that container's non-zero code. Null when it exited 0 or never ran, and when the builder could no longer read the code. In both of those the failure belongs to another phase, named by `phases` and `logTail`. Null while the build is running.

### [logTail](#get-build-response-logtail)

- **Type**: `string (nullable)`

The last lines of the build output that belong to the app's own code: what the model file printed and raised when the build imported it, the build's messages about the codebase and its requirements, and the output of the steps that install the declared requirements or, for a container source, run the Dockerfile. Output of the platform's own build steps is never included, on successful and failed builds alike. A failed build's tail always says why. When the platform ended the build, it ends with one line stating the cause: out of memory, an eviction for memory or disk, preemption, a build machine that was drained or became unavailable, its time limit, or a kill the machine gave no reason for; when the build was taken off its machine and nothing is left to say how, the line says that the platform took it off the build machine, as happens on a drain, preemption or a lost machine. This line comes even after output that already explains the failure. Otherwise, when none of that output explains the failure, it ends with one line that names the platform step the build stopped in, or says that the build was stopped, most often at its time limit. When the build stopped during the image build and its output can still be read, a line before the last one names the step it stopped in and says whether the step's command was still running or had finished while the step's files were saved into the image. After the time limit, or when the platform took the build off its machine, the tail keeps the output from while the build ran and adds the cause with no step line. After an eviction for disk, the output is gone and the tail states only the cause. For a container source the step is the Dockerfile instruction as written, shortened when long, and a step the platform adds after the Dockerfile is named only as one; for a code source only the steps that install the declared requirements or check the base image's Python are named. At most 100 lines and 16 KiB; the cause is the last line and stays within those bounds. While the build runs it is the output so far under the same rules, refreshed as the build proceeds, and it carries no line about a failure. Null until the build has written output of that kind.

### [durationMs](#get-build-response-durationms)

- **Type**: `integer (nullable)`
- **Format**: `int64`

Wall-clock milliseconds from `createdAt` to `completedAt`. Null while the build is still running or when no completion time was recorded.

### [phases](#get-build-response-phases)

- **Type**: `object[] (4 properties)`
- **Required**: true

Observed build phases with their timings, recorded once the build is terminal. Empty until then. Phase lists differ by `source.type`. Never assume a fixed list.

#### [name](#get-build-response-phases-name)

- **Type**: `string`
- **Required**: true

Phase identifier, e.g. `prepare`, `build`, `index`. Deliberately not an enum. New build kinds report new phases without a contract change.

#### [startedAt](#get-build-response-phases-startedat)

- **Type**: `string (date-time)`
- **Required**: true
- **Format**: `date-time`

#### [completedAt](#get-build-response-phases-completedat)

- **Type**: `string (date-time) (nullable)`
- **Format**: `date-time`

Null while the phase is still running.

#### [outcome](#get-build-response-phases-outcome)

- **Type**: `string`
- **Required**: true

**Possible values**: `running` `succeeded` `failed`

### [createdAt](#get-build-response-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [completedAt](#get-build-response-completedat)

- **Type**: `string (date-time) (nullable)`
- **Format**: `date-time`

When the build reached a terminal status (`ready`/`failed`/`superseded`). Null while running or for terminal builds recorded before this was tracked.

### [Errors](#get-build-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Cancel or delete a build

DELETEapi.serverless.runware.ai/v1/apps/{appId}/builds/{buildId}

Cancels a queued or running build and records it as `superseded`. Deleting a queued or running build ends its current rollout without activating the canceled build, so any previous version keeps serving. A terminal build can be deleted once no live rollout still needs it. Ready builds remain while a version that has not been deleted references them.

### [Request](#delete-build-request)

Path

### [appId](#delete-build-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

### [buildId](#delete-build-path-buildid)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [Errors](#delete-build-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">502</code></td><td data-rw-cid-pz7tphoj="true">An upstream service was unreachable or refused the request</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>