Slice for developers

The Slice API

Send a long video or audio episode, such as a podcast, an interview or a sermon, and get back captioned vertical clips. Or send a recording and get a transcript. It is the same work as the Slice website and the Slice MCP server, on the same account.

Base URL https://tryslicemedia.com/api/v1. JSON in, JSON out. The machine-readable description is at /openapi.json (OpenAPI 3.1). For Claude, ChatGPT or Cursor, connect the MCP server instead: https://mcp.tryslicemedia.com/mcp.

Get an API keyQuickstartOpenAPI file

Quickstart

Make a key in your account settings (a test key works straight away and costs nothing), then:

1. Start an episode from a link to the video or audio file

curl -s -X POST https://tryslicemedia.com/api/v1/episodes \
  -H "Authorization: Bearer $SLICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"source_url": "https://example.com/episode-41.mp4", "name": "Episode 41"}'

2. When it is ready (the episode.ready webhook, or poll every 10 seconds or more), read the clips it found

curl -s https://tryslicemedia.com/api/v1/episodes/EPISODE_ID \
  -H "Authorization: Bearer $SLICE_API_KEY"

3. Make clips 1 and 3, with big captions near the bottom

curl -s -X POST https://tryslicemedia.com/api/v1/episodes/EPISODE_ID/clips \
  -H "Authorization: Bearer $SLICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"clips": [1, 3], "caption_size": "large", "caption_position": "lower"}'

Each clip comes back as a job. When it is done, GET /jobs/JOB_ID (or the clip.ready webhook) gives a download_url:

{
  "job_id": "00000000-0000-4000-8000-0000000b0001",
  "kind": "cut",
  "status": "completed",
  "clip_id": "00000000-0000-4000-8000-0000000c0001",
  "title": "Why we stopped giving free trials",
  "download_url": "https://tryslicemedia.com/shorts/guides/lgc430-audio-cover-example-2026-10.mp4",
  "livemode": false
}

Uploading the file yourself

# No public link? Ask for an upload address instead:
curl -s -X POST https://tryslicemedia.com/api/v1/episodes \
  -H "Authorization: Bearer $SLICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file_name": "episode-41.mp4"}'
# -> { "episode_id": "...", "upload": { "method": "PUT", "url": "...", "headers": { ... } } }

curl -X PUT "UPLOAD_URL" -H "content-type: video/mp4" -H "x-upsert: true" --data-binary @episode-41.mp4
curl -s -X POST https://tryslicemedia.com/api/v1/episodes/EPISODE_ID/uploaded -H "Authorization: Bearer $SLICE_API_KEY"

The upload address and the upload page both last two hours. Episodes can be up to 3 hours and 4.5GB. Video (MP4, MOV, M4V, WebM, MKV, AVI) or audio (MP3, M4A, AAC, WAV, OGG, Opus, FLAC): an audio episode, such as an RSS enclosure, gets clips drawn with your audio template, the same as on the website.

Authentication

Send your key as a bearer token: Authorization: Bearer slk_live_…. Keys belong to one account and act as that account.

A key is shown once when you make it. Slice keeps only a fingerprint of it (sha256), so a lost key cannot be shown again: revoke it and make another. Revoking works at once. A key can stop working on its own after 30 or 90 days if you choose. Up to 5 keys an account.

Each key has permissions (scopes), chosen when you make it:

clips.read Read clipsEpisodes, found clips, jobs and captions.
clips.write Make clipsStart episodes, cut, caption and restyle.
transcripts TranscriptsUses your free 3 a month, then Transcribe hours.
webhooks WebhooksAdd and remove webhook addresses.

The free transcription keys (slk_free_, from /tools/audio-to-text/api) keep working with /api/v1/transcribe exactly as before. They do not work here.

Test mode

A slk_test_ key answers every call with the same sample episode, clips, jobs and transcript, and never uploads, renders or charges anything. Every answer carries "livemode": false. Use it to build an n8n flow, a Zap or a script before spending anything. The sample episode is 00000000-0000-4000-8000-00000000e001:

{
  "episode_id": "00000000-0000-4000-8000-00000000e001",
  "name": "Sample episode: pricing a podcast business",
  "status": "ready",
  "duration": "42:10",
  "clips": [
    {
      "number": 1,
      "ref": "k3p9",
      "title": "Why we stopped giving free trials",
      "hook": "Free trials taught people to wait.",
      "start": "04:12",
      "end": "05:01",
      "length": "0:49",
      "pieces": 1,
      "cut_status": "not_cut",
      "export_status": "none"
    }
  ],
  "livemode": false
}

Endpoints

They match the MCP tools one to one, and run the same code. Paths are relative to https://tryslicemedia.com/api/v1.

GET /me (any)The account, this key, this month's use, credit and the limits.
POST /episodes (clips.write)Start an episode from source_url (a direct https link to the video or audio file, such as an RSS enclosure), or get a PUT upload address. Answers 202.
POST /episodes/{id}/uploaded (clips.write)After the PUT: attach the file and start transcription and the clip search.
GET /episodes (clips.read)The 10 most recent episodes and their status.
GET /episodes/{id} (clips.read)Status, and once ready the found clips: number, ref, title, hook, start, end, length.
POST /episodes/{id}/search (clips.write)Find clips about a topic: { "about": "pricing for founders", "target_seconds": 45 }. Adds to the list.
POST /episodes/{id}/clips (clips.write)Cut and caption found clips: { "clips": [1, 3] } or refs, plus an optional look. One job per clip.
GET /jobs/{id} (clips.read)A cut or a render. download_url is a short link to the MP4, good for 7 days.
PATCH /clips/{id} (clips.write)Change a made clip's look (style, caption_size, caption_position, hook_title, fit, background) and render it again.
GET /clips/{id}/captions (clips.read)The captions on a clip. Narrow with ?at=, ?from= and ?to= (seconds) or ?q= (text).
GET /styles (clips.read)The caption styles by id and name.
POST /transcripts (transcripts)Transcribe a recording: { "url": "https://example.com/episode.mp3" }. Up to 90 minutes a file.
GET /transcripts/{id} (transcripts)Status, the text, and links to TXT, SRT and VTT.
GET /webhooks (webhooks)Your webhook addresses and their last 10 deliveries.
POST /webhooks (webhooks)Add one: { "url": "https://...", "events": ["clip.ready"] }. The signing secret is in this answer.
DELETE /webhooks/{id} (webhooks)Remove one.
POST /webhooks/{id}/test (webhooks)Send a signed sample event now.

Retries and Idempotency-Key

Send an Idempotency-Key header (any unique string, such as a UUID) on every POST and PATCH. A retry with the same key and the same body within 24 hours gets the first answer again, so a timeout can never start two episodes or pay for two renders. The same key with a different body is refused (422).

Polling

There is no long wait on any call. Use webhooks, or poll GET /episodes/{id} and GET /jobs/{id} every 10 seconds or more. A 20 minute episode is usually ready in a few minutes; a render takes a minute or two.

Webhooks

Add an address in your account settings or with POST /webhooks, and Slice sends a POST when something on your account finishes, whether it started through the API, the website or a chat. Up to 5 addresses, https only; private network addresses are refused.

episode.readyThe clip search finished. data is the same as GET /episodes/{id}, clips included. Also sent after each extra search.
episode.failedThe episode could not be searched, with the reason.
clip.readyA render finished. data is the same as GET /jobs/{id} plus episode_id, with download_url (good for 7 days).
clip.failedA render failed.
transcript.completedA transcript finished, with links to the results page and the TXT, SRT and VTT files.
transcript.failedA transcript failed, with the reason.

Signatures

Every delivery is signed with the Standard Webhooks scheme, so any Standard Webhooks library can check it. The headers are webhook-id (the same on every retry, so you can drop repeats), webhook-timestamp and webhook-signature:v1, then base64 of HMAC-SHA256 over id.timestamp.body, keyed with the base64 part of your whsec_ secret. Check the raw body, before parsing it, and refuse anything older than 5 minutes.

POST https://your-app.example.com/hooks/slice
webhook-id: msg_6f1c2a0e9b8d4c3a8e7f1a2b3c4d5e6f
webhook-timestamp: 1792252800
webhook-signature: v1,<base64 HMAC-SHA256, see below>

{
  "type": "clip.ready",
  "timestamp": "2026-10-17T14:00:00.000Z",
  "livemode": true,
  "data": {
    "episode_id": "...",
    "job_id": "...",
    "kind": "render",
    "status": "completed",
    "clip_id": "...",
    "title": "Why we stopped giving free trials",
    "download_url": "https://tryslicemedia.com/d/...",
    "message": "Ready: Why we stopped giving free trials. Download: ..."
  }
}

Checking a signature in Node

import crypto from 'node:crypto';

// secret is the whsec_... shown when you added the webhook.
export function verifySlice(secret, headers, rawBody) {
  const id = headers['webhook-id'];
  const ts = Number(headers['webhook-timestamp']);
  if (!id || Math.abs(Date.now() / 1000 - ts) > 300) return false; // older than 5 minutes
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = 'v1,' + crypto.createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64');
  return String(headers['webhook-signature'] || '').split(' ').some((sig) =>
    sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}

Checking a signature in Python

import base64, hashlib, hmac, time

def verify_slice(secret: str, headers: dict, raw_body: str) -> bool:
    msg_id = headers.get("webhook-id")
    ts = headers.get("webhook-timestamp", "0")
    if not msg_id or abs(time.time() - int(ts)) > 300:  # older than 5 minutes
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    mac = hmac.new(key, f"{msg_id}.{ts}.{raw_body}".encode(), hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(mac).decode()
    return any(hmac.compare_digest(expected, s) for s in headers.get("webhook-signature", "").split(" "))

Delivery and retries

Answer with any 2xx within 10 seconds. Redirects are not followed. A delivery that fails is tried again after 1, 5, 30, 60 minutes and then over about 25 hours in all, then given up. An address that has failed for 3 days in a row is switched off. Your account settings show the last 10 deliveries for each address, and a Send test event button. Deliveries can arrive more than once and out of order: use webhook-id and the ids in data.

Limits

Reads60 a minute per key
Writes (POST, PATCH, DELETE)20 a minute per key
New episodes30 a day per key (resets at midnight UTC)
Episodes on their way at once3 per account
One episodeUp to 3 hours and 4.5GB, video or audio
One transcriptUp to 90 minutes a file
Clips per make callUp to 10

Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 also says retry_after in seconds.

Errors

Every error has the same shape, and says the limit, what was measured and when it resets wherever that applies:

{
  "error": {
    "code": "credit_required",
    "message": "Exports after the first 5 on an episode cost $0.25 each, and there is not enough credit. Nothing was made or charged.",
    "needed_cents": 25,
    "credit_cents": 10
  }
}
400 bad_*, unknown_field, page_linkThe input is wrong. The message names the field and what it accepts.
401 missing_key, bad_key, revoked_key, expired_key, free_keyNo usable key. A slk_free_ key only works with /api/v1/transcribe.
402 plan_or_credit_required, credit_required, balance_below_zero, plan_required, transcript_limitNeeds credit or the Slice plan (plan_required: an episode made on the website). balance_below_zero: a refunded credit pack left the balance below zero; add credit to continue. Carries needed_cents and credit_cents, or the month's counts.
403 insufficient_scope, billing_reviewThe key lacks that permission (the error says which), or the account is held for review.
404 not_found, not_enabledNot on this account. Ids from another account answer exactly like ids that do not exist.
409 busy, not_ready, too_many_webhooks, idempotency_in_progressTry again later, or the episode is not at that step yet.
422 idempotency_mismatchThe Idempotency-Key was used before with a different body.
429 rate_limited, too_many_in_progress, daily_episode_limitOver a limit. Carries limit and retry_after, with RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers.
5xx server_error, *_unavailableOur side. Retry with the same Idempotency-Key: it is never charged twice.

Pricing

Pay as you go with credit, no plan needed: buy a pack, from $10, in your account settings. The Slice plan ($14.99 a month) includes 5 API episodes a month, used before any credit. What it costs:

An episode$5.00 an hour of the source video, by the minute, $1.00 minimum. A 20 minute episode is $1.67; an hour is $5.00.
What that includesTranscribing it, finding the clips, and 5 clip exports.
Each export after that$0.25 (a restyle that renders again counts as an export).
Each extra clip search$0.50 (POST /episodes/{id}/search). Asking again for the same search within 10 minutes is free.
Credit$10, $25, $50 ($55.00 of credit). Credit does not expire. Credit packs can be refunded within 7 days for unused credit.
TranscriptsYour free 3 a month (shared with the website), then Transcribe hours. No separate API price.
Test modeFree, with sample data.

An episode is charged once, when its clip search starts, by the length Slice measured. A call that is refused, or a clip that could not be started, is not charged. Real episodes need credit or the Slice plan; with neither, the API offers test mode and your 3 free transcripts a month. The website's free first video and 3 clips do not apply to the API. A refunded pack takes its credit back; if some of it was already spent the balance goes below zero, and live calls stop until you add credit (reads and test keys still work). Episodes and clips you make on the website or in a chat are not counted here. Every answer that spends credit says how much in charged_cents, and GET /me shows what is left.

Changelog

17 October 2026. Credit works without the plan: pay as you go from $10. New 402 codes plan_or_credit_required and balance_below_zero.

17 October 2026. First version: account keys with test mode, episodes, clips, jobs, captions, styles, transcripts, signed webhooks, Idempotency-Key, rate limits and the OpenAPI file.

Rather click than code?

Shorts by Slice does all of this in the browser: upload an episode and pick the clips.

Try Shorts by Slice