Your first edit in five minutes
The fastest path is a prebuilt tool. It gives your agent a narrow, stable contract for one common media task.
curl -X POST https://kinopipe.com/api/v1/tools/resize-video \
-H "Authorization: Bearer $KINOPIPE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: resize-demo-001" \
-d '{
"input": "https://example.com/source.mp4",
"options": {
"aspect_ratio": "9:16",
"fit": "cover"
}
}'Bearer keys for server-side requests
KinoPipe API keys begin with kp_live_. Keep them on your server and never expose them in browser code or public agent prompts.
The raw key is returned only once. KinoPipe stores a SHA-256 hash, so a lost key cannot be recovered.
Compose several edits in one job
Use the Jobs API when an agent needs a pipeline. KinoPipe accepts the job immediately with HTTP 202, then processes the operations in order on a media worker.
/api/v1/jobs{
"name": "social-cut",
"input": {
"url": "https://example.com/source.mp4",
"filename": "source.mp4"
},
"operations": [
{ "type": "trim", "start": 2, "end": 17 },
{
"type": "resize",
"width": 1080,
"height": 1920,
"fit": "cover"
}
],
"output": { "format": "mp4", "quality": "balanced" }
}Read job status
Poll the returned statusUrl with the same API key until the status is succeeded or failed.
/api/v1/jobs/{jobId}{
"ok": true,
"job": {
"id": "f7541d5e-…",
"status": "succeeded",
"progress": 100,
"consumedCredits": 3
},
"result": {
"output": {
"downloadUrl": "https://storage.googleapis.com/…",
"contentType": "video/mp4",
"byteSize": 437021
},
"runtimeMs": 2501
}
}Supported operations
| Type | Fields | Purpose |
|---|---|---|
| trim | start, end? | Cut a time range in seconds. |
| resize | width, height, fit? | Scale and cover or contain the output frame. |
| thumbnail | — | Return one JPG or WebP frame. |
| subtitles | url | Burn a public SRT or WebVTT file into the video. |
Send a stable Idempotency-Key when retrying the same edit. A repeated key returns the existing job with HTTP 200 and does not run or bill it twice.
Focused endpoints with useful defaults
Every tool accepts input, options and an optional webhook. The same presets can be tested by a human on their public tool pages.
/api/v1/tools/{slug}| Tool | Options | Open |
|---|---|---|
compress-videoCompress video | quality: fast | balanced | quality | Try |
video-to-mp4Video to MP4 | No options required | Try |
resize-videoResize video | aspect_ratio: 9:16 | 1:1 | 16:9 · fit: cover | contain | Try |
extract-audioExtract audio | format: mp3 | wav | Try |
video-thumbnail-generatorGenerate thumbnails | timestamp: number · format: jpg | webp | Try |
add-subtitles-to-videoBurn subtitles | captions: public SRT or WebVTT URL | Try |
trim-videoTrim video | start: number · end: number | Try |
optimize-video-for-webOptimize for web | quality: fast | balanced | quality | Try |
Receive signed terminal job events
Attach a webhook to any job or prebuilt tool request. KinoPipe sends a signed POST after the job reaches succeeded or failed.
Add a webhook to the request
{
"input": "https://example.com/source.mp4",
"options": { "start": 0, "end": 12 },
"webhook": {
"url": "https://api.example.com/webhooks/kinopipe",
"secret": "replace-with-at-least-16-characters",
"events": ["job.succeeded", "job.failed"]
}
}Payload
{
"id": "6f0462da-…",
"event": "job.succeeded",
"createdAt": "2026-08-24T14:42:18.000Z",
"data": {
"job": {
"id": "f7541d5e-…",
"status": "succeeded",
"consumedCredits": 3
},
"result": {
"output": { "downloadUrl": "https://storage.googleapis.com/…" },
"runtimeMs": 2501
}
}
}Verify the signature
Read the raw request body. Compute HMAC-SHA256 over timestamp.rawBody and compare it with the v1 value.
import { createHmac, timingSafeEqual } from "node:crypto"
export function verifyKinoPipeWebhook(rawBody, signatureHeader, secret) {
const fields = Object.fromEntries(
signatureHeader.split(",").map((part) => part.split("=")),
)
const expected = createHmac("sha256", secret)
.update(`${fields.t}.${rawBody}`)
.digest("hex")
const received = Buffer.from(fields.v1 ?? "", "hex")
const computed = Buffer.from(expected, "hex")
return received.length === computed.length
&& timingSafeEqual(received, computed)
}| Header | Value | |
|---|---|---|
| X-KinoPipe-Event | job.succeeded | The event type. |
| X-KinoPipe-Delivery | UUID | Use it to deduplicate deliveries. |
| X-KinoPipe-Signature | t=…,v1=… | Timestamp and HMAC signature. |
KinoPipe rejects credentials in URLs, non-standard ports, local hostnames and private network addresses. A failed callback never changes a successful media job into a failed one.
Pay for measured processing time
One credit equals one second of worker runtime, rounded up with a minimum charge of one credit per successful job.
KinoPipe checks the balance before processing and returns HTTP 402 with insufficient_credits. There are no automatic overages.
Stable HTTP status and error codes
Every error response includes an error code. Validation errors may also include a structured issues array.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_recipe · invalid_tool_request · invalid_webhook | The request contract or webhook target is invalid. |
| 401 | unauthorized | The API key is missing, invalid or revoked. |
| 402 | insufficient_credits | No processing credits remain. |
| 404 | tool_not_available | The requested preset is not executable. |
| 503 | queue_unavailable | The job could not be placed on the render queue. |
{
"error": "invalid_webhook",
"message": "Webhook URL must resolve only to public addresses"
}Machine-readable API contract
Use the OpenAPI 3.1 document to generate clients, agent tools or validation schemas from the same public contract.