---
title: "Presigned upload for the walkthrough video. Requires stored contact email. Body: { content_type, consent, size }. Send the byte size: at or above 16 MB the response carries `multipart` (an upload_id, a part_size and a presigned PUT per part) instead of `upload`, and each part is retried on its own rather than restarting the whole video. Report each part's ETag to /upload-complete. Below the threshold, or if opening a multipart upload fails, the response is a single `upload`."
method: POST
path: "/jobs/{id}/upload-url"
---

# Presigned upload for the walkthrough video. Requires stored contact email. Body: { content_type, consent, size }. Send the byte size: at or above 16 MB the response carries \`multipart\` (an upload_id, a part_size and a presigned PUT per part) instead of \`upload\`, and each part is retried on its own rather than restarting the whole video. Report each part's ETag to /upload-complete. Below the threshold, or if opening a multipart upload fails, the response is a single \`upload\`.

`POST /jobs/{id}/upload-url`

## Request

- Base URL: `https://widget.reelquoteai.com/api/v1`
- URL: `https://widget.reelquoteai.com/api/v1/jobs/{id}/upload-url`
- Auth: HTTP bearer

## Request body

- object
  - `content_type` string, required: A video/* MIME type
  - `size` integer: Byte size of the file. Decides single vs multipart.
  - `consent` boolean, required: Must be true: the customer agrees the video will be analysed and shared with the mover.

## Response `200`

Where to PUT the bytes

- union: One of two shapes. Under 16 MB: `upload` (a single presigned PUT). At or above: `multipart` (a presigned PUT per part; PUT each, collect the ETag response headers, then POST /upload-complete).
  - object
    - `job_id` string, required
    - `upload` object, required
      - `method` 'PUT'
      - `url` string: Presigned; expires
      - `headers` object: Send exactly these
        - `Content-Type` string
      - `expires_in` integer: Seconds
  - object
    - `job_id` string, required
    - `multipart` object, required
      - `upload_id` string: Pass back to /upload-complete
      - `part_size` integer: Bytes per part; the last part may be shorter
      - `parts` object[]
        - `part_number` integer: 1-based
        - `url` string: Presigned PUT for this part
      - `expires_in` integer: Seconds

## Other responses

- `400`: `invalid_request` — A field is missing, malformed or not allowed in this state. `message` says which. `secret_key_in_url` — A secret key was sent as `?k=`. Secret keys go in the Authorization header only.
- `401`: `unauthorized` — No key, an unknown key, or a revoked key.
- `404`: `not_found` — No such job for this key, or no such endpoint.
- `429`: `rate_limited` — Per-key limit (60/min publishable, 300/min secret), the mover's daily ballpark or video-analysis cap, or a visitor's daily catalog-search cap. Obey `Retry-After`. Carries `Retry-After` in seconds.
- `503`: `address_lookup_unavailable` — Address autocomplete is not configured or is down. Send the address as free text. `upload_unavailable` — Video storage is temporarily unavailable. Retry in a few minutes. `queue_unavailable` — The job could not be started just now; nothing was changed. Repeat the same request after `Retry-After`.

---

[API](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api.md) · [All operations](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api/revisions/56c304752192?raw)
