# Uploads

Files over 4 MB go up first: a URL to PUT the image to, and the key a conversion names it by.

### Create an upload

`POST /v1/uploads`

Mints a URL to PUT one image to, and the key a conversion names it by. Uploads never pass through the API (a request body is capped well under our 10485760-byte limit). The URL is signed for exactly `contentLength` bytes: the PUT must send that many. Its type and size are checked again, from the stored bytes, when the conversion is created.

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `contentType` | body | one of `image/png`, `image/apng`, `image/jpeg`, `image/jpg`, `image/pjpeg`, `image/webp`, `image/avif`, `image/gif`, `image/bmp`, `image/x-bmp`, `image/x-ms-bmp`, `image/tiff`, `image/tif`, `image/x-tiff` | yes | The file's media type. |
| `sizeBytes` | body | number, 1 to 10485760 | yes | The file's exact size in bytes. |

```bash
curl -X POST https://api.vecsnap.com/v1/uploads \
  -H "Authorization: Bearer $VECSNAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contentType":"image/png","sizeBytes":1}'
```

Answer 200:

```json
{
  "uploadUrl": "https://files.vecsnap.com/…",
  "inputKey": "inputs/2026-10-25/0b6f3c1e-7a2d-4e8b-9c10-5d4e3f2a1b0c/6e1d2c3b-4a5f-4e6d-8c7b-9a0f1e2d3c4b.png",
  "contentType": "example",
  "contentLength": 1,
  "expiresIn": 1
}
```

Every refusal it may answer: `file_empty` (400), `file_too_large` (400), `invalid_request` (400), `invalid_size` (400), `malformed_body` (400), `unsupported_type` (400), `invalid_api_key` (401), `unauthenticated` (401), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

---
This page: https://vecsnap.com/docs/api/uploads
