Skip to content
reelspanDocs

Publish

Upload media

Upload a video, photo or document once and get a media_id to post it many times, and send files over 100 MB straight to storage in parts.

POSTapi.reelspan.com/v1/media/uploadTry it →

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]"
{
  "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"

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

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.

POSTapi.reelspan.com/v1/media/uploadTry it →

Parameters

  • kindstring · query

    videophotodocument

Request body

multipart/form-data

  • filestringrequired

    The file (binary)

Responses

200 Stored

{
  "success": true,
  "media_id": "string",
  "kind": "video",
  "content_type": "string",
  "size_bytes": 0,
  "duration_seconds": 0
}

400 Not a supported file

{
  "success": false,
  "message": "string",
  "error": "string",
  "error_code": "string"
}

413 Too large for the plan

{
  "success": false,
  "message": "string",
  "error": "string",
  "error_code": "string"
}
POSTapi.reelspan.com/v1/media/uploadsTry it →

Request body

application/json

  • kindstringrequired

    videophotodocument

  • size_bytesintegerrequired
  • filenamestring

Responses

200 Part URLs

{
  "success": true,
  "media_id": "string",
  "part_size": 0,
  "parts": [
    {
      "part_number": 0,
      "url": "string"
    }
  ],
  "expires_in": 0
}

413 Larger than the plan allows

{
  "success": false,
  "message": "string",
  "error": "string",
  "error_code": "string"
}
POSTapi.reelspan.com/v1/media/uploads/{media_id}/completeTry it →

Parameters

  • media_idstring · pathrequired

Request body

application/json

  • partsobject[]required
    • part_numberintegerrequired
    • etagstringrequired

Responses

200 Stored

{
  "success": true,
  "media_id": "string",
  "kind": "video",
  "content_type": "string",
  "size_bytes": 0,
  "duration_seconds": 0
}

400 Not the expected kind of file, or unsupported

{
  "success": false,
  "message": "string",
  "error": "string",
  "error_code": "string"
}

404 Unknown or expired upload

{
  "success": false,
  "message": "string",
  "error": "string",
  "error_code": "string"
}

Something unclear or missing? Write to [email protected] and we’ll fix the page.