---
title: "Initialise an upload — validate, allocate a Site, return presigned PUT URLs"
method: POST
path: "/uploads/init"
tags: ["Uploads"]
---

# Initialise an upload — validate, allocate a Site, return presigned PUT URLs

`POST /uploads/init`

## Request

- Base URL: `https://api.nippy.host`
- URL: `https://api.nippy.host/uploads/init`
- Auth: none declared

## Request body

- InitUploadDto
  - `email` string, email, required: Email address of the uploader. A magic-link sign-in is mailed on finalize.
  - `subdomain` string, required: Requested subdomain (server may disambiguate with `-N` suffix on collision)
  - `files` InitUploadFileDto[], required
    - `name` string, required: Filename including extension
    - `size` number, required: File size in bytes
    - `contentType` string, required: MIME type the browser will use when uploading
  - `ref` string: First-touch acquisition channel (e.g. "cli", "mcp", "extension", a launch slug). Recorded on the user only if this upload creates the account; ignored for existing users.

## Response `200`

Init successful — files should be PUT to the returned URLs

- APIInitUploadResponse
  - `uploadBatchId` string, required
  - `subdomain` string, required: Subdomain the file will be served from. May differ from the requested value if disambiguation was applied.
  - `expiresAt` string, date-time, required
  - `files` APIPresignedFile[], required
    - `name` string, required: Original filename as supplied to /uploads/init
    - `uploadUrl` string, required: Presigned PUT URL — bind exact ContentType + Content-Length
    - `key` string, required: R2 object key the file will land at

## Other responses

- `400`: Validation failed (subdomain, file shape, size cap, etc.)
- `409`: The supplied email already belongs to a verified account. The response body includes `code: "EMAIL_BELONGS_TO_VERIFIED_USER"` so the client can redirect to the sign-in flow.
- `422`: The address cannot be used for a new account (ADR 0048). The body carries `code: "EMAIL_DOMAIN_NOT_ALLOWED"` (a blocked or disposable domain), `code: "EMAIL_ALIAS_NOT_ALLOWED"` (a `+` alias) or `code: "ACCOUNT_LIMIT_REACHED"` (a likely second account, ADR 0050). Existing users are never refused.

---

[API](https://skmtc.dev/nippy/apis/nippy-backend-api.md) · [All operations](https://skmtc.dev/nippy/apis/nippy-backend-api/llms.txt) · [OpenAPI document](https://skmtc.dev/nippy/apis/nippy-backend-api/revisions/7ab23f040a55?raw)
