---
title: "Upload media (from URL)"
method: POST
path: "/api/media/from-url"
tags: ["library"]
---

# Upload media (from URL)

`POST /api/media/from-url`

## Request

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

Add an image or video to a business's MEDIA library by giving Marky a URL to fetch.

PREFER THIS whenever the media has any http(s) URL (a generated-image output URL,
a web image, a CDN or file-host link). It is the simplest, most reliable path: Marky
downloads it server-side, so it works even when your runtime can't make outbound
requests. Only reach for `create_media_upload` (a local file AND your runtime has
outbound network) or `upload_media_from_reference` (ChatGPT only, auto-filled) when
you do NOT have a URL. Pass the http(s) URL; we store a durable copy. Private and
internal addresses (and redirects to them) are rejected.

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 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

- MediaUploadFromUrlRequest: JSON body for the fetch-by-URL media upload (the agent-friendly big-file upload). Base64 over MCP tops out fast — a 24 MB video is ~32 MB of JSON in a single tool call, which most agent runtimes refuse. An agent whose media is already reachable over http(s) sends the URL instead and we fetch it server-side.
  - `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.
  - `url` string, required: Public http(s) URL of the image or video to fetch. Private and internal addresses are rejected. Max size: 20 MB for images, 500 MB for video.
  - `filename` string, nullable: Optional original filename (e.g. `clip.mp4`). Helps detect video when the URL path and server content type don't make it obvious.
  - `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.
- `409`: Conflict — a resource like this already exists.
- `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)
