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.

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": "audio_url", "url": "https://example.com/episode.mp3" }
  }'

# 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). ~2-5 min on live keys for audio and RSS sources, ~10-12 min for video sources that opt into a trailer.

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.

typeurl shapeTrailerStatus
video_urlPublic MP4 (HTTPS)YesLive
audio_urlPublic MP3 or M4ANoLive
rss_feed_urlPodcast RSS feed (most recent episode)NoLive
youtube_urlYouTube watch / shorts / youtu.be URLYesLive. 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

POST/v1/jobs

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

GET/v1/jobs/{id}

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

GET/v1/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 20
  • starting_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

POST/v1/webhooks
{
  "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.

codetypeWhen
missing_authorizationauthentication_errorAuthorization header absent.
invalid_api_key_formatauthentication_errorKey doesn't start with clk_live_ or clk_test_.
invalid_api_keyauthentication_errorKey not found.
api_key_revokedauthentication_errorKey was revoked.
invalid_jsoninvalid_request_errorBody isn't valid JSON.
invalid_bodyinvalid_request_errorBody isn't a JSON object.
invalid_source_typeinvalid_request_errorsource.type isn't one of the supported values.
invalid_source_urlinvalid_request_errorsource.url is malformed for the chosen type.
youtube_ingestion_failedinvalid_request_errorYouTube download failed (private, region-locked, deleted, or bot-detection blocked). Job lands in 'failed' state; no charge.
trailer_not_supported_for_sourceinvalid_request_erroroptions.include_trailer set on audio_url or rss_feed_url.
invalid_eventsinvalid_request_errorWebhook events array is empty or contains unsupported values.
idempotency_key_reused_with_different_bodyidempotency_errorSame Idempotency-Key submitted with a different body inside the 24h window.
too_many_requestsrate_limit_errorPer-key budget exhausted. Retry-After header set.
not_foundinvalid_request_errorJob, 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/DELETE endpoints (submissions, webhook CRUD, test/replay).
  • 600 reads / 60s on GET endpoints (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 patternOutcome
source.type = youtube_url (any URL)Failed, error.code = youtube_ingestion_failed
source.url contains fail (case-insensitive)Failed, error.code = transcription_failed
Malformed URLRejected at submit with invalid_source_url
Anything elseSuccess fixture: 3 clips, optional trailer (video sources only), show notes, transcript stub.

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_..." }
    }
  }
}

Source on GitHub, package on npm.

All endpoints

MethodPathPurpose
POST/v1/jobsSubmit a new job.
GET/v1/jobs/{id}Get a job's current state.
GET/v1/jobsList recent jobs.
POST/v1/webhooksRegister a webhook.
GET/v1/webhooksList webhooks.
DELETE/v1/webhooks/{id}Delete a webhook.
POST/v1/webhooks/{id}/testFire a synthetic test event.
GET/v1/webhooks/{id}/deliveriesList deliveries for a webhook.
POST/v1/webhooks/deliveries/{id}/replayReplay a delivery.
Back to overviewGet a keySpot a typo or an unclear sentence? Email nelson@useclypt.com.