# Upload File

Upload an image or video to your haunt.gg ImageHost.

Upload an image or video to your haunt.gg ImageHost. The file is stored in your
personal bucket, gets a randomly generated name, and is served from your
configured ImageHost CDN. Folder assignment is handled automatically by your
folder rules.

**POST** `/api/imagehost/upload`

## Authorizations

| Name | Type | In | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | string | header | yes | API key from your dashboard with the `imagehost:upload` permission. Treat it like a password. |

## Request Body

The request must be sent as `multipart/form-data`.

| Name | Type | In | Required | Description |
| --- | --- | --- | --- | --- |
| `file` | File | body | yes | The file to upload. Allowed types: `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/avif`, `video/mp4`, `video/webm`, `video/quicktime`. |

> [!NOTE]
> The destination folder is determined server-side by your configured folder
> rules (matched against MIME type, extension, or original filename). There is
> no `folder` parameter — manage routing from the dashboard.

## Example Request

  #### cURL

```bash
curl -X POST https://haunt.gg/api/imagehost/upload \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@./screenshot.png"
```
  #### JavaScript

```ts
const form = new FormData();
form.append("file", file);

const res = await fetch("https://haunt.gg/api/imagehost/upload", {
  method: "POST",
  headers: { "X-API-Key": apiKey },
  body: form,
});
const data = await res.json();
```
  #### Python

```python
import requests

with open("screenshot.png", "rb") as f:
    res = requests.post(
        "https://haunt.gg/api/imagehost/upload",
        headers={"X-API-Key": api_key},
        files={"file": f},
    )
data = res.json()
```

## Response

  #### 200 — File uploaded successfully.

```json
{
  "ok": true,
  "url": "https://i.haunt.gg/aB3xK9pQ.png",
  "id": "c2f1a4d3-8b6e-4f12-9a5d-2e7c1b8a3f04",
  "fileName": "aB3xK9pQ",
  "fileExtension": "png",
  "fileSize": 18234,
  "mime": "image/png"
}
```
  #### 400 — No file was attached to the request.

```json
{
  "error": "Missing file.",
  "code": "NO_FILE"
}
```
  #### 400 — The file type is not allowed or could not be detected.

```json
{
  "error": "Unsupported file type.",
  "code": "INVALID_MIME"
}
```
  #### 400 — Uploading this file would exceed your storage quota.

```json
{
  "error": "Storage quota exceeded.",
  "code": "QUOTA_EXCEEDED"
}
```
  #### 401 — The API key is missing, invalid, or lacks the imagehost:upload permission.

```json
{
  "error": "User not found.",
  "code": "UNAUTHORIZED"
}
```
  #### 413 — The file exceeds the maximum allowed size for your plan.

```json
{
  "error": "File exceeds 78643200 bytes.",
  "code": "FILE_TOO_LARGE"
}
```
  #### 500 — The upload to storage failed. Retry the request.

```json
{
  "error": "Upload failed.",
  "code": "UPLOAD_FAILED"
}
```

## Limits

Per-file and total storage limits depend on your plan:

| Plan        | Max file size | Total storage |
| ----------- | ------------- | ------------- |
| Free        | 75 MB         | 1 GB          |
| Premium     | 150 MB        | 10 GB         |
| ImageHost   | 250 MB        | 100 GB        |

> [!WARNING]
> Files exceeding your plan's per-file limit are rejected with
> `413 FILE_TOO_LARGE`. If your bucket is full, new uploads return
> `400 QUOTA_EXCEEDED` — delete files or upgrade your plan to free up space.
