REST API
Media
Three calls to attach an image.
Files go straight from you to storage. They never pass through our API, so a large upload does not sit in a request.
POST/v1/media/presign
Ask for an upload URL and a media id.
| Name | Type | Description |
|---|---|---|
fileNameRequired | string | Original file name. |
contentTypeRequired | string | image/jpeg, image/png |
bytesRequired | number | File size in bytes. |
width | number | Pixel width, if you know it. |
height | number | Pixel height, if you know it. |
altText | string | What the image shows, for screen readers. Applied to carousel items. |
What comes back
| Name | Type | Description |
|---|---|---|
id | string | Pass this to publish as a mediaId. |
uploadUrl | string | One-time PUT target. |
contentType | string | Send exactly this on the PUT. |
expiresIn | number | Seconds the uploadUrl stays valid. |
mediaExpiresAt | string | ISO 8601. After this the media id is reclaimed if it was never published. |
# 1. ask for a slot
curl -X POST https://api.uplika.com/v1/media/presign \
-H "authorization: Bearer $UPLIKA_KEY" \
-H "content-type: application/json" \
-d '{"fileName":"cover.jpg","contentType":"image/jpeg","bytes":184320,"altText":"A cat on a windowsill"}'
# -> { "id": "med_01H...", "uploadUrl": "https://...", "mediaExpiresAt": "2026-08-12T..." }
# 2. send the bytes straight to storage (no auth header)
curl -X PUT "$UPLOAD_URL" -H "content-type: image/jpeg" --data-binary @cover.jpg
# 3. confirm
curl -X POST https://api.uplika.com/v1/media/med_01H.../complete \
-H "authorization: Bearer $UPLIKA_KEY"Upload the bytes
PUT the file to the uploadUrl with the same Content-Type you declared. No auth header. The URL is already signed and it expires.
POST/v1/media/{id}/complete
Confirm the upload. We check the object really exists.
What if the file is already on the web?
Agents without a shell cannot PUT bytes, so the three step upload is out of reach for them. Give us the URL instead and we download the file, copy it into our storage and hand back a media id. We never pass someone else’s URL on to the channel. https only, and Google Drive or Dropbox share links do not work because they return an HTML preview instead of the file.
POST/v1/media/from-url
Copy a public file into your workspace in one call.
| Name | Type | Description |
|---|---|---|
urlRequired | string | Public https URL of the file itself. |
altText | string | What the image shows, for screen readers. |
What we accept
| Name | Type | Description |
|---|---|---|
types | image | image/jpeg, image/png |
size | image | up to 8MB |
aspect ratio | image | up to 10:1 (any pixel width) |
count | image | up to 20 per post on Threads |
types | video | video/mp4, video/quicktime |
size | video | up to 2048MB |
length | video | up to 43200 seconds |
width | video | up to 3840px |
carousel | mixed | 2-20 items on Threads, images and video can be mixed |
Common questions
How many calls does one image take?
Three. Presign for an id and an upload URL, PUT the bytes to that URL, then complete to confirm. Only after the third can publish use the id.
Does the file go through your API?
No. It goes straight from you to storage on a signed URL, so a large upload never sits inside one of our requests.
Can I put an image and a video in the same post?
On Threads, yes, in a carousel of two to twenty items. Channels that do not allow it refuse with media_mixed, and the limits endpoint tells you which is which per channel.