# Start an upload

`POST https://api-vod-express.syncwords.com/v1/projects/{id}/uploads`

Start an upload

Starts an upload of one file into the project’s `input/` folder; your client sends the bytes straight to S3, two ways.

**One `PUT`**, for a file of 5 GB or under that is not replacing one: `PUT` the whole file to `url`, with no other header needed, and you are done. The file appears in `input/` when the `PUT` completes. `url` is absent for a larger file, and when a file of that name is already in `input/` (delete it first, or send parts). A project that stopped itself after its last file starts again within a minute (unless its `startOnNewFiles` is false); any other stopped project is not started by it: `POST …/start` afterwards.

**Parts**, for any size, resumable: cut the file into `partCount` parts of `partSize` bytes (the last one shorter), sign each with `GET …/uploads/{uploadId}/parts/{partNumber}`, `PUT` its bytes to the URL, keep the `ETag` header S3 answers, and send them all to `POST …/uploads/{uploadId}/complete`. The file appears in `input/` only once complete.

Either way a running project never picks up half a file. An upload left unfinished is discarded after three days.

**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The project id, a lowercase ULID. |

## Request body

Example:

```json
{
  "name": "season 2/episode 01.mp4",
  "size": 734003200,
  "type": "video/mp4"
}
```

Schema:

```json
{
  "type": "object",
  "required": [
    "name",
    "size"
  ],
  "additionalProperties": false,
  "properties": {
    "name": {
      "type": "string",
      "description": "The file’s name under `input/`; `season 2/episode 01.mp4` puts it in a subfolder. It needs a media extension, and must not contain `${`."
    },
    "size": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5497558138880,
      "description": "Its size in bytes; it decides `partSize` and `partCount`."
    },
    "type": {
      "type": "string",
      "description": "Its media type, stored with the file by a parts upload. A one-`PUT` upload stores the `Content-Type` header the `PUT` sends, if any."
    },
    "overwrite": {
      "type": "boolean",
      "default": false,
      "description": "Replace a file of the same name. The new file is processed, and billed, again. Replace a `failed` file under a new name instead: its old content would be processed again."
    }
  }
}
```

## Responses

### 201

Started

Example:

```json
{
  "data": {
    "uploadId": "kuoFY0gBzJF3QDKXnQxnv0epFyjyDyUuF20w4yjsjCE4Sq9hkf7Fn.QWqeFXYSSx03HDx…",
    "key": "season 2/episode 01.mp4",
    "partSize": 16777216,
    "partCount": 44,
    "url": "https://sw-vod-express.s3.us-east-1.amazonaws.com/projects/141/01m3k8jnrdw509457nhz7w81bq/input/season%202/episode%2001.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=…",
    "urlExpiresAt": "2026-09-28T07:10:46.000Z"
  }
}
```

Schema:

```json
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Upload"
    }
  }
}
```

### 401

No token, or an expired or unknown one: `unauthenticated`.

Example:

```json
{
  "message": "Unauthenticated.",
  "code": "unauthenticated"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 403

VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).

Example:

```json
{
  "message": [
    "VOD Express is not enabled for your account."
  ],
  "code": "vod_express_not_enabled"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 404

No such project in your account: `project_not_found`.

Example:

```json
{
  "message": "Project not found.",
  "code": "project_not_found"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 409

A file of that name is already in `input/`, and `overwrite` is not set: `file_exists`. Or the project reads its media from a feed, and takes no uploads: `project_reads_feed`. Or `overwrite` is set but the running project may be processing that file, which is neither `done` nor `failed` yet: `file_processing`; wait, or stop the project.

Example:

```json
{
  "message": [
    "A file with this name is already in the project. Send overwrite: true to replace it."
  ],
  "code": "file_exists"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 413

The body is over 2 MB: `payload_too_large`.

Example:

```json
{
  "message": [
    "The request body is too large."
  ],
  "code": "payload_too_large"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 415

The body is neither JSON nor YAML: `unsupported_media_type`.

Example:

```json
{
  "message": [
    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
  ],
  "code": "unsupported_media_type"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 422

An invalid name, size or type (`validation_failed`). Also a name whose outputs another input already writes: outputs are named after the input without its extension, so `a.mov` and `a.mkv` would both write `a.es.mp4`.

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### 429

Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.

Example:

```json
{
  "message": [
    "Too Many Attempts."
  ],
  "code": "too_many_requests"
}
```

Schema:

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Schemas

### Upload

`#/components/schemas/Upload`

```json
{
  "type": "object",
  "required": [
    "uploadId",
    "key",
    "partSize",
    "partCount"
  ],
  "properties": {
    "uploadId": {
      "type": "string"
    },
    "key": {
      "type": "string",
      "description": "The file’s name under `input/`, which every later call of the upload sends back."
    },
    "partSize": {
      "type": "integer",
      "description": "Bytes a part: 16 MiB, or more for a file that would need over 10,000 parts."
    },
    "partCount": {
      "type": "integer"
    },
    "url": {
      "type": "string",
      "description": "A presigned S3 URL to `PUT` the whole file to, instead of the parts. Present when the file is 5 GB or under and no file of that name is in `input/`."
    },
    "urlExpiresAt": {
      "type": "string",
      "format": "date-time",
      "description": "When `url` stops working: an hour from now at most, sooner when the credentials that signed it expire first. Present with `url`."
    }
  }
}
```

### Error

`#/components/schemas/Error`

```json
{
  "type": "object",
  "required": [
    "message",
    "code"
  ],
  "properties": {
    "message": {
      "description": "For people. A string, or an array of one string on some statuses: read it as `Array.isArray(m) ? m[0] : m`.",
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      ]
    },
    "code": {
      "description": "For programs: stable, one per situation. See the Errors guide.",
      "enum": [
        "bad_request",
        "invalid_output_path",
        "invalid_idempotency_key",
        "unauthenticated",
        "invalid_credentials",
        "forbidden",
        "vod_express_not_enabled",
        "account_restricted",
        "not_found",
        "project_not_found",
        "file_not_found",
        "file_meta_not_found",
        "output_not_found",
        "feed_not_found",
        "upload_not_found",
        "webhook_not_found",
        "webhook_delivery_not_found",
        "aws_access_not_provisioned",
        "storage_connection_not_found",
        "method_not_allowed",
        "conflict",
        "file_exists",
        "project_reads_feed",
        "project_reads_folder",
        "file_processing",
        "project_config_locked",
        "idempotency_in_progress",
        "storage_connection_in_use",
        "payload_too_large",
        "unsupported_media_type",
        "validation_failed",
        "service_start_failed",
        "idempotency_key_reused",
        "webhook_limit_reached",
        "storage_failed",
        "storage_connection_check_failed",
        "storage_connection_folder_only",
        "storage_prefix_taken",
        "build_without_storage_connections",
        "build_has_no_schema",
        "too_many_requests",
        "internal_error",
        "bad_gateway",
        "service_stop_failed",
        "aws_access_failed",
        "platform_unavailable",
        "service_unavailable",
        "build_list_unavailable"
      ]
    },
    "errors": {
      "type": "object",
      "description": "On a 422: each field’s messages, keyed by field name, or by JSON pointer inside `config`.",
      "additionalProperties": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  }
}
```
