> Public copy of the ViewStream developer guide (also for AI agents). All guides: https://www.viewstream.co.il/developers/guide/index.md · API reference: https://api.viewstream.co.il/docs/

# Getting started with the ViewStream API

This page takes you from nothing to a first authenticated call. It is for developers who connect a CMS, a newsroom
tool or a backend to ViewStream.

## Base URL

| What | URL |
|---|---|
| Management API (everything on this page) | `https://api.viewstream.co.il/v1` |
| Interactive reference (Swagger UI) | `https://api.viewstream.co.il/docs/` |
| OpenAPI documents | `https://api.viewstream.co.il/openapi.yaml`, `/openapi.json`; Sites Delivery API: `/openapi/sites-delivery.yaml`, `.json` |

The API is JSON over HTTPS. Every request with an unknown `Host` header is refused with `421 misdirected_request`
(source: `hostAllowList` in middleware.go).

> **The reference page calls production.** "Try it out" on `/docs/` sends real requests with your key. Explore with
> a key that has read-only scopes.

## 1. Create an API key in Studio

You need a Studio role that holds `keys:manage` — **Engineer (מהנדס)** or higher.

1. In Studio, open **Integrations (אינטגרציות)** in the side menu.
2. In the **API keys (מפתחות API)** section, type a **Key name (שם המפתח)**, for example `CMS production`
   (at most 120 characters).
3. Tick the **Scopes (הרשאות)** the integration needs. Pick the smallest set; the table below says what each one
   opens.
4. Click **Create key (יצירת מפתח)**.
5. Copy the key from the box *"Your new key — shown once…"* (*"המפתח החדש שלכם — מוצג פעם אחת…"*) and store it
   in your secret store. ViewStream keeps only a hash; the key cannot be shown again.

Rules enforced by the API (apikeys.go):

- A key never gets a scope that you yourself do not hold (`422`, *"you cannot grant a scope you do not hold"*).
- A key belongs to exactly one tenant and keeps its scopes for life. To change scopes, create a new key and revoke the
  old one.
- **Revoke (ביטול)** asks you to type the key prefix; every system that uses the key gets `401` immediately.
- The same actions are available over the API: `GET /v1/api-keys`, `POST /v1/api-keys {name, scopes[]}`,
  `DELETE /v1/api-keys/{id}` (scope `keys:manage`). The plaintext key is returned once, in the `key` field of the
  `201` answer.

Key format: `vs_<8-character prefix>_<32 random characters>` (letters and digits). The prefix is what Studio shows
in the key list and what the audit log records.

Keep keys on servers. Never put a key in a web page, a mobile app or a public repository.

## 2. Send the key as a bearer token

```bash
export VS_KEY='vs_ab12cd34_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'   # placeholder — use your own key
curl -s https://api.viewstream.co.il/v1/me -H "Authorization: Bearer $VS_KEY"
```

`GET /v1/me` needs no scope. For a key it answers:

```json
{
  "user": null,
  "auth": "api_key",
  "current_tenant": {"id": "…", "slug": "acme", "name": "…", "mode": "platform", "cdn_hostname": "cdn.acme.vustream.net", "role": "api_key"},
  "tenants": [ { "…": "the same tenant" } ],
  "scopes": ["assets:read", "channels:read"]
}
```

- `mode` is `platform` (full product) or `cdn` (CDN-only tenant). CDN-only tenants get `403 feature_disabled` on
  assets, uploads, channels, clips and player configurations (`platformOnly` in
  middleware.go).
- `scopes` is exactly what the key may do.

If the header is missing, malformed, unknown or revoked, the API answers `401 invalid_credentials` with
`WWW-Authenticate: Bearer realm="viewstream"`. After repeated failures from one IP address (30 in 10 minutes) or for
one key prefix (10 in 10 minutes) further attempts get `429` before the key is even checked
(middleware.go, `keyFailPerIP`, `keyFailPerPrefix`).

## 3. Scopes

A route that needs a scope your key lacks answers `403 insufficient_scope`, and `detail` names the scope
(`"this route requires scope assets:write"`). Some routes accept one of two scopes; `detail` then lists both with
"or".

The table is built from auth/scopes.go (the scope list and the role
table) and the router public.go (what each scope opens). "Lowest
role" is the lowest Studio role that holds the scope; roles add up (an Editor has everything a Viewer has).

| Scope | Lowest role | What it opens (main routes) |
|---|---|---|
| `assets:read` | Viewer (צופה) | `GET /assets`, `/assets/{id}`, `/trash`, `/jobs`, `/jobs/{id}`, `/jobs/failures`, `/collections`, `/posters/…`, `/subtitles/settings`, `/subtitles/tracks`, `/subtitles/…/cues`, `/summaries/settings`, `/programmes/{id}/summary`, `/assets/{id}/summary`, `/images/…`, `/assets/{id}/ai-video`, `/assets/{id}/packaging`, `/library/imports`, `/player-configs`, `/player-config-assignments`, `/tenant/branding`, `/tenant/defaults` |
| `assets:write` | Editor (עורך) | create/patch/re-encode/delete/restore assets; **uploads** (`/uploads…`); posters; collections; library imports; subtitle edits, re-runs; summaries edit/regenerate; image and video upscale; job retry/ignore; with `channels:read` also programme → VOD publishing |
| `assets:publish` | Publisher (מפרסם) | the `publish` field of `PATCH /assets/{id}`; publishing catch-up programmes to VOD |
| `assets:purge` | Admin (מנהל) | `DELETE /assets/{id}?permanent=true`, `POST /trash/empty`, `PATCH /trash/settings` |
| `clips:read` | Viewer | `GET /clips`, `/clips/{id}` |
| `clips:write` | Editor | `POST /clips`, `DELETE /clips/{id}` |
| `clips:publish` | Publisher | only checked when `POST /clips` carries `"publish": true` (see the note in [recipes.md](recipes.md#create-a-clip)) |
| `channels:read` | Viewer | `GET /channels…`, programmes, EPG (`/channels/{id}/epg/…`, `/epg/catalog`, `/epg/destinations`), recording status/timeline/thumbs, catch-up list and search, live-to-VOD rule, boundaries, blackouts, ingest settings and events, packaging, lip-sync, catch-up exclusions (read) |
| `channels:write` | Engineer (מהנדס) | create/change/delete channels; ingest settings (`PUT /ingest`, validate, apply, disable, migrate, allowed IPs); recording mode; ad settings and ad breaks; boundaries; markers; EPG source and import; also accepted instead of `epg:write`/`epg:publish` (older keys) |
| `channels:operate` | Engineer | live controls: `POST /channels/{id}/ingest/switch`, `/slate`, `/restart`; reveal ingest secrets and rotate/reveal the SRT passphrase; acknowledge alerts |
| `uploads` | Editor | **Gates no route today** — `/v1/uploads…` checks `assets:write`. See contradictions |
| `prewarm` | Engineer | `POST /prewarm`, `GET /prewarm/{id}`; polling the job with `GET /jobs/{id}` |
| `delivery:write` | Engineer | `POST /purge`; distributions (create, patch, delete, rotate secret, sign URL); playback policies and attachments; blackouts (create, delete); partner CDNs and steering; player configurations and preset assignments (write) |
| `webhooks:manage` | Engineer | `/webhooks…`, `/inbound-hooks…`, `/event-destinations…`, `/event-inbox` |
| `storage:manage` | Engineer | `POST /storage/usage/refresh` only (`GET /storage/keys` and `/storage/usage` need `stats:read`) |
| `keys:manage` | Engineer | `/api-keys…`; `/playback/keys` (list, rotate, export) |
| `stats:read` | Viewer | `/stats/…` (realtime, overview, timeseries, breakdown, top, traffic, QoE, CDN, completion, programmes, sessions, ads, sites, protection, raw export); reports and report schedules (read); distributions, policies, CDN partners, steering (read); storage usage; security leaks/revocations/settings (read); monitors, alerts and service status (read, as an alternative to `channels:read`) |
| `stats:pii` | Admin | viewer-level columns in raw exports, viewer ids in session search, hashed IPs in event export, `DELETE /stats/viewers/{vid}` |
| `events:read` | Viewer | `GET /events/stream` (Server-Sent Events) and `GET /notifications` |
| `team:manage` | Admin | users, invitations, tenant audit log (`GET /audit`); report schedules (or `notifications:manage`) |
| `notifications:manage` | Admin | notification rules; create/change monitors, alert destinations, snooze; acknowledge alerts; report schedules |
| `billing:read` | Owner (בעלים) | `GET /billing/usage`, `/billing/statements` |
| `tenant:settings` | Owner | `PUT /tenant/branding`, `/tenant/defaults`, `/subtitles/settings`, `/summaries/settings`, `/images/settings`, `/packaging/settings`, `/lipsync/settings`; catch-up exclusion rules and overrides |
| `sites:read` | Viewer | `GET /sites…`, `/pages/{id}…`, `/series…`, `/people…` |
| `sites:write` | Editor | edit pages (drafts), menus, redirects, shows (`/series`), people; preview links; page check |
| `sites:publish` | Publisher | publish pages, menus and themes; cancel a scheduled version |
| `sites:admin` | Engineer | create/delete sites, domains, theme, routes |
| `playback:sign` | Engineer | `POST /playback/tokens` (tokenised playback URLs for your backend) |
| `security:manage` | Engineer | `PUT /security/settings`, `POST /security/revocations`, `POST /security/leaks/{id}/dismiss` |
| `epg:write` | Editor | edit the guide: programmes, draft operations, fixes, copy/shift, templates (or `channels:write`) |
| `epg:publish` | Publisher | publish/roll back the guide, workflow mode, EPG destinations (or `channels:write`) |
| `support:read` | Viewer | `GET /support/requests`, `/support/requests/{id}` (the tenant's support requests and their conversation) |
| `support:write` | Viewer | `POST /support/requests` (10 per tenant per hour), `/support/requests/{id}/messages`, `/support/requests/{id}/close` |

Every route under `/v1` except login, invitations and `GET /v1/me`, `/v1/tenants` also needs a current tenant; for an
API key that is always the key's tenant.

## 4. Your first useful call

List the newest ready videos:

```bash
curl -s "https://api.viewstream.co.il/v1/assets?status=ready&limit=10" \
  -H "Authorization: Bearer $VS_KEY"
```

```json
{"items": [{"id": "0192…", "title": "…", "status": "ready", "published": true, "playback": {"hls": "https://cdn…/master.m3u8", "poster": "…"}, "…": "…"}],
 "next_cursor": "MDE5Mj…"}
```

Next steps:

- [conventions.md](conventions.md) — errors, pagination, rate limits, async jobs, the event stream, time formats.
- [recipes.md](recipes.md) — upload a video, catch-up, playback tokens, clips, statistics, monitors, webhooks.
- [player-integration.md](player-integration.md) — put the player on a page.
