Schedule & Chill
DocsREST APIMedia

Media

Media

Upload a file once, then attach it to any number of posts via media_ids.

The library also works as a searchable asset store: alt_text and tags are searched as well as the filename, so a clip described as "cable crossover at the gym" is found by "gym" even though its filename is PXL_20260806_113344.mp4. Write a real alt_text at upload time — an item with no description is only findable by someone who already knows its filename.

Audio (mp3, wav, m4a, aac) is accepted as a production input — music beds, voice takes — and can never be attached to a post.

The media object

Field Type Description
id integer Unique id. Pass to a post's media_ids.
original_name string The uploaded filename.
mime_type string e.g. image/jpeg, video/mp4.
size_bytes integer File size.
cdn_url string Public URL of the file.
width / height integer|null Pixel dimensions for images.
duration number|null Seconds, for video and audio. null means it was never measured, not zero.
sha256 string|null Checksum of the stored bytes. Use it to verify a local copy without re-downloading.
folder string|null Optional organizational folder.
tags string[] Optional tags.
alt_text string|null Accessibility text.

Upload media

POST /api/media

Send as multipart/form-data.

Field Type Required Notes
file file yes jpg, jpeg, png, gif, webp, mp4, mov, webm, m4v, mp3, wav, m4a, aac, pdf. Max 90 MB — use the resumable flow above that.
folder string no Organizational folder.
tags string[] no Each tag ≤ 50 characters.
alt_text string no ≤ 500 characters.
curl -X POST https://schedulenchill.com/api/media \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -F "file=@./launch.png" \
  -F "folder=product-launch" \
  -F "tags[]=launch" \
  -F "alt_text=Product launch screenshot"

Returns 201 Created with the media object. Use its id in a post's media_ids.

Building over MCP or from a server with a public file URL? Use the upload_media tool, which ingests media by URL instead of multipart upload.


Upload a video from your own computer

The single-request upload above is capped, and a large video in one request is fragile — one dropped connection and you start over. For anything big, open a resumable session and stream the file in chunks. Nothing is capped by request size, and an interrupted upload picks up where it stopped.

This is what your AI tool does when you say "upload ~/Videos/demo.mp4 and schedule it" — the MCP upload_media tool with source="local" returns exactly the envelope below.

1. Open a session

POST /api/media/uploads
Field Type Required Notes
filename string yes Original name, with extension.
mime_type string yes e.g. video/mp4.
size_bytes integer yes Exact size. Finalize rejects a mismatch.
sha256 string no Hex digest. When given, finalize verifies against it.
folder string no Carried onto the finished library item.
tags string[] no Carried onto the finished library item.
alt_text string no Carried onto the finished library item. Set it here — a large upload that lands undescribed almost never gets one added later.

A free account may upload up to 400 MB per file; the absolute cap is 1 GB. Going over fails at session-open with a 422 that names the limit, so nothing is streamed for nothing. GET /api/capabilities reports your ceiling in limits.max_upload_bytes — read it rather than assuming.

curl -X POST https://schedulenchill.com/api/media/uploads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"filename":"demo.mp4","mime_type":"video/mp4","size_bytes":11402,
       "sha256":"86e39c5a..."}'
{
  "upload_id": "779b36fd-6d13-4c42-b3e5-c731956b093c",
  "chunk_size": 8388608,
  "received_bytes": 0,
  "size_bytes": 11402,
  "expires_at": "2026-08-04T07:35:44+00:00"
}

2. Stream the chunks

PATCH each chunk_size-byte slice as a raw body — not multipart — with offset set to the running byte count (0, chunk_size, 2 × chunk_size, …).

curl -X PATCH "https://schedulenchill.com/api/media/uploads/UPLOAD_ID?offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./chunk-0
{ "received_bytes": 11402, "size_bytes": 11402, "completed": true }

Send chunks one at a time, in order. Parallel PATCHes corrupt the assembled file. If a chunk fails, re-GET the session to read received_bytes and resume from there — that number is the source of truth, not your own bookkeeping.

3. Finalize

curl -X POST https://schedulenchill.com/api/media/uploads/UPLOAD_ID/complete \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

Returns the media object. Its id goes in a post's media_ids.

{ "id": 12, "original_name": "demo.mp4", "mime_type": "video/mp4", "size_bytes": 11402 }

Notes

  • Use a token with * abilities (the default). A restricted token 401s on the chunk PATCHes.
  • /mcp and /api/* share one auth guard, so the same bearer works for both.
  • Sessions expire — expires_at is in the envelope. Abandoned ones are pruned.
  • DELETE /api/media/uploads/{id} cancels a session you no longer want.

List media

GET /api/media

Paginated, 15 per page. Optional filters:

Query param Description
search Match filename, alt text or tags.
type image or video.
folder Filter by folder.
tags Filter by tag.
per_page Rows per page, 1–200. Over 200 returns 422.
page Page number.

Retrieve media

GET /api/media/{id}

Update media

PUT /api/media/{id}

Update metadata only — folder, tags, alt_text. The file itself is immutable; upload a new file to replace it.


Delete media

DELETE /api/media/{id}

Returns 204 No Content.