---
title: "Send the bytes to storage instead of to us"
method: POST
path: "/v1/uploads/direct"
tags: ["Uploads"]
---

# Send the bytes to storage instead of to us

`POST /v1/uploads/direct`

## Request

- Base URL: `https://api.tryplayhead.com`
- URL: `https://api.tryplayhead.com/v1/uploads/direct`
- Auth: HTTP bearer

Two short calls and one transfer that never touches our servers. Use this rather than `POST /v1/uploads` whenever the client can make two requests: a 2 GB export streamed through the API holds a worker for as long as the connection takes, and sent to the link this returns it costs nothing on our side.

1. Call this. You get an `upload_id`, a `complete_url`, and either a `put` target or, for a large file, a `multipart` one.
2. Send the bytes. A `put` target is one request with exactly the headers it names. A `multipart` target is one request per part, each answering with an `ETag`.
3. POST to `complete_url`, reporting the parts if there were any. That answers the same body `POST /v1/uploads` does.

Add `?ticket=ut_…` instead of a credential to use an upload ticket. The `complete_url` returned then carries the ticket already.

`bytes` is what decides whether parts are offered. It is a claim about a file we have not seen, so the object is measured again at step 3 and an oversized one is deleted rather than kept.

## Request body

- object
  - `filename` string
  - `bytes` integer
  - `content_type` string

## Response `200`

Where to put the bytes, and where to say they arrived.

- DirectUploadTarget: Where to put the bytes of one file. Exactly one of `put` and `multipart` is present.
  - `upload_id` string
  - `max_bytes` integer
  - `expires_at` string, date-time
  - `complete_url` string, uri
  - `put` object: One request. Send exactly the headers named here.
    - `method` 'PUT'
    - `url` string, uri
    - `headers` object
  - `multipart` object: One request per part, each answering with an `ETag`. Every part except the last is `part_bytes` long.
    - `id` string
    - `part_bytes` integer
    - `parts` object[]
      - `number` integer
      - `url` string, uri

## Other responses

- `400`: The declared size is over the limit.
- `401`: Missing, malformed or expired credential.
- `429`: Upload rate limited.

---

[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)
