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

# Upload assets

> Upload a media file and attach it to an OpenPMM Post.

OpenPMM uses a multipart upload for each file. Each part is 8 MiB, except the final part.

Read the [media guidelines](/docs/media/overview) before you upload. Each destination has its own image limits and video limits.

<Steps>
  <Step title="Begin the upload">
    Calculate the CRC64NVME checksum for each part. Send the checksums in part order.

    ```json theme={null}
    {
      "kind": "video",
      "file_name": "launch-video.mp4",
      "content_type": "video/mp4",
      "byte_size": 248031,
      "parts": [
        {
          "part_number": 1,
          "checksum_crc64nvme": "rosUhgp5mIg="
        }
      ]
    }
    ```

    OpenPMM returns one `upload_url` for each part. It also returns the required headers for each URL.

    Use `video` for every video file. This value does not select a YouTube
    Short or a provider Reel placement. Set that choice in the Post destination
    options.
  </Step>

  <Step title="Upload the bytes">
    Send each part to its `upload_url`. Include all values from `required_headers`.

    Do not send the OpenPMM API key to an upload URL. Save the `ETag` response header for each part.

    You can retry a failed part with the same URL. A retry replaces the earlier data for that part number.
  </Step>

  <Step title="Complete the upload">
    Calculate the CRC64NVME checksum for the full file. Send the checksum, size, and completed parts.

    ```json theme={null}
    {
      "byte_size": 248031,
      "checksum_crc64nvme": "rosUhgp5mIg=",
      "parts": [
        {
          "part_number": 1,
          "etag": "\"part-etag\"",
          "checksum_crc64nvme": "rosUhgp5mIg="
        }
      ]
    }
    ```

    Call `POST /workspaces/{workspace_id}/asset-uploads/{upload_id}/complete`. OpenPMM checks the stored file and returns the asset.
  </Step>
</Steps>

Store the returned asset `id`. Add the ID to the post `media` array for the
opening post. Use `media_items` to select a body item in a self-reply chain:

```json theme={null}
{
  "body": ["Opening post", "Reply with an image"],
  "media_items": [
    { "asset_id": "ast_01JABCDEF", "item_index": 1 }
  ]
}
```

The first body item has the `item_index` value `0`.

Attach the returned Asset to a Post and publish normally. If the file cannot
publish to a selected Destination, the publication request returns
`media_change_required`. See [Resolve media changes](/docs/guides/media-compatibility).

<Warning>
  The upload URLs expire at `expires_at`. Create a new upload after this time.
</Warning>
