---
title: "Register Upload"
method: POST
path: "/uploads/register"
tags: ["uploads"]
---

# Register Upload

`POST /uploads/register`

Finalize a signed-URL upload into a Moda file row.

Streams the staged blob through ``download_with_hash`` (writes to a
temp file on disk, hashes in 8 KiB chunks) so a 250 MB upload never
fully materializes in process memory — loading even a couple of those
concurrently on Cloud Run would risk OOM and defeat the point of the
direct-to-storage flow.

Deduplicates against existing team files by content hash. On a dedup
miss the staged blob is server-side-copied to the canonical
``teams/<team>/files/<id>`` key via ``storage.copy`` (zero data
transfer on GCS) and registered via ``create_file_from_storage``. The
pending blob is best-effort deleted either way.

## Headers

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

## Request body

- RegisterUploadRequest
  - `storage_key` string, required — ``storage_key`` returned by a prior ``POST /v1/uploads/url`` after the PUT completed.
  - `filename` string, nullable — Filename to record on the File row. Defaults to the basename embedded in ``storage_key``.
  - `mime_type` string, nullable — MIME type to record on the File row. Defaults to the type the signed URL was originally bound to (inferred from the filename when not supplied).

## Response `200`

Successful Response

- FileUploadResponse
  - `id` string, required — Unique file identifier (prefixed ``file_...``).
  - `url` string, required — Stable proxy URL for the uploaded file. Use this in attachment URLs.
  - `filename` string, required — Filename of the uploaded file.
  - `mime_type` string, required — MIME type of the file.
  - `size_bytes` integer, nullable — File size in bytes.
  - `was_duplicate` boolean — True if an identical file already existed (deduplicated).

## 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)
