# ReedTV API

Programmatic access to your ReedTV account: manage videos, sync your BYOA
(Bring Your Own Cloudflare Stream Account) library, and read analytics.

- **Base URL:** `https://reedtv.com`
- **Format:** JSON. All request bodies and responses are `application/json`.
- **Stability:** This is the supported public API. Endpoints not listed here are
  internal and may change without notice; do not depend on them.

---

## Authentication

All API requests authenticate with an **API key** in the `Authorization` header:

```
Authorization: Bearer rtv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Create and manage keys from the **Developer** page in your ReedTV portal:
`https://reedtv.com/api-keys.html`. The full key is shown **once** at creation —
store it securely (it cannot be retrieved later). Keys do not expire unless you
set an expiry, and can be revoked at any time.

> API keys are scoped (see below) and can only reach the endpoints documented
> here. They cannot access billing, account, or admin functionality.

### Scopes

A key is granted one or more scopes. A request to an endpoint whose required
scope the key lacks returns `403`.

| Scope | Grants |
|-------|--------|
| `videos:read` | List/read videos, storage usage, captions |
| `videos:write` | Create/update/delete videos, copy to shared, bulk ops, manage captions |
| `vendors:read` | List BYOA accounts and their remote (Cloudflare Stream) videos |
| `vendors:write` | Connect/verify/sync/delete BYOA accounts |
| `analytics:read` | Read analytics overview and per-video analytics |

---

## Conventions

- **Success:** `2xx` with a JSON body. Many write endpoints return
  `{ "success": true, "data": ... }`.
- **Error:** non-`2xx` with `{ "success": false, "error": "message" }`.
- **Auth errors:** `401` (missing/invalid/expired/revoked key), `403`
  (missing scope, suspended account, or endpoint not available to API keys).
- **Pagination:** list endpoints accept `?page=` (default 1) and `?limit=`
  (default 20) and return a `pagination` object.
- **IDs:** videos are addressed by their `publicId` (a UUID). BYOA accounts are
  addressed by their numeric `accountId`.

---

## Endpoints

### Videos

#### `GET /api/videos` — list videos
Scope: `videos:read`
Query: `page`, `limit`, `status` (`pending|processing|ready|error`),
`search`, `vendor` (`shared|byoa`), `sort` (`created_at|title|status|duration_seconds`), `order` (`asc|desc`).

```bash
curl -s https://reedtv.com/api/videos?limit=50 \
  -H "Authorization: Bearer $REEDTV_API_KEY"
```

Response:
```json
{
  "success": true,
  "data": [
    {
      "videoId": 123,
      "publicId": "8f3c…",
      "title": "My video",
      "status": "ready",
      "errorReason": null,
      "duration": 612,
      "dimensions": { "width": 1920, "height": 1080 },
      "isPublic": true,
      "viewCount": 42,
      "vendor": { "type": "byoa", "accountId": 5 },
      "watchUrl": "https://reedtv.com/watch?v=8f3c…",
      "createdAt": "2026-06-01 12:00:00"
    }
  ],
  "pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
}
```

#### `GET /api/videos/{publicId}` — get one video (incl. playback + embed)
Scope: `videos:read`. Returns `video`, `playback` (hls/dash/iframe/thumbnail),
`watchUrl`, `embedUrl`, and a ready-to-paste `embedCode` iframe.

```bash
curl -s https://reedtv.com/api/videos/8f3c… \
  -H "Authorization: Bearer $REEDTV_API_KEY"
```

#### `GET /api/videos/storage` — storage usage & plan
Scope: `videos:read`. Returns shared-storage minutes used/included, overage,
BYOA video count, plan info, and spending cap.

#### `POST /api/videos` — create a video + get an upload URL
Scope: `videos:write`. Creates a video record and returns a one-time upload URL.
**The file itself is uploaded directly to Cloudflare Stream** at the returned
`upload.url` (it does not pass through ReedTV). Use `uploadType: "tus"` for
resumable uploads of large files.

Body:
```json
{
  "title": "My video",
  "description": "optional",
  "vendorAccountId": 5,
  "uploadType": "direct",
  "fileSizeBytes": 104857600
}
```
`vendorAccountId` is optional — omit (or null) to upload to ReedTV shared
storage; provide a BYOA account id to upload to your own Cloudflare Stream.

Response (`201`):
```json
{
  "video": { "videoId": 124, "publicId": "…", "vendorVideoId": "…", "status": "pending" },
  "upload": { "url": "https://upload.videodelivery.net/…", "type": "direct", "expiresAt": "…" },
  "watchUrl": "https://reedtv.com/watch?v=…"
}
```
Upload step (direct):
```bash
curl -X POST "<upload.url>" -F file=@/path/to/video.mp4
```
The video then processes asynchronously; poll `GET /api/videos/{publicId}` until
`status` is `ready` (or `error`, with `errorReason`).

#### `PUT /api/videos/{publicId}` — update metadata
Scope: `videos:write`. Body may include `title`, `description`, `ogTitle`,
`ogDescription`, `ogImageUrl`, `isPublic`, `embedSettings`.

#### `DELETE /api/videos/{publicId}` — delete a video
Scope: `videos:write`. Query `?keepRemote=true` removes it from ReedTV only and
leaves the video in your BYOA Cloudflare Stream account.

#### `POST /api/videos/{publicId}/copy-to-shared` — copy a BYOA video to shared storage
Scope: `videos:write`.

#### `POST /api/videos/bulk-copy-to-shared` — copy up to 20 BYOA videos
Scope: `videos:write`. Body: `{ "publicIds": ["…", "…"] }`.

#### `POST /api/videos/bulk-delete` — delete up to 50 videos
Scope: `videos:write`. Body: `{ "publicIds": ["…"] }`. Query `?keepRemote=true`
supported.

#### Captions
- `GET /api/videos/{publicId}/captions` — list captions (`videos:read`)
- `POST /api/videos/{publicId}/captions` — generate (`{ "action": "generate", "language": "en" }`) or upload a VTT (`{ "language": "en", "vttContent": "WEBVTT…" }`) (`videos:write`)
- `GET /api/videos/{publicId}/captions/{lang}/vtt` — download VTT (`videos:read`)
- `DELETE /api/videos/{publicId}/captions/{lang}` — delete (`videos:write`)

---

### BYOA accounts (Bring Your Own Cloudflare Stream Account)

#### `GET /api/vendor-accounts` — list connected accounts
Scope: `vendors:read`.

```json
{
  "accounts": [
    { "accountId": 5, "accountName": "My CF", "isActive": true,
      "cloudflare": { "accountId": "abc123" }, "videoCount": 87 }
  ]
}
```

#### `GET /api/vendor-accounts/{accountId}/remote-videos` — list videos in your Cloudflare Stream account
Scope: `vendors:read`. Returns the videos present in your CF Stream account,
flagged with whether each is already imported into ReedTV. Use this to discover
what to sync.

#### `POST /api/vendor-accounts/{accountId}/sync` — import remote videos into ReedTV
Scope: `vendors:write`. Body: `{ "vendorVideoIds": ["uid1", "uid2"] }` to import
specific videos. Each imported video gets a ReedTV `publicId` and `watchUrl`.

```bash
curl -X POST https://reedtv.com/api/vendor-accounts/5/sync \
  -H "Authorization: Bearer $REEDTV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"vendorVideoIds":["a1b2c3d4e5f6"]}'
```

#### `POST /api/vendor-accounts/{accountId}/verify` — re-verify credentials
Scope: `vendors:write`.

#### `POST /api/vendor-accounts` — connect a new BYOA account
Scope: `vendors:write`. Body: `{ "accountName": "…", "cfAccountId": "…", "cfApiToken": "…" }`
(token needs Cloudflare Stream:Edit). Pro/BYOA plans only.

#### `DELETE /api/vendor-accounts/{accountId}` — disconnect a BYOA account
Scope: `vendors:write`. Removes the account from ReedTV (your Cloudflare Stream
videos are not deleted).

---

### Analytics

#### `GET /api/analytics/overview` — account-wide analytics
Scope: `analytics:read`.

#### `GET /api/analytics/videos/{publicId}` — per-video analytics
Scope: `analytics:read`. Views, countries, referrers, and time series.

---

## Typical BYOA automation flow

1. `GET /api/vendor-accounts` → find your `accountId`.
2. `GET /api/vendor-accounts/{accountId}/remote-videos` → list CF Stream videos
   not yet in ReedTV.
3. `POST /api/vendor-accounts/{accountId}/sync` with their `vendorVideoIds` →
   import them.
4. `GET /api/videos?vendor=byoa&limit=100` → read back each video's `watchUrl`.
5. Paste each `watchUrl` into a post on any platform that supports rich embeds —
   the embed-compatible watch URL is what makes the player render.

Required scopes for this flow: `vendors:read`, `vendors:write`, `videos:read`.

---

## Fair use

API keys are intended for normal automation. Please keep request rates
reasonable (poll processing videos no more than once every few seconds). Abusive
traffic may be throttled or have keys revoked.
