API reference
Clypt API reference.
Base URL: https://useclypt.com/api/v1. All requests use bearer-token auth and JSON bodies. Every response carries an X-Request-Id header you can quote in support questions.
Building with an AI assistant?
The full reference in a single AI-readable file: https://useclypt.com/developers/llms-full.txt. Paste that URL into Claude, Cursor, or Codex and ask it to integrate Clypt. One fetch covers everything on this page.
Quick start
Sixty seconds, free, sandbox key. Submit a fake job, watch the status flip, see the fixture output.
# 1. Submit
curl -X POST https://useclypt.com/api/v1/jobs \
-H "Authorization: Bearer clk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "video_url", "url": "https://example.com/episode.mp4" }
}'
# Response (202 Accepted)
# { "id": "job_a1b2...", "status": "queued", ... }
# 2. Poll until status is "complete" or "failed"
curl https://useclypt.com/api/v1/jobs/job_a1b2... \
-H "Authorization: Bearer clk_test_YOUR_KEY"
# 3. When status is "complete", output.clips[] is populated.Wall-clock to terminal: 10-70s on sandbox keys (deterministic fixture, no real pipeline). ~3-5 min on live keys for video_url; youtube_url adds ~3-8 min for ingestion; options.include_trailer adds ~5-7 min on top.
Authentication
Every request needs an Authorization: Bearer <api_key> header. Keys come in two environments:
clk_live_…— production. Runs the real pipeline, bills against your account.clk_test_…— sandbox. Returns deterministic fixtures, never bills, never touches Deepgram or R2. Same base URL.
The prefix on the key is decorative — the server checks the key’s stored environment against the prefix and refuses if they disagree.
Source types
The source.type discriminator picks how Clypt acquires the underlying media. The downstream pipeline is identical regardless.
| type | url shape | Trailer | Status |
|---|---|---|---|
video_url | Public MP4 (HTTPS) | Yes | Live |
youtube_url | YouTube watch / shorts / youtu.be URL | Yes | Live. Ingestion adds ~3-8 min wall-clock for the video download before the standard pipeline runs. Failures (private / region-locked / deleted / bot-detection) surface as youtube_ingestion_failed; no charge on failure. |
Submit a job
Returns 202 Accepted with a queued job envelope. The pipeline runs asynchronously — poll GET /v1/jobs/{id} or register a webhook to be notified.
Body
{
"source": {
"type": "video_url",
"url": "https://r2.example.com/episode.mp4"
},
"options": {
"include_trailer": true,
"caption_style": "yellow-pop" // optional override
}
}Response (202)
{
"id": "job_4f8d2a1c9e...",
"object": "job",
"status": "queued",
"source": { "type": "video_url", "url": "..." },
"options": { "include_trailer": true },
"created_at": "2026-05-19T12:00:00Z",
"completed_at": null,
"output": null,
"error": null
}Get a job
Returns the current job envelope. Status walks queued → processing → complete or queued → processing → failed. Once terminal, the row never changes again.
Output shape (status=complete)
{
"id": "job_4f8d2a1c9e...",
"status": "complete",
"completed_at": "2026-05-19T12:05:00Z",
"output": {
"clips": [
{
"id": "clip_xxx",
"rank": 1,
"category": "Bold/Contrarian",
"hook": "First-line text the clip leads with.",
"pull_quote": "...",
"start_time": 197.6,
"end_time": 228.7,
"duration_seconds": 31.0,
"rendered_urls": {
"linkedin": "https://r2.useclypt.com/.../9x16.mp4",
"x": "https://r2.useclypt.com/.../16x9.mp4"
},
"transcript_text": "..."
}
],
"trailer": {
"url": "https://r2.useclypt.com/.../trailer.mp4",
"duration_seconds": 67.4
},
"guest_share_url": "https://useclypt.com/guest/...",
"show_notes": "# Markdown show notes...",
"transcript": [
{ "speaker": "host", "start": 0.5, "end": 4.2, "text": "..." }
]
},
"error": null
}When status=failed: output is null and error carries { code, message }. See Errors.
List jobs
Returns the org’s recent jobs newest-first. Cursor pagination via starting_after — pass the last job’s id from the previous page.
Query params
limit— 1-100, default 20starting_after— cursor (a job id)
{
"object": "list",
"has_more": true,
"data": [ { "id": "job_...", "status": "complete", ... }, ... ]
}Webhooks
Register an HTTPS endpoint and Clypt will POST signed job.completed and job.failed events to it. No polling required.
Register
{
"url": "https://your.app/webhooks/clypt",
"events": ["job.completed", "job.failed"]
}Response includes a secret (whsec_…) — shown once at registration, never returned again. Store it; use it to verify signatures.
Signature header
Every delivery carries X-Clypt-Signature: t=<unix_ts>,v1=<hmac_sha256_hex> (Stripe-shape). The signed payload is `${t}.${raw_body}`.
// Node.js verification snippet
import { createHmac, timingSafeEqual } from "crypto";
export function verifyClyptSignature(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=", 2))
);
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(parts.v1, "hex")
);
}Reject deliveries with a t older than 5 minutes to prevent replay attacks against your endpoint.
Event shape
{
"id": "evt_...",
"object": "event",
"type": "job.completed",
"created": 1747500000,
"request_id": "req_...",
"data": {
"object": { /* same shape as GET /v1/jobs/{id} */ }
}
}Retries
Any non-2xx response (or timeout, or connection error) schedules a retry. Backoff: 1s, 10s, 1m, 10m, 1h, 6h, 24h. After 7 failed attempts the delivery is marked failed. 10 consecutive failed deliveries deactivates the webhook entirely (you can re-register it).
Test + replay
POST /v1/webhooks/{id}/test fires a synthetic job.completed event derived from your most recent real job — useful for wiring signature verification before a real submission. POST /v1/webhooks/deliveries/{id}/replay re-emits any past delivery, reusing the original request_id so your receiver can dedupe.
Errors
Every non-2xx response carries a JSON error envelope:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_source_url",
"message": "source.url must be a valid HTTPS URL.",
"param": "source.url",
"request_id": "req_..."
}
}type is one of invalid_request_error, authentication_error, rate_limit_error, idempotency_error, api_error. Branch on code for specific handling.
| code | type | When |
|---|---|---|
missing_authorization | authentication_error | Authorization header absent. |
invalid_api_key_format | authentication_error | Key doesn't start with clk_live_ or clk_test_. |
invalid_api_key | authentication_error | Key not found. |
api_key_revoked | authentication_error | Key was revoked. |
invalid_json | invalid_request_error | Body isn't valid JSON. |
invalid_body | invalid_request_error | Body isn't a JSON object. |
invalid_source_type | invalid_request_error | source.type isn't one of the supported values. |
invalid_source_url | invalid_request_error | source.url is malformed for the chosen type. |
youtube_ingestion_failed | invalid_request_error | YouTube download failed (private, region-locked, deleted, or bot-detection blocked). Job lands in 'failed' state; no charge. |
analysis_failed | pipeline_error | Transcription or clip-selection step returned an error. Job lands in 'failed'; no charge. |
no_clips_to_render | pipeline_error | Analysis produced zero clip candidates from the source. Nothing to render; no charge. |
all_renders_failed | pipeline_error | Every approved clip render failed terminally. No usable output; no charge. |
pipeline_stalled | pipeline_error | Job exceeded the 30-minute end-to-end pipeline budget without reaching a terminal state. Swept by the stall watchdog; no charge. |
invalid_events | invalid_request_error | Webhook events array is empty or contains unsupported values. |
idempotency_key_reused_with_different_body | idempotency_error | Same Idempotency-Key submitted with a different body inside the 24h window. |
too_many_requests | rate_limit_error | Per-key budget exhausted. Retry-After header set. |
not_found | invalid_request_error | Job, webhook, or delivery ID doesn't exist for this org. |
This table covers the codes a normal caller will see. Internal codes (e.g., insert failures) are logged with the same envelope shape but aren’t enumerated here.
Idempotency
Pass an Idempotency-Key header on POST /v1/jobs (and only that route). 24-hour replay window.
- Same key + same body → returns the original job (with the same id).
- Same key + different body → 409
idempotency_key_reused_with_different_body. - Keys can be any 8-255 char string. UUIDs work; so does anything that hashes deterministically off your input.
Rate limits
Budgets are per-API-key, sliding 60-second windows.
- 60 writes / 60s on
POST/DELETEendpoints (submissions, webhook CRUD, test/replay). - 600 reads / 60s on
GETendpoints (status polling, lists).
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds). On 429 a Retry-After header is set too.
If you need higher limits, email nelson@useclypt.com with your use case.
Sandbox
Sandbox keys (clk_test_…) hit the same base URL as live keys but skip the pipeline entirely — every submission returns a deterministic fixture 10-70s later. No transcription cost, no R2 storage, no Stripe usage record. Webhooks fire as if from a real job, so you can wire your integration end-to-end before committing to live.
| Submission pattern | Outcome |
|---|---|
source.type = youtube_url (any URL) | Failed, error.code = youtube_ingestion_failed |
source.url contains fail (case-insensitive) | Failed, error.code = transcription_failed |
| Malformed URL | Rejected at submit with invalid_source_url |
| Anything else | Success fixture: 3 clips, optional trailer (video sources only), show notes, transcript stub. |
Pricing
Flat per-job pricing, paid from a prepaid credit wallet. Failed jobs are never billed — the debit fires only when a job reaches status = "complete".
| Source type | Cost per successful job |
|---|---|
video_url | $3.00 |
youtube_url | $7.00 (covers residential-proxy ingestion) |
Free tier: the first 3 successful live jobs your org submits are free, lifetime — counted across either source type. The fourth requires wallet balance. Sandbox usage (clk_test_… keys) is unlimited and never counts.
Top up: $5 to $500 per transaction from your developer billing page. Stripe Checkout, card receipt sent automatically. Credits are FIFO: the oldest credit row is consumed first.
Expiry: Credits expire 12 months from the date of purchase. The dashboard shows the next-bite expiry date so you always know when the oldest credit drops off.
MCP server
The MCP server is a thin shim over the same API. Three tools — submit_job, get_job, list_jobs — over stdio MCP transport. Works with Claude Desktop, Cursor, Claude Code, and any MCP-compliant host.
# Claude Desktop config (macOS):
# ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"clypt": {
"command": "npx",
"args": ["-y", "@useclypt/mcp-server"],
"env": { "CLYPT_API_KEY": "clk_test_..." }
}
}
}All endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/jobs | Submit a new job. |
| GET | /v1/jobs/{id} | Get a job's current state. |
| GET | /v1/jobs | List recent jobs. |
| POST | /v1/webhooks | Register a webhook. |
| GET | /v1/webhooks | List webhooks. |
| DELETE | /v1/webhooks/{id} | Delete a webhook. |
| POST | /v1/webhooks/{id}/test | Fire a synthetic test event. |
| GET | /v1/webhooks/{id}/deliveries | List deliveries for a webhook. |
| POST | /v1/webhooks/deliveries/{id}/replay | Replay a delivery. |