---
title: "Upload media from a host file reference"
method: POST
path: "/api/media/from-reference"
tags: ["library"]
---

# Upload media from a host file reference

`POST /api/media/from-reference`

## Request

- Base URL: `https://api.mymarky.ai`
- URL: `https://api.mymarky.ai/api/media/from-reference`
- Auth: HTTP bearer

ChatGPT-only: attach a file ChatGPT holds, fetched server-side. DO NOT hand-call.

ChatGPT fills the `file` argument AUTOMATICALLY from the user's attachment or a
generated image (its Apps SDK marks this argument as a file param) and passes a
reference carrying a `download_url`. Marky downloads the bytes server-side and
stores a durable copy — so it works even though ChatGPT's code sandbox blocks the
outbound upload that `create_media_upload` needs. The returned `original_url` is the
Marky-hosted link you feed to `create_post` / `attach_media_to_file`.

DO NOT select this tool yourself. If you are NOT ChatGPT — or you don't already
have a real host-issued `download_url` for the file — use `upload_media_from_url`
instead (when you have a URL), or `create_media_upload` (a local file when your
runtime can make outbound web requests). There is no local-file access here, only
a server-side fetch of the URL in `file.download_url`, so calling this without a
genuine download_url just fails — don't invent one.

Send an `Idempotency-Key` header to make a retry safe (a resend that fetches
the same bytes returns the original media asset instead of a duplicate).

Supported formats: JPEG, PNG, WebP, GIF, MP4, MOV. Images are automatically
optimized. Max size: 20 MB for images, 500 MB for video.

Errors:
    400 invalid_value: The business_id is not a valid UUID, the download_url is not fetchable (bad scheme, private address, HTTP error), or the content is not a supported image or video.
    401 authentication_required: API key or token is missing, invalid, or revoked.
    403 permission_denied: Your plan does not include API access, or this business is not in your org.
    404 resource_not_found: No business with that ID exists.
    413 FILE_TOO_LARGE: The fetched media is larger than the size limit.
    422 IDEMPOTENCY_KEY_CONFLICT: The Idempotency-Key was reused with a different body.
    429 rate_limit_exceeded: You sent too many requests this minute.

## Headers

- `Idempotency-Key` string, nullable: Optional. Send a unique value (e.g. a UUID) to make this create request safe to retry. A repeat with the same key returns the original response instead of creating a duplicate. Cached for 24h, scoped to your org. Reusing a key with a different body returns 422.
- `Marky-Version` '2026-04-08' | '2026-07-17' | '2026-08-02' | '2026-08-04' | '2026-08-06'

## Request body

- MediaUploadFromReferenceRequest: JSON body for the ChatGPT-native media upload (fetch by host file reference). ChatGPT fills `file` automatically from the user's attachment or a generated file, and Marky fetches the bytes server-side — no outbound upload from ChatGPT's code sandbox, which hosted interpreters block (so the presigned-PUT flow of `create_media_upload` fails there). See `upload_media_from_reference`.
  - `business_id` string, required: UUID of the business to upload the media into. Get it from `list_businesses`. Must belong to the API key's org.
  - `file` HostFileReference, required: A host-provided handle to a file the AGENT's platform holds for us to fetch. ChatGPT's Apps SDK substitutes this object for a tool argument the tool marks via tool-level `openai/fileParams` (stamped on the public MCP tool in main.py). Instead of the model emitting raw bytes (impossible past a few hundred KB) or the sandbox PUTting to a presigned URL (hosted ChatGPT sandboxes block outbound uploads), ChatGPT passes the file BY REFERENCE and Marky fetches `download_url` server-side. See `upload_media_from_reference`.
    - `download_url` string, required: Server-fetchable http(s) URL for the file bytes. Marky downloads this server-side (SSRF + size guarded). Private and internal addresses are rejected.
    - `file_id` string, nullable: The host's opaque id for the file. Informational only.
    - `mime_type` string, nullable: MIME type the host reports (e.g. `image/png`). Advisory only — Marky sniffs the actual bytes.
    - `file_name` string, nullable: Original filename (e.g. `chart.png`). Helps detect video vs image when the URL and content type are ambiguous.
  - `alt_text` string, nullable: Optional hint about the media: an address, a name, anything the picture can't show. Stored as `source_alt_text`; `alt_text` is written by looking at the image itself shortly after upload.

## Response `201`

Successful Response

- MediaUploadResponse: What an upload route returns: the new media, plus what the caller should know. A subclass rather than new fields on MediaResponse, because MediaResponse is also every library READ. `source_alt_text` and `warnings` only mean something at the moment of upload, when the agent can still act on them.
  - `id` string, required: Media ID
  - `business_id` string, required: Business this media belongs to
  - `alt_text` string, nullable: Alt text description
  - `original_url` string, nullable: Media URL
  - `type` string, nullable: Media type: image, video, or audio
  - `width` integer, nullable: Width in pixels
  - `height` integer, nullable: Height in pixels
  - `source` string, nullable: Media origin (e.g. upload, unsplash)
  - `created_at` string, required: When the media was uploaded
  - `source_alt_text` string, nullable: The alt_text you sent, kept as a hint. `alt_text` is written a few seconds after upload by looking at the image itself, so it is empty in this response. Search results show that description.
  - `warnings` FieldWarning[], required: Non-blocking notes about this media, empty when there is nothing to say. `low_resolution` means the long edge is under 1080px, so it will look soft on most feeds; tell the user and prefer a larger copy.
    - `field` string, required: The field this warning is about, e.g. 'strategy', 'caption' or 'platform_overrides[0].caption'.
    - `code` string, nullable: Stable machine-readable code, e.g. 'cta_without_contact'. Branch on this, never on the message. Null on the older size-budget warnings.
    - `level` 'approaching_limit' | 'over_limit' | 'advisory', required: 'approaching_limit' = past the soft limit, act soon. 'over_limit' = past the hard limit, act now. 'advisory' = worth fixing before this publishes, but it may be intended.
    - `message` string, required: Plain-language explanation of what is wrong and how to fix it.

## Other responses

- `400`: Bad request — the input was malformed or otherwise invalid.
- `401`: Authentication failed — the API key is missing or invalid.
- `422`: Validation error — one or more fields failed validation.
- `429`: Rate limited — retry after the window resets (see Retry-After).
- `500`: Server error — something failed on our side; retry or quote X-Request-Id.

---

[API](https://skmtc.dev/mymarky/apis/marky-public-api.md) · [All operations](https://skmtc.dev/mymarky/apis/marky-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mymarky/apis/marky-public-api/revisions/25aeacae455c?raw)
