Upload a file ahead of the post and you get a media_id. Use it in place of the file on any post: the same video to several profiles, a retry with new settings, or captions written from it, without sending the bytes again. Files over 100 MB go up in parts, straight to storage.
Upload a file
Send the file in a multipart field named file, and say what it is with kind in the query: video (the default), photo or document.
curl -X POST "https://api.reelspan.com/v1/media/upload?kind=video" \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-F "[email protected]"import { Reelspan } from 'reelspan';
import { readFile } from 'node:fs/promises';
const reelspan = new Reelspan();
const media = await reelspan.media.upload({ data: await readFile('launch.mp4'), name: 'launch.mp4', type: 'video/mp4' }, { kind: 'video' });from reelspan import Reelspan
reelspan = Reelspan()
media = reelspan.media.upload("launch.mp4", kind="video"){
"success": true,
"media_id": "med_Xb7Qk2LmN4pR8sTv0wYz",
"kind": "video",
"content_type": "video/mp4",
"size_bytes": 48211337,
"duration_seconds": 42.5
}duration_seconds is filled in for videos and null for photos and documents.
kind |
Accepts | Largest file |
|---|---|---|
video |
MP4, MOV, WebM | Your plan's upload limit (Plans and limits) |
photo |
JPEG, PNG, WebP, GIF, HEIC | 20 MB |
document |
PDF, PPTX, PPT, DOCX, DOC | 100 MB |
Reelspan checks the file's real type from its contents, not its name. A file of the wrong kind gets 400; one over the limit gets 413 FILE_TOO_LARGE.
Add save=true (and an optional title) to the query to keep the file in the media library right away.
Post it by media_id
Use the id in the field for its role:
| Post | Field |
|---|---|
| Video | video_media_id |
| Video thumbnail (YouTube) | thumbnail_media_id (a photo upload) |
| Video cover (TikTok) | cover_media_id (a photo upload) |
| Photos | photo_media_ids[], or {network}_photo_media_ids[] for one network; videos work here too for Instagram and Threads carousels |
| Document | document_media_id (a document upload) |
| AI captions | media_id |
curl -X POST https://api.reelspan.com/v1/posts/video \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-F "user=my-brand" \
-F "platform[]=tiktok" \
-F "platform[]=youtube" \
-F "title=Launch day" \
-F "video_media_id=med_Xb7Qk2LmN4pR8sTv0wYz"await reelspan.posts.video({ user: 'my-brand', platforms: ['tiktok', 'youtube'], title: 'Launch day', videoMediaId: media.media_id });reelspan.posts.video("my-brand", ["tiktok", "youtube"], title="Launch day", video_media_id=media["media_id"])An id of another workspace, or of the wrong kind (a photo as video_media_id), gets 400 MEDIA_NOT_FOUND.
How long an upload lasts
- An upload that no post uses is deleted after a day.
- Once a post that uses it has finished, the file is kept for a few days (7 by default) so you can retry, then deleted, unless another waiting post still needs it.
- Files kept in the media library stay until you remove them, so keep anything you'll post again later.
Large files, in parts
A single request carries at most 100 MB, so send bigger files straight to storage in parts. It takes three steps.
1. Start the upload with the kind and the exact size in bytes. You get a media_id, the part_size, and one signed URL per part:
{
"success": true,
"kind": "video",
"media_id": "med_Q9vT3kLw8ZpX2nB6yR1s",
"part_size": 8388608,
"parts": [
{ "part_number": 1, "url": "https://…" },
{ "part_number": 2, "url": "https://…" }
],
"expires_in": 3600
}Parts are at least 8 MB, and a file is split into 1,000 parts at most. The size is checked against your plan here (413 if it's over).
2. Upload each part with a PUT to its URL. Part n is the bytes from (n - 1) × part_size, part_size long; the last part is whatever is left. Each URL is signed for its part's exact length, so a body of any other size is refused. Keep the ETag header from each answer, exactly as it comes (with its quotes). Parts can go up in parallel and in any order. The URLs work for expires_in seconds (an hour).
3. Complete it with every part's number and ETag. Reelspan then reads the stored file's real size and type; one that isn't the kind you declared, or is over your plan's limit, is deleted and refused with 400 or 413. The answer is the same as a single upload's, and the media_id is ready to post.
FILE=film.mp4
SIZE=$(stat -c%s "$FILE") # macOS: stat -f%z
API=https://api.reelspan.com/v1
AUTH="Authorization: Bearer $REELSPAN_API_KEY"
START=$(curl -s -X POST "$API/media/uploads" -H "$AUTH" -H "Content-Type: application/json" \
-d "{\"kind\": \"video\", \"size_bytes\": $SIZE, \"filename\": \"$FILE\"}")
MEDIA_ID=$(echo "$START" | jq -r .media_id)
PART_SIZE=$(echo "$START" | jq -r .part_size)
PARTS='[]'
for row in $(echo "$START" | jq -c '.parts[]'); do
N=$(echo "$row" | jq -r .part_number)
URL=$(echo "$row" | jq -r .url)
ETAG=$(dd if="$FILE" bs="$PART_SIZE" skip=$((N - 1)) count=1 2>/dev/null \
| curl -s -X PUT --data-binary @- -D - -o /dev/null "$URL" \
| grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r')
PARTS=$(echo "$PARTS" | jq --argjson n "$N" --arg e "$ETAG" '. + [{part_number: $n, etag: $e}]')
done
curl -X POST "$API/media/uploads/$MEDIA_ID/complete" -H "$AUTH" -H "Content-Type: application/json" \
-d "{\"parts\": $PARTS}"// media.upload switches to parts on its own from 90 MB: three at a time, each retried on its own
const media = await reelspan.media.upload(file, {
kind: 'video',
onProgress: (sent, total) => console.log(Math.round((sent / total) * 100), '%'),
});# media.upload switches to parts on its own from 90 MB, reading the file from disk piece by piece
media = reelspan.media.upload("film.mp4", kind="video", on_progress=lambda sent, total: print(f"{sent * 100 // total}%"))The SDKs also hand a large file to this flow when you pass it straight to posts.video.
Stop an upload
To give up on an upload you started, call POST /v1/media/uploads/{media_id}/abort. Storage drops the parts sent so far and the media_id stops working. An upload that is never completed expires after 24 hours; completing or aborting it after that gets 404 UPLOAD_NOT_FOUND, and you start again.
curl -X POST "https://api.reelspan.com/v1/media/uploads/med_Q9vT3kLw8ZpX2nB6yR1s/abort" \
-H "Authorization: Bearer $REELSPAN_API_KEY"More on big files in Large files.