---
title: "Attach a file to a post by reference"
method: POST
path: "/attach-media/{postGroupId}"
tags: ["Media"]
---

# Attach a file to a post by reference

`POST /attach-media/{postGroupId}`

## Request

- Base URL: `https://api.publora.com/api/v1`
- URL: `https://api.publora.com/api/v1/attach-media/{postGroupId}`
- Auth: API key in header `x-publora-key`

Attach one image or video to an existing draft or scheduled post using a
file reference: an HTTPS download URL plus a stable file ID, the shape
ChatGPT supplies through `openai/fileParams` to the MCP `attach_media`
tool. Publora downloads the file right away through the same guarded
pipeline as `mediaUrls`, which detects the type from the bytes and probes
the file. Accepted: JPEG, PNG, GIF, WebP, TIFF and AVIF images up to 25 MB;
MP4, MOV and WebM videos up to 150 MB. The file is attached with status
`ready`, and no presigned PUT or complete-media call is needed.
The post is always left in `draft`. A scheduled post is demoted to draft
and keeps its stored time; schedule it again with update-post. The body
accepts only `file` and `fileName`, and publishing parameters are rejected
with INVALID_MEDIA_FILE. The response has the update-post shape and does
not include the media ID, so read `media[]` from get-post. Each attempt that
reaches the download counts one URL against the media URL ingestion limit
of 60 URLs per hour, shared with `mediaUrls`; idempotent replays don't count.

## Path parameters

- `postGroupId` string, required

## Headers

- `x-publora-user-id` string
- `Idempotency-Key` string

## Request body

- object
  - `file` object, required: File reference. A string is rejected, including a local path, base64 data or a bare URL.
    - `download_url` string, uri, required: HTTPS URL to download from; temporary signed URLs work. Same checks as mediaUrls: https only, no embedded credentials, port 443, no private or loopback hosts.
    - `file_id` string, required: Stable file identifier; part of the idempotency fingerprint
    - `mime_type` string: Hint only; the type is detected from the bytes
    - `file_name` string: Original file name; used when fileName is absent
  - `fileName` string: Descriptive file name with an extension. Takes precedence over `file.file_name`; if both are absent the name is `chatgpt-media`. Only the last path segment is kept; it is trimmed, each run of whitespace becomes `_`, and any character other than an ASCII letter, digit, `_`, `.` or `-` is removed. A name that ends up empty or only dots is rejected, including one taken from `file.file_name`. get-post returns the result as `media[].sourceFileName`.

## Response `200`

File downloaded, validated and attached; the post is in draft

- object
  - `success` boolean
  - `message` string
  - `scheduledTime` string, date-time, nullable: The stored scheduled time. A post demoted from scheduled keeps its time while in draft; `null` when no time is stored.
  - `postGroup` object
    - `_id` string
    - `status` 'draft'
    - `content` string
    - `platforms` string[]
    - `scheduledTime` string, date-time: Only included when a scheduled time is stored

## Other responses

- `400`: One of: invalid file input (INVALID_MEDIA_FILE, nothing downloaded); an invalid post ID; a failed download or validation (`Media ingestion failed` with per-URL `mediaResults`, nothing attached, and the idempotency key freed); a post that can no longer be edited (POST_NOT_EDITABLE); or a YouTube target connection that is no longer the caller's (INVALID_PLATFORM_CONNECTION, downloaded file discarded).
- `401`: Invalid API key
- `404`: Post not found or owned by another user
- `409`: Nothing was changed. Either a request with this `Idempotency-Key` is still in flight, or the post is being published, or another write committed first. Match on `code`, and re-read the post with get-post before retrying.
- `422`: This `Idempotency-Key` was already used for a different post, file ID or file name. Nothing was changed.
- `429`: Media URL ingestion rate limit reached (60 URLs per fixed one-hour window)
- `500`: Server error

## Changes

- **2026-09-30** `4741f235a2fd`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/publora/apis/publora-api/changes/attach-media/:postGroupId/post.md)

---

[API](https://skmtc.dev/publora/apis/publora-api.md) - [All operations](https://skmtc.dev/publora/apis/publora-api/llms.txt) - [OpenAPI document](https://skmtc.dev/publora/apis/publora-api/revisions/f93f96e52798?raw)
