---
title: "Create a presigned direct-to-S3 upload slot."
method: POST
path: "/media/uploads"
tags: ["Media"]
---

# Create a presigned direct-to-S3 upload slot.

`POST /media/uploads`

## Request

- Base URL: `https://api.reveldigital.com`
- URL: `https://api.reveldigital.com/media/uploads`
- Auth: one of:
  - OAuth 2
    - Authorization code: `https://id.reveldigital.com/connect/authorize`
    - Token URL: `https://id.reveldigital.com/connect/token` scopes: `webapi`
  - API key in header `X-RevelDigital-ApiKey`
  - API key in query parameter `api_key`
- TypeScript: <https://skmtc.dev/reveldigital/apis/revel-digital-rest-api/docs/media/uploads/post.ts> (a function with typed inputs; its response is checked against the schema)

Returns a short-lived presigned S3 `PUT` URL so a client can upload large binary
artifacts (e.g. Revel Digital `.webapp` zip bundles) DIRECTLY to storage without the
bytes streaming through the API. After uploading, call
`POST /media/uploads/{id}/finalize` to ingest the object into the media library.
            
The account is always derived from the authenticated key/token — never from the body.
The presigned URL is scoped to a single object key under the account's namespace, so it
can never overwrite another account's objects.
            
Three-step flow:
```
# 1) Request a slot
curl -X POST "https://api.reveldigital.com/api/media/uploads" \
     -H "X-Reveldigital-Apikey: <API_KEY>" \
     -H "Content-Type: application/json" \
     -d '{ "name": "myapp.webapp", "group_id": "<GROUP_ID>", "tags": ["env=Production"], "content_type": "application/zip" }'
            
# 2) Upload the bytes straight to S3 (Content-Type MUST match the returned header)
curl -T myapp.webapp -H "Content-Type: application/zip" "<upload_url>"
            
# 3) Finalize / ingest
curl -X POST "https://api.reveldigital.com/api/media/uploads/<id>/finalize" \
     -H "X-Reveldigital-Apikey: <API_KEY>"
```
            
All media types supported by the multipart upload work here too (images, audio, video,
presentations, PDF, and `.webapp` bundles): finalize downloads the staged object to
the server and runs the SAME ingestion pipeline, so video is transcoded just as it is for
a multipart upload.

## Request body

- MediaUploadRequest: Request body for creating a direct-to-S3 upload slot via `POST /media/uploads`.
  - `name` string, nullable: The file name of the asset to upload, including extension (e.g. `myapp.webapp`). The extension determines the resulting media type, so preserve `.webapp` for webapp bundles.
  - `group_id` string, nullable: Optional media group id the finalized media will be placed in. When omitted the account's root media group is used.
  - `tags` string[], nullable: Optional tags applied to the finalized media (stored newline-delimited on the media record).
  - `content_type` string, nullable: Optional content type the client will send on the S3 PUT. Defaults to `application/zip` for `.webapp` bundles, otherwise the type inferred from the file extension. The value is pinned into the presigned signature and the client MUST send the same header.
  - `advertiser_id` string, nullable: Optional advertiser/user id the finalized media is attributed to.
  - `is_shared` boolean, nullable: Optional flag marking the finalized media as shared with other accounts.
  - `size` integer, nullable: Optional declared object size in bytes. When supplied it is validated against the configured maximum up front; the real size is always re-validated at finalize.

## Response `201`

Returns the created upload slot

- MediaUploadResponse: Response returned from `POST /media/uploads` describing the presigned S3 PUT slot.
  - `id` string, nullable: The opaque upload slot id. Use it to finalize (`POST /media/uploads/{id}/finalize`) or to query status (`GET /media/uploads/{id}`).
  - `upload_url` string, nullable: The presigned S3 URL the client uploads the raw bytes to.
  - `method` string, nullable: The HTTP method to use against RevelDigital.Core.PublicApi.Models.MediaUploadResponse.UploadUrl. Always `PUT`.
  - `headers` object, nullable: The exact headers the client MUST send on the upload request (at minimum `Content-Type`).
  - `key` string, nullable: The S3 object key the bytes are written to. Scoped to the authenticated account.
  - `expires_at` string, date-time: The UTC time the presigned URL expires (ISO-8601).

## Other responses

- `400`: If the request is invalid (bad name, file type, group, or size)

---

[API](https://skmtc.dev/reveldigital/apis/revel-digital-rest-api.md) - [All operations](https://skmtc.dev/reveldigital/apis/revel-digital-rest-api/llms.txt) - [OpenAPI document](https://skmtc.dev/reveldigital/apis/revel-digital-rest-api/revisions/39d5c8302822?raw)
