Hakari

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. |