Browse docs
The flow at a glance#
Four calls, all authenticated with your usual API key or OAuth token. The bytes go straight from your machine to storage — SkedCast's API only ever sees a URL.
- POST /media/presign — mint an assetId + upload URL.
- PUT <url> — upload the raw bytes.
- POST /media — register the upload; this is what starts scanning + transcoding.
- POST /posts — compose, referencing the assetId in mediaIds.
1. Presign an upload#
clientId is optional — an asset can be agency-wide. Declare the real filename, MIME type, and byte size; they're re-verified server-side (magic bytes, not the declared contentType) once the file lands.
curl -X POST https://api.skedcast.com/v1/media/presign \
-H "Authorization: Bearer sked_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"clientId": "<clientId>",
"filename": "launch-teaser.mp4",
"contentType": "video/mp4",
"sizeBytes": 24831992
}'{
"data": {
"assetId": "b3f1a2c4-5d6e-4f70-8a9b-1c2d3e4f5a6b",
"key": "acme-agency/…/b3f1a2c4-…/launch-teaser.mp4",
"url": "https://<bucket>.r2.cloudflarestorage.com/…",
"expiresAt": "2026-08-06T15:10:00Z"
},
"meta": { "correlationId": "c-…" }
}2. Upload the bytes#
PUT the file straight to the presigned url from step 1 — no Authorization header, no SkedCast involvement. Match the Content-Type you declared at presign.
curl -X PUT "<url from step 1>" \
-H "Content-Type: video/mp4" \
--data-binary @launch-teaser.mp43. Register the asset#
Registering is what starts the pipeline — a virus scan, then transcoding into the renditions each platform needs. The asset is status: "pending" until that finishes; poll GET /media/:id or subscribe to the media.ready webhook event.
platforms is optional. Omit it and SkedCast prepares a rendition for every platform you have a live connected account on (scoped to clientId when you send one) — the truthful upper bound on where this asset could actually be published. Pass an explicit list, e.g. ["youtube", "tiktok"], to narrow further and skip renditions you know you won't need. If a post later targets a platform with no rendition yet, the publish pipeline fills the gap on demand and parks that target until it's ready — it doesn't fail.
curl -X POST https://api.skedcast.com/v1/media \
-H "Authorization: Bearer sked_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"assetId": "b3f1a2c4-5d6e-4f70-8a9b-1c2d3e4f5a6b",
"key": "acme-agency/…/b3f1a2c4-…/launch-teaser.mp4",
"clientId": "<clientId>",
"contentType": "video/mp4",
"sizeBytes": 24831992,
"filename": "launch-teaser.mp4",
"platforms": ["youtube", "tiktok"]
}'{
"data": { "assetId": "b3f1a2c4-5d6e-4f70-8a9b-1c2d3e4f5a6b", "status": "pending" },
"meta": { "correlationId": "c-…" }
}curl -X POST https://api.skedcast.com/v1/media/import-url \
-H "Authorization: Bearer sked_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "clientId": "<clientId>", "url": "https://example.com/launch-teaser.mp4" }'4. Compose, with an optional video cover#
Once the asset is transcoded (or even while it's still processing — the publish pipeline waits), reference its assetId in mediaIds on POST /posts as usual.
For a video, you can also set a custom cover/thumbnail. A cover is a separately uploaded IMAGE asset — run it through the exact same presign → PUT → register sequence above — and pass its assetId as coverMediaId on the post body. YouTube, Instagram, Facebook, and Telegram accept a custom cover image this way; omit coverMediaId to use the auto-extracted poster frame instead.
TikTok doesn't take a cover image — it takes a timestamp. Set coverFrameMs (milliseconds into the video) to pick which frame TikTok uses; it's independent of coverMediaId and applies even when you also set an image cover for the other platforms.
Setting a cover is best-effort by contract: it never fails the publish. Check the outcome afterward via coverIssue on GET /post-targets/:id (see the API reference's Post targets section) — null means it applied cleanly (or there was nothing to apply), a short string explains a rejection, a failed fetch, or an unconfirmed result.
curl -X POST https://api.skedcast.com/v1/posts \
-H "Authorization: Bearer sked_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"clientId": "<clientId>",
"content": "Launch day is here 🚀",
"mediaIds": ["<videoAssetId>"],
"coverMediaId": "<coverImageAssetId>",
"targets": { "mode": "selection", "accountIds": ["<accountId>"] },
"schedule": { "type": "at", "scheduledAt": "2026-08-10T15:00:00Z" }
}'FAQ
- Do I need to wait for transcoding before composing the post?
- No. You can compose against a still-processing asset — the publish pipeline waits for the platform's rendition to be ready, and if a rendition wasn't prepared at all (e.g. you narrowed platforms and later target a different one), it's produced on demand and the target is parked until it lands.
- What's the difference between coverMediaId and coverFrameMs?
- coverMediaId points at a separately uploaded IMAGE asset used as the cover on platforms that accept one (YouTube, Instagram, Facebook, Telegram). coverFrameMs is a millisecond offset into the video itself, sent to TikTok as its native cover-frame timestamp — TikTok has no image-cover mechanism. They're independent and can both be set on the same post.
- Does clientId have to be set when uploading?
- No — it's optional on presign, register, and import-url. Omit it for an agency-wide asset available to every client.