---
title: Source uploads | Runware Docs
url: https://runware.ai/docs/serverless/api-source-uploads
description: Stage an archive and publish it as the immutable source an app is created from.
relatedDocuments:
  - https://runware.ai/docs/serverless/api
  - https://runware.ai/docs/serverless/deploying
  - https://runware.ai/docs/serverless/api-versions
---
## Introduction

A **source** is the archive an app is built from: a zip of your code, or a Dockerfile with its build context. It is immutable and belongs to your organization, so the same source can back any number of apps and versions, each choosing its own entry point.

**Creating an app names a source by `sourceId`**, and so does updating the code an app runs. This flow is the only thing that produces one.

## The flow

```text
POST /v1/source-uploads
PUT  <transfer.url>
POST /v1/source-uploads/{uploadId}/complete
```

**Zip and hash the archive before you start.** Creating the session declares the archive's exact byte length and its lowercase SHA-256, and completion accepts only bytes that match what you declared.

The create response carries a `transfer` instruction beside the session: a method, a short-lived `url` for this upload's staging object, and the headers to send with it. **That request is not one of the routes on this page.** Send the archive to the URL you were given, with those headers, and then call complete.

Completion publishes the verified bytes and **the ready session carries the `sourceId` to use**. A session is `pending` until then, and ends as `ready`, `rejected`, `expired` or `deleted`. **A rejected session keeps its `rejectionReason` and a retry returns the same result**, so that reason is what to act on.

> [!NOTE]
> What the archive has to contain depends on its type. A `code` archive is your codebase, and the app points at its entry point with `modelFile`. A `container` archive carries a `Dockerfile` and a `container.yaml` at its root, plus whatever the Dockerfile copies in. See [Writing a model](https://runware.ai/docs/serverless/writing-a-model) and [Bringing a container](https://runware.ai/docs/serverless/bringing-a-container).

## Create a source upload

POSTapi.serverless.runware.ai/v1/source-uploads

Creates an upload session for a source archive. The session belongs to the authenticated organization and names no app: an upload precedes any app, and the source it publishes may back several. The response contains a short-lived transfer instruction for one exact staging object. Repeating the request with the same idempotency key and declaration while the session is pending and unexpired returns the same upload resource with a refreshed transfer instruction. Replays with a different declaration, or after the session becomes ready, rejected, expired, or deleted, return `409`.

> [!NOTE]
> The `idempotencyKey` is yours to choose and makes this call safe to retry: repeating it with the same declaration, while the session is still pending and has not expired, returns the same session with a fresh transfer instruction. Repeating it with a different declaration, or once the session has finished, answers `409`.

### [Request](#create-source-upload-request)

Body

### [declaredByteLength](#create-source-upload-body-declaredbytelength)

- **Type**: `integer`
- **Required**: true
- **Format**: `int64`
- **Min**: `1`
- **Max**: `10485760`

Exact size of the archive in bytes, at most 10 MiB. Completion weighs the staged bytes against it and refuses an archive that does not match.

### [idempotencyKey](#create-source-upload-body-idempotencykey)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `255`

Client-generated key used to make session creation safe to retry.

### [sha256](#create-source-upload-body-sha256)

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

Lowercase SHA-256 digest of the complete archive.

### [sourceType](#create-source-upload-body-sourcetype)

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

**Allowed values**: `code` `container`

### [Response](#create-source-upload-response)

### [upload](#create-source-upload-response-upload)

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

#### [id](#create-source-upload-response-upload-id)

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

#### [sourceId](#create-source-upload-response-upload-sourceid)

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

The immutable source this upload published. Set once the upload is `ready`. It is what app create and source update name. Equal to `id`, so a retry of completion returns the same value.

#### [declaredByteLength](#create-source-upload-response-upload-declaredbytelength)

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

#### [sha256](#create-source-upload-response-upload-sha256)

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

#### [sourceType](#create-source-upload-response-upload-sourcetype)

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

**Possible values**: `code` `container`

#### [state](#create-source-upload-response-upload-state)

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

**Possible values**: `pending` `ready` `rejected` `expired` `deleted`

#### [rejectionReason](#create-source-upload-response-upload-rejectionreason)

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

Reason completion rejected the archive, when state is `rejected`.

#### [expiresAt](#create-source-upload-response-upload-expiresat)

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

#### [createdAt](#create-source-upload-response-upload-createdat)

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

#### [updatedAt](#create-source-upload-response-upload-updatedat)

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

### [transfer](#create-source-upload-response-transfer)

- **Type**: `variant`
- **Required**: true

**Format 1: object**:

#### [mode](#create-source-upload-response-transfer-format-1-mode)

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

**Possible values**: `singlePut`

#### [method](#create-source-upload-response-transfer-format-1-method)

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

**Possible values**: `PUT`

#### [url](#create-source-upload-response-transfer-format-1-url)

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

Short-lived URL for this upload's exact staging object.

#### [headers](#create-source-upload-response-transfer-format-1-headers)

- **Type**: `object`
- **Required**: true

Headers the client must include in the upload request.

#### [expiresAt](#create-source-upload-response-transfer-format-1-expiresat)

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

Time after which the transfer instruction is no longer valid.

### [Errors](#create-source-upload-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="">409</code></td><td data-rw-cid-pz7tphoj="true">The idempotency key was already used. <code class="component-Code">conflict</code> means the replay declared a different archive. The <code class="component-Code">source-upload-*</code> types mean the session the key names is no longer pending, and say which state it reached.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">413</code></td><td data-rw-cid-pz7tphoj="true">The request body exceeds its size limit: 10 MiB on <code class="component-Code">invoke-sync</code> and <code class="component-Code">invoke-async</code>, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. <code class="component-Code">detail</code> names the limit in bytes.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">415</code></td><td data-rw-cid-pz7tphoj="true">The request body arrived with a Content-Type this operation does not accept. The body was not decoded, so this is not a schema failure. A body of <code class="component-Code">application/json</code> that cannot be parsed returns <code class="component-Code">400</code>. One that parses and breaks the schema returns <code class="component-Code">422</code>. A body sent with no Content-Type is the same refusal.</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>

## Complete a source upload

POSTapi.serverless.runware.ai/v1/source-uploads/{uploadId}/complete

Verifies the staging object's length, content type, and SHA-256 digest against the session declaration, applies the archive rules for the source type, and publishes the verified bytes as an immutable source artifact. The first success creates the source and returns the ready session carrying its `sourceId`. A retry returns the same session and the same `sourceId`. A rejected upload keeps its rejection so later retries return the same result.

### [Request](#complete-source-upload-request)

Path

### [uploadId](#complete-source-upload-path-uploadid)

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

Source upload session identifier.

### [Response](#complete-source-upload-response)

### [id](#complete-source-upload-response-id)

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

### [sourceId](#complete-source-upload-response-sourceid)

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

The immutable source this upload published. Set once the upload is `ready`. It is what app create and source update name. Equal to `id`, so a retry of completion returns the same value.

### [declaredByteLength](#complete-source-upload-response-declaredbytelength)

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

### [sha256](#complete-source-upload-response-sha256)

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

### [sourceType](#complete-source-upload-response-sourcetype)

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

**Possible values**: `code` `container`

### [state](#complete-source-upload-response-state)

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

**Possible values**: `pending` `ready` `rejected` `expired` `deleted`

### [rejectionReason](#complete-source-upload-response-rejectionreason)

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

Reason completion rejected the archive, when state is `rejected`.

### [expiresAt](#complete-source-upload-response-expiresat)

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

### [createdAt](#complete-source-upload-response-createdat)

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

### [updatedAt](#complete-source-upload-response-updatedat)

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

### [Errors](#complete-source-upload-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">The upload cannot be completed in its current state. The <code class="component-Code">type</code> says which state and what to do next: upload the object, retry, or open a new session.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">The uploaded archive broke an artifact rule for its source type. The upload keeps this rejection: a later completion answers <code class="component-Code">409</code> with type <code class="component-Code">source-upload-rejected</code> and the stored reason. <code class="component-Code">errors[]</code> carries one entry per rule broken.</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>

## Get a source upload

GETapi.serverless.runware.ai/v1/source-uploads/{uploadId}

Returns the upload session belonging to the authenticated organization. Once the upload is `ready` the session carries the `sourceId` it published. An upload belonging to another organization returns `404`.

### [Request](#get-source-upload-request)

Path

### [uploadId](#get-source-upload-path-uploadid)

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

Source upload session identifier.

### [Response](#get-source-upload-response)

### [id](#get-source-upload-response-id)

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

### [sourceId](#get-source-upload-response-sourceid)

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

The immutable source this upload published. Set once the upload is `ready`. It is what app create and source update name. Equal to `id`, so a retry of completion returns the same value.

### [declaredByteLength](#get-source-upload-response-declaredbytelength)

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

### [sha256](#get-source-upload-response-sha256)

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

### [sourceType](#get-source-upload-response-sourcetype)

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

**Possible values**: `code` `container`

### [state](#get-source-upload-response-state)

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

**Possible values**: `pending` `ready` `rejected` `expired` `deleted`

### [rejectionReason](#get-source-upload-response-rejectionreason)

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

Reason completion rejected the archive, when state is `rejected`.

### [expiresAt](#get-source-upload-response-expiresat)

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

### [createdAt](#get-source-upload-response-createdat)

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

### [updatedAt](#get-source-upload-response-updatedat)

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

### [Errors](#get-source-upload-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="">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>

## Abort a source upload

DELETEapi.serverless.runware.ai/v1/source-uploads/{uploadId}

Aborts an upload that has not published its source and removes its staging object. The session remains as a deleted tombstone so its staging key cannot be reused. Repeating a successful abort is idempotent. A `ready` upload cannot be aborted: the source it published is a resource in its own right, and the request answers `409` with type `source-upload-completed`.

> [!NOTE]
> Only a session that has not published its source can be aborted. Once it is `ready` the source exists in its own right and the request answers `409`.

### [Request](#delete-source-upload-request)

Path

### [uploadId](#delete-source-upload-path-uploadid)

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

Source upload session identifier.

### [Errors](#delete-source-upload-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">The upload is <code class="component-Code">ready</code>. The source it published is a resource in its own right, so the upload cannot be aborted.</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 source

GETapi.serverless.runware.ai/v1/sources/{sourceId}

Returns the immutable source artifact a completed upload published: its type and the length and digest upload completion verified. A source is organization-scoped and may be named by any number of apps and versions in that organization. One belonging to another organization returns `404`. There is no list and no delete.

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

Path

### [sourceId](#get-source-path-sourceid)

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

Source artifact identifier.

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

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

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

### [sourceType](#get-source-response-sourcetype)

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

**Possible values**: `code` `container`

### [byteLength](#get-source-response-bytelength)

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

Verified length of the archive in bytes.

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

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

Verified lowercase SHA-256 digest of the archive.

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

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

### [Errors](#get-source-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="">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>