> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gcore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload video via API to VOD storage

> Upload video to VOD storage by copying from external URL or using TUS resumable protocol, with batch migration and optional settings.

Two API upload methods are available: copy a file from an external URL, or upload a local file using the TUS resumable protocol. For large-scale migrations, Gcore provides a support-assisted batch workflow — contact [support@gcore.com](mailto:support@gcore.com) to arrange it. The Customer Portal upload workflow is covered in [Upload a video](/streaming/video-hosting/upload-a-video-and-embed-it-to-your-app).

All requests require an [API token](/account-settings/api-tokens). Set it as an environment variable before running the examples:

```bash theme={null}
export GCORE_API_KEY="{YOUR_API_KEY}"
```

The source file must comply with the input parameters and codec requirements in [Input parameters and codecs](/streaming/live-streams-and-videos-protocols-and-codecs/input-parameters-and-codecs). Only files with supported protocols, video/audio codecs, profiles, GOP structure, and bitrate constraints are accepted and reliably transcoded. Source properties such as pixel format, color range, or codec profile must remain stable throughout the file; reprocessing cannot fix mid-stream changes.

Processing statuses for uploaded videos are described in [VOD status](/streaming/video-hosting/vod-status).

## Copy from external storage

If the source file is available at a public HTTPS or SFTP URL, Gcore downloads it server-side. Supported protocols:

* HTTP/HTTPS from external video hosting services (Vimeo, Mux)
* HTTP/HTTPS from S3-compatible storages (AWS S3, Azure Blob Storage)
* SFTP

> [!NOTE]
>
> The source file must be in one of these formats: MP4, 3g2, 3gp, asf, avi, dif, dv, flv, f4v, m4v, mov, mpeg, mpg, mts, m2t, m2ts, qt, wmv, vob, mkv, ogv, webm, ogg, mxf, quicktime, x-ms-wmv, mpeg-tts, vnd.dlna.mpeg-tts.
>
> Streaming formats such as HLS (.m3u8/.ts) and DASH (.mpd/.m4v) are intended for end-viewer delivery and cannot be used as source files.

Valid and invalid source URL examples:

* Valid: `https://demo-files.gvideo.io/gcore.mp4` (13.8 MB from S3)
* Valid SFTP: `sftp://login:password@domain.com/path/file.mp4`
* Invalid (HLS): `https://demo-files.gvideo.io/hls/master.m3u8`

Send `POST /streaming/videos` with the source URL in the `origin_url` attribute:

```bash theme={null}
curl -L 'https://api.gcore.com/streaming/videos' \
-H 'Content-Type: application/json' \
-H 'Authorization: APIKey $GCORE_API_KEY' \
-d '{
  "video": {
    "name": "Gcore Demo",
    "description": "Video copied from an external S3 Storage",
    "origin_url": "https://demo-files.gvideo.io/gcore.mp4"
  }
}'
```

The API returns:

```json theme={null}
{
  "id": 2496039,
  "name": "Gcore Demo",
  "description": "Video copied from an external S3 Storage",
  ...
}
```

There is no limit on the number of videos that can wait in the transcoding queue. API requests are still subject to the [RPS limits](/api-reference/overview).

### Download behavior

Gcore attempts to download the source file three times, expecting a `200 OK` response. If all three attempts fail, the video entity stays in `Empty` status and the `error` field contains details — delete the empty entity and create a new one with a correct URL.

If the download succeeds, Gcore starts transcoding the video. Check transcoding progress via the [GET /streaming/videos/{id}](/api-reference/streaming/videos/get-video) endpoint or [webhook notifications](/streaming/extra-features/get-webhooks-from-the-streaming-platform).

## Upload from a local device

For uploads from a backend server or from a browser or mobile app, Gcore uses the [TUS resumable upload protocol](https://tus.io). TUS splits the file into segments and resumes from the last successful segment if an interruption occurs.

The upload process consists of three steps.

### Step 1. Create a video entity

Send `POST /streaming/videos` without the `origin_url` attribute to create a video entity and receive its ID and `client_id`:

```bash theme={null}
curl -L 'https://api.gcore.com/streaming/videos' \
-H 'Content-Type: application/json' \
-H 'Authorization: APIKey $GCORE_API_KEY' \
-d '{
  "video": {
    "name": "Gcore Demo",
    "description": "Uploaded via TUS session"
  }
}'
```

The API returns:

```json theme={null}
{
  "id": 2501637,
  "client_id": 1000503,
  "name": "Gcore Demo",
  "description": "Uploaded via TUS session",
  "duration": null,
  "slug": "70NbFUCZyXU2s3tY",
  "status": "empty",
  ...
}
```

Save `id` and `client_id` — both are required in Step 3.

### Step 2. Get TUS session parameters

Use the `id` from Step 1 to request the TUS upload server URL and session token:

```bash theme={null}
curl -L 'https://api.gcore.com/streaming/videos/2501637/upload' \
-H 'Authorization: APIKey $GCORE_API_KEY'
```

The API returns:

```json theme={null}
{
  "servers": [
    {
      "id": 561,
      "role": "upload",
      "hostname": "vod-uploader-ed-2.gvideo.co",
      "active": true,
      "ssl": true
    },
    ...
  ],
  "token": "eyJhbGciOiJIUzI3O6kc3Og...",
  "video": {
    "id": 2501637,
    ...
  }
}
```

Fields in the response:

* `servers` — available upload servers; use any server where `active` is `true`.
* `token` — session token valid for 4 hours. Once the TUS session starts, the token is no longer needed.
* `video` — a copy of the video entity data.

### Step 3. Upload via TUS

Use the values collected from Steps 1 and 2 as the TUS client configuration:

| Parameter | Source |
| - | - |
| Endpoint | `https://{hostname}/upload/` — hostname from Step 2 `servers[]` |
| Chunk size | 10 000 000 bytes (10 MB) — adjust based on network reliability |
| Filename | Local filename |
| File MIME type | e.g. `video/mp4` |
| Token | `token` from Step 2 |
| Video ID | `id` from Step 1 |
| Client ID | `client_id` from Step 1 |

Choose a chunk size that fits the network conditions: larger chunks use maximum bandwidth but require re-uploading a larger segment after a failure; smaller chunks are more resilient but slower due to per-request HTTP overhead.

> [!NOTE]
>
> Transmitted data must comply with the [Upload-Metadata header rules](https://tus.io/protocols/resumable-upload#upload-metadata):
>
> * Header fields must be comma-separated.
> * All values are Base64-encoded.
> * All keys are unique.

Bash upload command:

```bash theme={null}
DEBUG=1 tusc -H "https://vod-uploader-ed-2.gvideo.co" -f "file_to_upload.mp4" -b "/upload/" -- -H "'Upload-Metadata: client_id $(echo <client_id> | base64 -w0),filename $(echo <filename> | base64 -w0),token $(echo <token> | base64 -w0),video_id $(echo <video_id> | base64 -w0)'"
```

JavaScript upload with the [CodePen template](https://codepen.io/apih9000/pen/wvQmNEW?editors=1011) — copy `token`, `video_id`, and `client_id` to test the TUS upload flow:

![CodePen TUS upload template showing token, video\_id, and client\_id input fields](https://mintcdn.com/gcore/Z9h2R6Aoyu14Imh_/images/docs/streaming/video-hosting/upload-video-via-api/codepen-example.png?fit=max\&auto=format\&n=Z9h2R6Aoyu14Imh_\&q=85\&s=803526a6b182149261f60eda7e6281cc)

```js theme={null}
const options = {
  endpoint,
  chunkSize,
  retryDelays: [0, 1000, 3000],
  metadata: {
    filename: file.name,
    filetype: file.type,
    // required TUS metadata fields
    token: token,
    video_id: videoid,  // variable name used in CodePen
    client_id: clientid // variable name used in CodePen
  },
  onError(error) {
    ...
  },
  onProgress(bytesUploaded, bytesTotal) {
    ...
  },
  onShouldRetry(err, retryAttempt, options) {
    // HTTP 500 can be transient or a configuration error (token/ID mismatch).
    // Inspect err.originalResponse body to distinguish them before retrying.
    return retryAttempt < 3;
  },
  onSuccess() {
    ...
  }
}

upload = new tus.Upload(file, options)
upload.findPreviousUploads().then((previousUploads) => {
  askToResumeUpload(previousUploads, upload)
  upload.start()
  uploadIsRunning = true
})
```

Available TUS client implementations:

* JS: tus-js-client v2.x — [GitHub](https://github.com/tus/tus-js-client/releases?q=v2.\&expanded=true)
* Android: [Gcore's open-source Kotlin demo app](https://github.com/G-Core/android-demo-vod-hosting)
* iOS: [Gcore's open-source Swift demo app](https://github.com/G-Core/ios-demo-vod-hosting)
* Other: [tus.io/implementations](https://tus.io/implementations)

### TUS error reference

Common errors returned by the TUS upload server and their causes:

* `HTTP 405 Method Not Allowed` — unexpected response when creating the upload session; check the request parameters.
* `HTTP 429 Too Many Requests` — retry the request after a delay.
* `HTTP 500 Internal Server Error`:
  * `token invalid (null)` — check parameter format, Base64 encoding, and key-value separation.
  * `token invalid (eyJhbGciOi...)` — same as above.
  * `token video (12345) does not match this video (23456)` — `video_id` mismatch; do not retry.
  * `token client (123) does not match this client (234)` — `client_id` mismatch; do not retry.

## Batch migration

For migrating a large collection of videos, Gcore provides a support-assisted batch workflow. Contact [support@gcore.com](mailto:support@gcore.com) to arrange it.

To prepare the migration, obtain an HTTP link to each file in the source storage:

* [Google Drive](https://support.google.com/drive/answer/2494822?hl=en\&co=GENIE.Platform%3DDesktop\&oco=0#zippy=%2Cshare-multiple-files)
* [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html) — enable public access, then [list objects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) to get URLs
* [Vimeo](https://help.vimeo.com/hc/en-us/articles/12426150952593-Direct-links-to-video-files)
* [Mux](https://docs.mux.com/guides/video/download-for-offline-editing#retrieving-the-url-to-the-master)
* [Dropbox](https://help.dropbox.com/share/create-and-share-link)
* [Gcore Object Storage or SFTP](/storage/manage-object-storage/manage-buckets-via-the-control-panel#manage-files)

For storage types not listed, contact [support@gcore.com](mailto:support@gcore.com).

## Optional upload settings

The following parameters can be included in any `POST /streaming/videos` request body to configure transcoding behavior at upload time.

### Processing priority

Every uploaded video enters a transcoding queue. Two priority levels influence scheduling:

* Global priority — managed automatically by the system at the infrastructure level and is not user-configurable.
* Account priority — applied within an account, controlled via the `priority` field.

Use the `priority` field to adjust scheduling order for transcoding tasks within an account:

* Type: integer
* Range: -10 to 10
  * -10 — lowest priority (processed after other jobs)
  * 0 — default
  * 10 — highest priority (processed first)

This value adjusts relative job weight in the queue; it does not guarantee absolute ordering.

### AI subtitles

AI-generated subtitles can be created automatically at upload time by adding two parameters to the request body:

* `auto_transcribe_audio_language` — transcribes the audio track into subtitles. Set to `auto` for language detection, or specify a language code such as `en` or `fr`.
* `auto_translate_subtitles_language` — translates the transcribed subtitles into additional languages. Set to `default` for the account language, or provide a comma-separated list of language codes.

Language codes and translation options are documented in [Generate AI subtitles](/streaming/ai-video-service/generate-ai-subtitles-and-add-them-to-video/generate-captions-in-customer-portal).
