---
title: "Create Upload Url"
method: POST
path: "/uploads/url"
tags: ["uploads"]
---

# Create Upload Url

`POST /uploads/url`

Mint a short-lived signed PUT URL for direct-to-storage upload.

Two-step flow for files above the gateway's ~32 MiB inbound cap:

1. ``POST /v1/uploads/url`` with ``{filename, mime_type}`` →
   ``{upload_url, storage_key, ...}``.
2. PUT raw bytes to ``upload_url`` with ``Content-Type: <mime_type>``.
3. ``POST /v1/uploads/register`` with ``{storage_key}`` to receive a
   standard ``FileUploadResponse`` whose ``url`` is reusable as an
   attachment in ``start_design_task``.

Files are still capped at ``settings.MAX_FILE_SIZE_BYTES`` (enforced at
register time when the actual size is known). Signed URLs expire after
``expires_in_seconds`` (default 600s, max 3600s).

## Headers

- `Moda-Version` '2026-04-12' | '2026-05-01'

## Request body

- CreateUploadUrlRequest — Request a signed PUT URL for direct-to-storage upload. The multipart ``POST /uploads`` endpoint funnels bytes through the API gateway, which caps inbound bodies at ~32 MiB (Cloud Run HTTP/1 limit) — files above that size are rejected with a 413 before the handler runs. This two-step flow sidesteps the cap by letting the client PUT bytes directly to storage.
  - `filename` string, required — Original filename. Only the basename is used; path components are stripped.
  - `mime_type` string, required — MIME type of the file (e.g. ``application/vnd.openxmlformats-officedocument.presentationml.presentation`` for PPTX, ``application/pdf``, ``image/png``). Must be on the allow-list.
  - `expires_in_seconds` integer — How long the signed URL is valid (60–3600 seconds, default 600).

## Response `200`

Successful Response

- CreateUploadUrlResponse
  - `upload_url` string, required — Pre-signed URL. PUT the raw file bytes here with ``Content-Type: <mime_type>`` within ``expires_in_seconds``. No auth header on the PUT — the URL itself is the capability.
  - `storage_key` string, required — Opaque key identifying the pending upload. Pass back to ``POST /v1/uploads/register`` after the PUT completes to finalize the file.
  - `mime_type` string, required — Resolved MIME type the signed URL is bound to.
  - `expires_in_seconds` integer, required — How long the upload URL remains valid (seconds).
  - `instructions` string, required — Human-readable usage hint describing the PUT + register contract.

## Other responses

- `401` — Authentication required.
- `403` — Permission denied for this scope.
- `404` — Resource not found.
- `409` — Conflict (idempotency / resource state).
- `422` — Request validation failed.
- `429` — Rate limit exceeded.
- `500` — Internal error.

---

[API](https://skmtc.dev/moda/apis/moda-public-api.md) · [All operations](https://skmtc.dev/moda/apis/moda-public-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/moda/moda-public-api/revisions/b34cb4cca59e/schema)
