API reference (v1)
The whole pipeline is API-first: everything the app does is a REST call. Base URL is your workspace origin; all endpoints are under /api/v1.
Authentication
Create a key in the app (or via POST /api/v1/keys with a session) and pass it as X-API-Key. The secret is shown once; we store only a hash.
curl -X POST $BASE/api/v1/keys \
-H "Content-Type: application/json" \
--cookie "$SESSION_COOKIE" \
-d '{"name": "my-integration"}'
# -> { "data": { "apiKey": "cf_..." } } # store it nowPOST /api/v1/shorts
Create a clips request. Costs 1 credit. Exactly one source: url (YouTube), fileUrl (direct video link) or uploadKey (see uploads).
curl -X POST $BASE/api/v1/shorts \
-H "X-API-Key: cf_..." -H "Content-Type: application/json" \
-d '{
"fileUrl": "https://example.com/episode.mp4",
"start": 0, "end": 1200,
"preferredLength": "under60sec",
"noClipping": false,
"webhookUrl": "https://your.app/hooks/clipember"
}'
# 202 -> { "data": { "requestId": "...", "status": "processing", "creditsUsed": 1 } }GET /api/v1/requests
Your requests, newest first, with clip counts.
GET /api/v1/requests/:id
Request status, pipeline stages (ingest → transcribe → highlight → crop_plan → render) and clips. Includes the source video title once ingest resolves it. Rendered clips include a time-limited downloadUrl and a thumbUrl poster frame.
POST /api/v1/uploads
Returns a presigned PUT URL for a direct browser/CLI upload; pass the returned key to /shorts as uploadKey.
curl -X POST $BASE/api/v1/uploads \
-H "X-API-Key: cf_..." -H "Content-Type: application/json" \
-d '{"filename": "episode.mp4", "contentType": "video/mp4"}'
# -> { "data": { "key": "uploads/<you>/<uuid>.mp4", "putUrl": "https://..." } }
curl -X PUT -H "Content-Type: video/mp4" --data-binary @episode.mp4 "$PUT_URL"GET /api/v1/clips/:id
A single clip with the transcript words inside its window and the saved EDL — the editor's data source.
POST /api/v1/clips/:id/compile
Re-render one clip from an EDL v1 document (word cuts, caption style, layout, background, hookTitle / ctaText overlays). Recompiles are free.
{
"version": 1, "sourceAssetId": "…",
"segments": [{ "srcStart": 187.0, "srcEnd": 206.0 }],
"captions": {
"templateId": "neon", // bold-pop | karaoke | minimal | boxed |
// neon | sunset | grape | subtle | inkbox
"sizePct": 62, "positionYPct": 73, "wordsPerCaption": 3,
"translateTo": "en" // optional: caption translation (audio unchanged)
},
"layout": "auto", "background": { "kind": "blur" },
"wordEdits": [{ "index": 12, "cut": true }],
"hookTitle": "WAIT FOR IT…", // static title, top of the clip
"ctaText": "Follow for part 2" // static call-to-action, bottom
}POST /api/v1/clips/:id/feedback
Thumbs up/down on a clip: {value: -1 | 0 | 1}. Feedback is stored per clip and will feed scorer tuning.
GET /api/v1/schedule
Queued posts. POST with {clipId, platform, scheduledFor} queues a rendered clip (platforms: tiktok, youtube_shorts, instagram_reels).
POST /api/v1/channels
Channel Automation: pass {channel: 'UC…' | 'youtube.com/channel/UC…'}. New uploads are auto-clipped with your settings; the first check only baselines (no backlog charges). GET lists channels, DELETE /api/v1/channels/:id disconnects.
Webhooks
If the request carries webhookUrl, we POST there when processing finishes — on success and on failure. Three attempts with backoff.
{
"event": "request.completed", // or "request.failed"
"requestId": "…",
"status": "completed",
"clips": [
{ "id": "…", "score": 92, "title": "…",
"startMs": 187000, "endMs": 206000, "status": "rendered" }
]
}Errors
{ "error": { "code": "insufficient_credits", "message": "no credits remaining" } }
// codes: unauthorized, invalid_request, insufficient_credits, not_found, internal_error