---
title: "Create media upload URL"
method: POST
path: "/api/media/uploads"
tags: ["library"]
---

# Create media upload URL

`POST /api/media/uploads`

## Request

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

Create a short-lived URL to upload a local file into a business's MEDIA library.

USE THIS when the file is on a device or sandbox disk and has no public URL
(media already on the web goes through `upload_media_from_url` instead).
PUT the raw bytes to the returned `upload_url` (see `instructions` for the exact
curl command); the PUT response is the created media asset, whose `original_url`
feeds `create_post` and the rest of the API. From a hosted Claude client, run the
curl in the code-execution sandbox — the user's chat attachment is on its disk.

PREFER letting Marky FETCH the image over pushing it: if the image has any URL
(a generated-image output URL, a web image, a host-provided download URL), skip
this tool and use `upload_media_from_url` or pass the URL to `create_post` in
`media_urls` — Marky downloads it server-side, so it works even when your sandbox
blocks outbound network. Only use this PUT flow when the image is local-only AND
your runtime can make outbound web requests. On ChatGPT, prefer
`upload_media_from_reference` (ChatGPT fills the file automatically and Marky fetches
it server-side, so the sandbox never makes the outbound upload). If the PUT fails
with a connection
error, your sandbox is blocking egress: don't retry — fall back to a URL Marky can
fetch, or tell the user the image couldn't be attached (don't post `media_urls: null`).

The URL expires after 1 hour and is bound to this business. Re-PUTting the same
bytes safely returns the original asset; different bytes on the same URL are
rejected.

Errors:
    400 invalid_value: The business_id is not a valid UUID.
    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.
    429 rate_limit_exceeded: You sent too many requests this minute.

## Headers

- `Marky-Version` '2026-04-08' | '2026-07-17' | '2026-08-02' | '2026-08-04' | '2026-08-06'

## Request body

- MediaUploadCreateRequest
  - `business_id` string, required: The business whose library the file will land in. Get it from `list_businesses`.
  - `filename` string, nullable: Original filename, e.g. 'photo.jpg'. Helps detect videos by extension.
  - `content_type` string, nullable: MIME type, e.g. 'image/jpeg' or 'video/mp4'. The PUT request's Content-Type header takes precedence.
  - `alt_text` string, nullable: Alt text stored on the uploaded media.
  - `source` 'max_video', nullable: Set only by Marky's own agent for media it generated, so the library can tell it apart from the business's uploads. Leave unset for a real upload.

## Response `201`

Successful Response

- MediaUploadCreateResponse
  - `upload_id` string, required: Unique id for this upload.
  - `upload_url` string, required: PUT the raw file bytes here. Valid for expires_in seconds.
  - `expires_in` integer, required: Seconds until the upload_url stops working.
  - `max_bytes` integer, required: Size cap: 20 MB for images, 500 MB for video.
  - `instructions` string, required: Copy-paste curl command that performs the upload and prints the created media as JSON.

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