> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mightynetworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Asset uploads

> Upload an image, file, or other media over REST and reference the returned asset ID from Mighty API mutations

GraphQL requests carry JSON, so a binary file cannot travel in one. The Mighty API uploads media through a REST endpoint instead: you `POST` the bytes and the response gives you an asset ID.

Pass that ID to a GraphQL mutation to attach the asset to a post or article, a chat message, a member's profile, an event, or a Space or Network image slot. See [Attaching uploaded assets](#attaching-uploaded-assets) for post and comment body restrictions.

<Note>
  Video and audio go through the `createUploadSession` mutation, not this endpoint. That path uploads in chunks and puts the file through transcoding, so the result plays in the product. This endpoint stores a single file in one request.
</Note>

## Endpoint

```http theme={null}
POST https://api.mn.co/networks/{network_id}/assets
```

The `{network_id}` segment takes either the Network's numeric ID (for example `12345`) or its subdomain slug (for example `my-community` for `my-community.mn.co`), the same two forms the GraphQL endpoint accepts.

Send a file as `multipart/form-data`. A `base64` or `source_url` upload carries no binary, so it can be sent as ordinary form fields instead.

<Warning>
  Requests to `api.mn.co` must include a non-empty `User-Agent` header. Requests without one are blocked by bot protection and receive an HTML challenge page with HTTP `403` — a JSON-parse error on the client. We recommend `your-app-name/version (+url)`.
</Warning>

## Authentication and scopes

Authenticate the same way you authenticate a GraphQL request: pass an OAuth access token or an Admin API key as a Bearer token.

```http theme={null}
Authorization: Bearer YOUR_ACCESS_TOKEN
```

The credential has to carry one of two scopes. A member token needs `write:assets`. A Host credential, such as an Admin API key, can carry `host:write:network_assets` instead. Either one satisfies the endpoint, and a credential holding neither receives a `403`.

See the [Authentication Guide](/api/authentication) for the OAuth flows and [OAuth Applications — Scopes](/oauth-applications#scopes) for the full scope catalog.

## Request fields

Every upload names its source with exactly one of `file`, `base64`, or `source_url`. Everything else is optional.

| Field                      | Type    | Required      | Default | Notes                                                                                                                                                                             |
| -------------------------- | ------- | ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file`                     | File    | One of three  |         | The bytes to store, sent as `multipart/form-data`.                                                                                                                                |
| `base64`                   | String  | One of three  |         | The file contents, Base64-encoded. Send `filename` and `content_type` with it.                                                                                                    |
| `filename`                 | String  | With `base64` |         | The name to store the file under, including its extension.                                                                                                                        |
| `content_type`             | String  | With `base64` |         | The MIME type of the decoded bytes, for example `image/jpeg`.                                                                                                                     |
| `source_url`               | String  | One of three  |         | An `http` or `https` URL to fetch the file from.                                                                                                                                  |
| `asset_style`              | String  | No            | `post`  | Where you intend to use the asset. See [Asset styles](#asset-styles).                                                                                                             |
| `original_aspect_ratio`    | String  | No            |         | A positive decimal such as `1.7778`, or a `W:H` ratio such as `16:9`. A ratio is stored as `W / H`.                                                                               |
| `metadata[is_main_image]`  | Boolean | No            |         | Marks the asset as the main image of the content it is attached to. See [Main image validation](#limits-and-validation) for the supported formats and the `source_url` exception. |
| `metadata[is_video_image]` | Boolean | No            |         | Marks the asset as a video's poster frame.                                                                                                                                        |

Send `metadata` either as nested form fields (`metadata[is_main_image]=true`) or as a JSON object string (`metadata={"is_main_image":true}`). Both keys are declarations, so `false` and omitting the key mean the same thing, and keys other than the two above are dropped.

## Asset styles

`asset_style` tells Mighty what the asset is for, which decides how it is stored and processed. Pick the style that matches where the asset will end up.

| Style              | What it is for                                              | Consumed by                                                                                                                                            |
| ------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `post`             | Images and files in a post or article, and post attachments | `createPost` and `updatePost` `assetIds`                                                                                                               |
| `post_description` | Images inside a post's or article's description             | Accepted for compatibility; Mighty API post mutations do not accept embedded images in `body`                                                          |
| `comment`          | Images and files on comments and chat messages              | `createMessage` and `createDirectMessage` `assetIds`; see [comment limitations](#attaching-uploaded-assets)                                            |
| `file`             | A document or other non-image attachment                    | `createPost` and `updatePost` `assetIds`                                                                                                               |
| `avatar`           | A member's profile photo                                    | `updateMember` `avatarId`                                                                                                                              |
| `thumbnail`        | A video's cover image                                       | Accepted so an Admin API upload call moves across unchanged; no Mighty API mutation attaches this style today                                          |
| `cover`            | A Space cover photo                                         | Accepted so an Admin API upload call moves across unchanged; `updateSpaceImage` and `updateNetworkImage` take any image style and restyle it on attach |
| `header`           | A Space or Network Primary Image                            | `updateSpaceImage` and `updateNetworkImage` `assetId`                                                                                                  |
| `square_thumbnail` | An event's card thumbnail                                   | `createEvent` and `updateEvent` `thumbnailId`                                                                                                          |
| `cinema_header`    | An event's wide header                                      | `createEvent` and `updateEvent` `headerId`                                                                                                             |

<Note>
  The destination slot determines the final style. A detached asset can be restyled and attached in place; reusing an asset already attached to other content can create a copy.

  For `createEvent` and `updateEvent`, `thumbnailId` cannot name an asset styled `cinema_header`, and `thumbnailId` and `headerId` must name different assets. Pass `headerId` alone to use the header on the event card when the event has no separate thumbnail.
</Note>

An `asset_style` outside the table is rejected with a `400`. So are the video and audio styles, which return a `400` pointing you at `createUploadSession`.

## Limits and validation

**Size.** A `file` or `base64` upload larger than 25 MB is rejected with a `413` before anything is stored. A `source_url` has no size until it is fetched, so a fetched file over 25 MB is rejected with a `400` and the message `Validation failed: This file must be 25MB or smaller.`

**Content types.** Uploads are checked against an allowlist of common image types (JPEG, PNG, GIF, WebP, AVIF, HEIC, HEIF, TIFF, BMP), audio types (MP3, M4A, WAV, FLAC, AIFF), video types (MP4, MOV), and document types (PDF, plain text, CSV, Word, Excel, PowerPoint, Pages). SVG, HTML, and script or executable files are refused whatever content type they declare, because they can carry executable content.

**Source URLs.** A `source_url` has to use `http` or `https`. Loopback addresses are rejected with `Invalid URL`, and private or internal addresses are refused by the server's outbound request filter, so the URL has to be reachable from the public internet. The fetch gives up after 5 seconds to connect and 5 seconds to read. A failed download returns `400` with validation details that can include missing-file, missing-content-type, and download errors. An empty stored file can return `This file is empty or failed to upload. Please try again.`

**Main image.** For `file` and `base64` uploads, `metadata[is_main_image]` is kept only when the declared content type or filename extension identifies JPEG, PNG, GIF, WebP, AVIF, HEIC, or HEIF. Either signal is enough. Other formats, including accepted image uploads such as TIFF and BMP, lose the key without failing the request. A `source_url` upload skips this check and retains the declaration regardless of the fetched file's type.

## Response

A successful upload returns `201` with the stored asset.

```json theme={null}
{
  "id": 4871203,
  "url": "https://media.example.com/asset/4871203/beach.jpg",
  "original_url": "https://files.example.com/asset/4871203/beach.jpg",
  "content_type": "image/jpeg",
  "original_filename": "beach.jpg",
  "size_bytes": 284119,
  "asset_style": "post",
  "original_aspect_ratio": 1.7777777777777777,
  "metadata": {
    "is_main_image": true
  }
}
```

| Field                   | Type          | Description                                                                       |
| ----------------------- | ------------- | --------------------------------------------------------------------------------- |
| `id`                    | Integer       | The asset ID. Pass this to the mutations listed in [Asset styles](#asset-styles). |
| `url`                   | String        | The CDN URL for the asset.                                                        |
| `original_url`          | String        | The URL of the stored original file.                                              |
| `content_type`          | String        | The stored MIME type.                                                             |
| `original_filename`     | String        | The filename the asset was stored under.                                          |
| `size_bytes`            | Integer       | The stored size in bytes.                                                         |
| `asset_style`           | String        | The style the asset was stored with.                                              |
| `original_aspect_ratio` | Float or null | What you sent, as a decimal. `null` when you sent nothing.                        |
| `metadata`              | Object        | The stored declarations. `{}` when you sent none.                                 |

### Mapping from the Admin API

If you are moving an upload call over from the [Admin API](/admin-api), its `AssetResponse` fields land here as follows.

| Admin API `AssetResponse` | Mighty API                                                               |
| ------------------------- | ------------------------------------------------------------------------ |
| `id`                      | `id`                                                                     |
| `url`                     | `url`                                                                    |
| `name`                    | `original_filename`                                                      |
| `type`                    | Not returned. Its value is always `"Asset"` for an upload made this way. |

## Errors

Endpoint errors return a JSON body of the shape `{ "error": "..." }`. The bot-protection challenge described above is an HTML response.

| Status | Message                                                                                            | Cause                                                                                                              |
| ------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `400`  | `Provide exactly one of file, base64, or source_url`                                               | The request named no source, or more than one.                                                                     |
| `400`  | `filename and content_type are required with base64`                                               | A `base64` upload was missing one of them.                                                                         |
| `400`  | `Invalid asset_style. Must be one of: ...`                                                         | `asset_style` is not in the [table above](#asset-styles).                                                          |
| `400`  | `asset_style video is a media style; upload video and audio with the createUploadSession mutation` | A video or audio style was requested. The message names the style you sent.                                        |
| `400`  | `original_aspect_ratio must be a positive number or W:H ratio`                                     | The value is not a positive decimal or a `W:H` ratio with positive sides.                                          |
| `400`  | `metadata must be a JSON object`                                                                   | `metadata` is not an object, or its JSON string does not parse as an object.                                       |
| `400`  | `Invalid URL`                                                                                      | `source_url` points at a loopback address.                                                                         |
| `400`  | `This file is empty or failed to upload. Please try again.`                                        | The stored file is empty.                                                                                          |
| `400`  | `Validation failed: ...`                                                                           | Asset validation failed, including a failed `source_url` download. Details can contain multiple validation errors. |
| `400`  | `Validation failed: This file must be 25MB or smaller.`                                            | A `source_url` fetch returned more than 25 MB.                                                                     |
| `401`  | `Invalid, expired or missing access token`                                                         | The Bearer credential is missing, expired, revoked, or not valid for this Network.                                 |
| `402`  | `This action is temporarily unavailable because this Network is out of credits.`                   | The Network is out of credits.                                                                                     |
| `403`  | `This request requires one of the write:assets, host:write:network_assets OAuth scopes`            | The credential holds neither asset write scope.                                                                    |
| `413`  | `File size must be 25MB or smaller`                                                                | A `file` or `base64` upload is over the size cap.                                                                  |

## Attaching uploaded assets

For a post or article, pass the upload response's `id` in `assetIds` on `createPost` or `updatePost`. Their `body` accepts plain text and formatting HTML. Tags such as `<img>`, `<a>`, and `<iframe>` are rejected, even when they include an asset ID.

On `updatePost`, `assetIds` replaces the attachment list. Include all IDs you want to keep, or omit `assetIds` to leave attachments unchanged.

Comments have a separate body format: `createComment` and `updateComment` sanitize the HTML you send and remove iframes. Neither mutation exposes an `assetIds` argument.

## Examples

Upload a file, mark it as the content's main image, and record its aspect ratio:

```bash theme={null}
curl https://api.mn.co/networks/my-community/assets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "User-Agent: my-app/1.0 (+https://example.com)" \
  -F "file=@beach.jpg" \
  -F "asset_style=post" \
  -F "original_aspect_ratio=16:9" \
  -F "metadata[is_main_image]=true"
```

Upload from a URL Mighty fetches for you:

```bash theme={null}
curl https://api.mn.co/networks/my-community/assets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "User-Agent: my-app/1.0 (+https://example.com)" \
  -d "source_url=https://example.com/images/beach.jpg" \
  -d "asset_style=post"
```

## Next steps

<CardGroup cols={2}>
  <Card title="Mighty API" icon="diagram-project" href="/api">
    Attach the asset with a GraphQL mutation.
  </Card>

  <Card title="Authentication" icon="key" href="/api/authentication">
    Get a token that carries an asset write scope.
  </Card>
</CardGroup>
