---
title: "Start a signed upload"
method: POST
path: "/uploads"
tags: ["Uploads"]
---

# Start a signed upload

`POST /uploads`

## Request

- Base URL: `https://api.propertypixel.app/v1`
- URL: `https://api.propertypixel.app/v1/uploads`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/authorize`, token URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/token`)

Reserve room in a project for up to 25 photos and get one signed URL per file. PUT each file's raw bytes to its upload_url with exactly the returned headers, then call POST /uploads/{id}/complete with the storage_path, filename, size and content_type of every file that uploaded. Files must be JPEG, PNG, WebP, HEIC or HEIF, at most 25 MB each; a project holds at most 100 photos. The reservation expires at expires_at (about 15 minutes); complete before then. Files are stored exactly as sent, except HEIC/HEIF, which is converted to JPEG on completion. Downscale large images yourself (the web app uses a 2048px long edge).

Requires a connection with edit access.

## Headers

- `Idempotency-Key` string: A unique key (for example a UUID) that makes retries safe. A retry with the same key and body returns the stored response; the same key with a different body returns 422. Keys last 24 hours.

## Request body

- object
  - `project_id` string, uuid, required: The project the photos go into.
  - `files` object[], required
    - `filename` string, required
    - `content_type` string, required: image/jpeg, image/png, image/webp, image/heic or image/heif.
    - `size` integer, required: Exact size in bytes.

## Response `201`

Start a signed upload

- Upload
  - `object` 'upload', required
  - `id` string, uuid, required: Pass to POST /uploads/{id}/complete.
  - `expires_at` string, date-time, required
  - `files` object[], required
    - `filename` string, required
    - `upload_url` string, uri, required
    - `method` 'PUT', required
    - `headers` object, required
    - `storage_path` string, required
  - `failed` object[], required: Files rejected before upload (type or size).
    - `filename` string, required
    - `error` string, required

## Other responses

- `400`: The request is invalid. Codes: `validation_failed`.
- `401`: Missing, invalid or revoked credential. Codes: `unauthorized`.
- `403`: The credential can't do this. View-only connections get forbidden_scope on writes. Codes: `forbidden_scope`, `forbidden`.
- `404`: The resource doesn't exist in this workspace. Codes: `not_found`.
- `409`: The request conflicts with the current state. Codes: `conflict`, `idempotency_in_progress`.
- `422`: An Idempotency-Key was reused with a different body, or an upload was rejected. Codes: `idempotency_conflict`, `upload_rejected`.
- `429`: Too many requests for this connection. Codes: `rate_limited`.
- `500`: Something failed on our side. Retry later. Codes: `internal`.

---

[API](https://skmtc.dev/propertypixel/apis/propertypixel-api.md) · [All operations](https://skmtc.dev/propertypixel/apis/propertypixel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/propertypixel/apis/propertypixel-api/revisions/8f7baef701cc?raw)
