API reference
All API calls are HTTPS at https://api.hakari.cloud. The version prefix is /v1.
Authentication
Send an API key as a Bearer token:
Authorization: Bearer hkr_<43 chars>
API keys are project-scoped — every endpoint below is under /v1/projects/:projectSlug/… and the key you use must belong to the matching project.
Pagination
List endpoints accept:
skip— starting offset (default 0).limit— page size (default 20, max 100).
Responses take the shape { items, total, skip, limit }.
Streams
GET /v1/projects/:projectSlug/streams
List streams, newest first.
Query params: skip, limit, q (name search), status (live / idle / disabled / pending), from / to (ISO-8601 createdAt range).
{
"items": [
{ "_id": "...", "name": "Launch", "status": "live", "streamKey": "hkr_...", "vodEnabled": true, "createdAt": "..." }
],
"total": 12, "skip": 0, "limit": 20
}
POST /v1/projects/:projectSlug/streams
Create a stream.
{
"name": "Launch",
"qualities": ["720p", "480p"],
"protocols": ["llhls", "webrtc"],
"vodEnabled": true,
"dvrEnabled": true,
"dvrDuration": 3600,
"drmEnabled": false
}
Returns the created stream including ingestUrl, pushUsername, pushPassword, streamKey, playbackUrls, rtmpPort.
GET /v1/projects/:projectSlug/streams/:id
PATCH /v1/projects/:projectSlug/streams/:id
DELETE /v1/projects/:projectSlug/streams/:id
Disable + destroy the container. Sets status: disabled.
POST /v1/projects/:projectSlug/streams/:id/rotate-push
Mint fresh push credentials. The response includes the new pushUsername / pushPassword / ingestUrl.
POST /v1/projects/:projectSlug/streams/:id/playback-ticket
{ "expiresInSec": 900, "allowIp": "203.0.113.5", "streamExpireSec": 3600 }
Returns a fully-signed set of playback URLs. See Signed playback.
PATCH /v1/projects/:projectSlug/streams/:id/signed-playback
{ "enabled": true }
Toggle per-stream signed-playback enforcement.
VOD
GET /v1/projects/:projectSlug/vod
Same pagination shape as streams. Extra query param: status (pending_upload / uploaded / queued / processing / complete / failed / cancelled).
POST /v1/projects/:projectSlug/vod
{
"name": "Launch recording",
"filename": "launch.mp4",
"outputFormat": "hls",
"videoCodec": "h264",
"audioCodec": "aac",
"aspectRatio": "preserve",
"ladder": [
{ "name": "720p", "width": 1280, "height": 720, "videoBitrate": "3M" },
{ "name": "480p", "width": 854, "height": 480, "videoBitrate": "1500k" }
]
}
Returns { vod, upload } — upload.url is a presigned PUT. See VOD upload flow.
POST /v1/projects/:projectSlug/vod/:id/uploaded
Mark the source file as uploaded. Dispatches the transcode job.
POST /v1/projects/:projectSlug/vod/:id/heartbeat
Optional — keeps the upload session alive if you're chunking client-side.
GET /v1/projects/:projectSlug/vod/:id
DELETE /v1/projects/:projectSlug/vod/:id
Cancel a pending/processing VOD or soft-archive a complete one.
POST /v1/projects/:projectSlug/vod/:id/playback-ticket
Same shape as the stream playback-ticket — returns a signed playbackUrl for this VOD.
PATCH /v1/projects/:projectSlug/vod/:id/signed-playback
{ "enabled": true }
Webhooks
GET /v1/projects/:projectSlug/webhooks/events
Returns { events: ["stream.created", ...] } — the full catalog. Useful for rendering an event picker.
GET /v1/projects/:projectSlug/webhooks
POST /v1/projects/:projectSlug/webhooks
{
"url": "https://api.your-app.com/hakari/hook",
"description": "Production events",
"events": ["stream.live", "stream.ended", "vod.complete"],
"enabled": true
}
Response includes the generated secret (32 hex chars) used to verify signatures. See Verify the signature.
PATCH /v1/projects/:projectSlug/webhooks/:id
Enable/disable, change events or URL.
DELETE /v1/projects/:projectSlug/webhooks/:id
GET /v1/projects/:projectSlug/webhooks/deliveries
Recent deliveries (last 50 by default; limit up to 200). Filter by webhookId.
API keys
GET /v1/projects/:projectSlug/api-keys/scopes
Returns { scopes: ["streams:read", ...] } — the full list of supported scopes.
GET /v1/projects/:projectSlug/api-keys
List keys on this project. Never returns hashedKey — only prefix, label, scopes, lastUsedAt, revokedAt.
POST /v1/projects/:projectSlug/api-keys
{ "label": "Production backend", "scopes": ["streams:write", "vod:write"] }
The response includes key — the raw hkr_… — exactly once. Copy and store.
DELETE /v1/projects/:projectSlug/api-keys/:id
Revoke. Effective on the next auth check.
Webhook event payloads
Every delivery envelopes the payload:
{
"id": "<delivery id>",
"event": "<event name>",
"createdAt": "ISO-8601",
"data": { /* event-specific */ }
}
stream.created / stream.live / stream.ended / stream.disabled
{
"id": "<stream id>",
"name": "Launch",
"streamKey": "hkr_abc",
"status": "live",
"projectId": "...",
"vodEnabled": true,
"createdAt": "..."
}
stream.disabled additionally includes reason (e.g. auto-stop: no input, insufficient credits).
vod.uploaded / vod.processing / vod.complete / vod.failed / vod.cancelled
{
"id": "<vod id>",
"name": "Launch recording",
"status": "complete",
"projectId": "...",
"outputFormat": "hls",
"playbackUrl": "https://stream.hakari.cloud/vod/<id>/master.m3u8",
"thumbnailUrl": "https://stream.hakari.cloud/vod/<id>/thumb.jpg",
"sourceSize": 1073741824,
"completedAt": "..."
}
Errors
| Status | Meaning |
| --- | --- |
| 400 | Validation — DTO failed a class-validator rule. message is an array of per-field errors. |
| 401 | Missing / invalid API key, or signed-URL failed. X-Deny-Reason header on the latter. |
| 403 | Auth OK but scope/role doesn't allow the action. |
| 404 | Project, stream, VOD, webhook, key — not found for this auth context. |
| 409 | Slug/name conflict (streamKey unique violations). |
| 429 | Rate-limited. Retry with backoff. |
| 5xx | Something is wrong on our side. Retries are safe for idempotent methods. |