Developers

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 now

POST /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