---
title: "Finish a direct upload"
method: POST
path: "/v1/uploads/{uploadId}/complete"
tags: ["Uploads"]
---

# Finish a direct upload

`POST /v1/uploads/{uploadId}/complete`

## Request

- Base URL: `https://api.tryplayhead.com`
- URL: `https://api.tryplayhead.com/v1/uploads/{uploadId}/complete`
- Auth: HTTP bearer

Reads the object, probes it, and gives the upload a video id. Report the parts here when the target was a `multipart` one.

Idempotent: this step can transcode, so it is the one call slow enough to hit a client timeout, and calling it again on a finished upload returns the same answer rather than an error.

## Path parameters

- `uploadId` string, required

## Request body

- object
  - `multipart` object
    - `id` string, required
    - `parts` object[], required
      - `number` integer, required
      - `etag` string, required

## Response `200`

The upload id and what was probed from the file.

- UploadResult
  - `upload_id` string: Pass this as `upload_id` on a later video call.
  - `video` Video
    - `id` string
    - `platform` string: `youtube`, `tiktok`, `instagram`, `x`, `facebook`, `vimeo`, `reddit`, `twitch`, `dailymotion`, `web` for a generically extracted page, or `upload`.
    - `duration` number: Seconds.
    - `width` integer
    - `height` integer
    - `orientation` string
    - `src_fps` number
    - `title` string, nullable
    - `language` string, nullable
    - `engagement` object, nullable: The platform's own public numbers for the post, read when the link was resolved. A count the platform does not give is null, never 0: Instagram gives no views without a login. Null for an upload.
      - `views` integer, nullable
      - `likes` integer, nullable
      - `comments` integer, nullable
      - `reposts` integer, nullable
      - `posted_at` string, nullable: When the post went up: a date and time, or a date when the platform gives only the day.
      - `read_at` string, date-time: When these numbers were read.
    - `content_box` object, nullable: The picture inside the frame when the source was letterboxed. Present means the tiles show this region, not the full frame.
      - `x` integer
      - `y` integer
      - `width` integer
      - `height` integer
    - `carousel` object, nullable: Present when the post is a photo carousel — N stills with no timeline. One second is one slide: second 0 is slide 1. The number is an index wearing the units the rest of the API speaks, not a duration.
      - `slides` integer
      - `audio_seconds` number, nullable
    - `start_timecode` string, nullable: SMPTE timecode of the source's first frame, e.g. `14:36:27:07`. Present on camera and deck formats, absent on everything from the web. A different clock from the elapsed seconds everywhere else in this response — both are true, and neither should be converted into the other.
    - `interlaced` boolean
    - `audio_tracks` integer: Streams, not channels. A camera master routinely has eight.
  - `size_bytes` integer: What is stored.
  - `received_bytes` integer: What was sent.
  - `credits_charged` number: Zero for a metadata read.

## Other responses

- `400`: The parts could not be joined, or the file is too big.
- `401`: Missing, malformed or expired credential.
- `404`: No bytes have arrived for that upload yet.
- `422`: ffprobe cannot read the file as media.

---

[API](https://skmtc.dev/tryplayhead/apis/playhead-api.md) · [All operations](https://skmtc.dev/tryplayhead/apis/playhead-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tryplayhead/apis/playhead-api/revisions/21dfb775b852?raw)
