---
title: "Issue a presigned PUT URL for direct-to-R2 media uploads"
method: POST
path: "/uploads/presign"
tags: ["Uploads"]
---

# Issue a presigned PUT URL for direct-to-R2 media uploads

`POST /uploads/presign`

## Request

- Base URL: `https://www.sociahive.com/api/v1`
- URL: `https://www.sociahive.com/api/v1/uploads/presign`
- Auth: one of:
  - API key in header `X-API-Key`
  - OAuth 2 (authorization code, authorization URL `/api/oauth/authorize`, token URL `/api/oauth/token`, scopes: `uploads:write`)

Mobile clients upload media directly to R2 instead of proxying through the origin. Server picks the storage key under `uploads/{user_id}/{uuid}.{ext}` — clients cannot supply a key.

## Request body

- PresignUploadRequest
  - `filename` string, required: Client-supplied display name. Used for logging only; server picks the storage key.
  - `mime_type` 'image/jpeg' | 'image/png' | 'image/webp' | 'video/mp4' | 'video/quicktime', required: Allowed MIME types only. Anything else is rejected with invalid_input.
  - `size_bytes` integer, required: File size in bytes. Per-tier limits: Growth 25 MB, Scale 100 MB, Agency 500 MB.

## Response `200`

Presigned upload URL

- PresignUploadResponse
  - `key` string, required: The server-chosen storage key. Send it to POST /uploads/verify after the PUT.
  - `upload_url` string, uri, required: Short-lived presigned PUT URL. Issue a single HTTP PUT carrying every header in required_headers.
  - `required_headers` object: Headers the PUT MUST carry verbatim. Each value is bound into the URL's SigV4 signature, so omitting or altering one makes R2 reject the upload with 403. Send every entry as returned; do not hardcode the list — it can grow. Optional so a client can fall back to Content-Type + Content-Length against an older server.
  - `public_url` string, uri, required: Permanent public URL where the uploaded bytes will be served once the PUT completes.
  - `expires_at` string, date-time, required: ISO 8601 timestamp at which upload_url stops accepting writes (15 min after issuance).

## Other responses

- `400`: Bad request — invalid parameters
- `401`: Authentication required — provide X-API-Key or Bearer token
- `403`: Insufficient scope for this operation
- `429`: Rate limit exceeded
- `500`: Internal server error

---

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