# My TV Channel — Agent API Create and run 24/7 linear HLS TV channels programmatically. Users drop videos (or video URLs) and describe the schedule they want; an AI agent drives this API on their behalf. Channels go live at https://{handle}.my-tv-channel.com ## MCP (easiest if your client supports it) Endpoint: https://my-tv-channel.com/mcp (Streamable HTTP, stateless) https://my-tv-channel.com/api/mytvchannel/mcp — same server, longer alias. This is the URL published in the MCP registry and on Smithery; both answer identically and neither is going away. Auth: Authorization: Bearer mytv_sk_... 19 tools covering the whole surface. Setup + client config: /api/mytvchannel/mcp/README.md Human-readable version of all of this: https://my-tv-channel.com/agents/ Note: you cannot upload local files over MCP — use import_video_from_url with a public URL. ## Auth Authorization: Bearer mytv_sk_... (API key) Get a key: iOS/macOS app → Profile → Agent Access, or https://my-tv-channel.com/agent-keys/ Rate limit: 1000 requests/hour per key (429 + Retry-After when exceeded). An active subscription is required (any tier). Keys inherit the owner's tier limits. ## Base URL https://my-tv-channel.com/api/mytvchannel All responses: {"success": true, "data": ...} or {"success": false, "error": "...", "code": N} ## Start here GET /users/status.php — who am I: tier, limits, quotas, channels + launch gaps ## Channels POST /channels/create.php — {handle, name, channel_type: location|thematic, description?, primary_language?, tags?[], category?, orientation?, is_public?} (is_public is available on every tier) PUT /channels/update.php?id=N — update allowlisted fields (name, description, tags, scheduling_mode, ...) GET /channels/list.php?owner=me GET /channels/get.php?id=N — includes schedule_status / scheduleStatus (inactive|pending|active|error) and watchUrl GET /channels/check-handle.php?handle=X POST /channels/delete.php — {channel_id} archive: off-air + out of all listings. Not destructive (handle stays reserved, assets retained). 409 if transcodes are still running. Idempotent. GET /channels/schedule.php?channel_id=N — EPG (no auth) NOTE: this is the one endpoint whose "data" is a bare ARRAY, not an object. Entries: {id, channelId, assetId, startTime, endTime, isLive, title, durationSeconds, hlsUrl, ...} ## Content POST /assets/upload.php — multipart/form-data: file (MP4/MOV/M4V/MPEG/AVI), channel_id, title? POST /assets/import-url.php — {channel_id, url, title?, tags?[], weight?} import from a direct media URL. PREFER THIS over upload when you have a link — you do not need to download the file yourself. mp4/mov/m4v/mpeg/mpg/avi or a finite .m3u8 (VOD, not live). Must be a direct, public, unauthenticated link — not a web page, not behind a login. Quota is charged on the video's duration, same meter as upload. Returns immediately; status goes downloading -> pending -> transcoding -> ready. Max 20 queued imports at a time. POST /assets/import-youtube.php — {channel_id, url, title?, tags?[], weight?} import a YouTube video the owner PROVES they own. They must add the exact text "MyTVChannelApp#" to the video's description or tags on YouTube first; only the uploader can do that. If it is missing you get a 403 whose message contains the exact string to give the owner — relay it verbatim. Their tag also comes from GET /users/device.php. Public, finished videos only (no live, no playlists, no private). Max 10/day. Same quota as upload. GET /assets/my.php?channel_id=N GET /assets/status.php?channel_id=N | ?asset_id=N — transcode polling + launch-requirement summary PUT /assets/update.php — {asset_id, title?, tags?[], weight? (0-10, 0=never scheduled)} NOTE: the id goes in the BODY as asset_id (?id= also accepted). POST /assets/delete.php — {asset_id} soft delete: stops being scheduled, cancels any running transcode, auto-regenerates. Idempotent. POST /assets/retry.php — re-queue a failed transcode GET /subscriptions/quota.php — upload/stream quota detail ## Scheduling (today) Scheduling is weighted-random: per-asset weight 0-10 (default 5) controls frequency. IMPORTANT: set an asset's weight as soon as it reports ready — NOT after all uploads finish. The channel auto-launches the instant it crosses 4 ready assets / 15 min, and the schedule is built from the weights present at that moment. Later weight changes are saved but do not air until the schedule is regenerated — call /schedule/regenerate.php below to apply them at once. Ordered mini-series: tag assets seqXX01, seqXX02, ... (XX = any 2 letters) to play in order. POST /schedule/batch-insert.php — {channelId, assetIds[], mode: end|at_time, startTime?} (Pro+ tier) POST /schedule/regenerate.php — {channel_id} rebuild the schedule NOW with current weights. Use this after changing weights on an already-live channel. Throttled to 1 per channel per 5 min (429 + Retry-After). ## Schedule rules (Pro+) — fixed slots and dayparts GET /schedule/rules.php?channel_id=N — current ruleset PUT /schedule/rules.php — {channel_id, timezone, rules[]} REPLACES the whole set POST /schedule/validate.php — same payload, saves NOTHING; returns {valid, errors[], warnings[], preview[]} with a simulated 24h grid. ALWAYS validate before PUT. Not tier-gated, so you can preview on any tier. Rule shape: time_slot: {rule_type:"time_slot", start_time:"18:00", end_time?:"21:00", days_mask:127, selector_type:"asset"|"tag"|"playlist", selector_value:, priority?:0} daypart_weight: {rule_type:"daypart_weight", start_time:"22:00", end_time:"06:00", days_mask:127, selector_type:"asset"|"tag", selector_value:..., weight_multiplier:0-10} // 0 = excluded in that window days_mask is a bitmask: bit0=Mon, bit1=Tue … bit6=Sun. 127=every day, 31=Mon-Fri, 96=weekend. Times are WALL CLOCK in the channel timezone (set `timezone` to an IANA name, e.g. Europe/Paris). With end_time a time_slot keeps drawing from its selector until that time — that is how you get "cartoons 06:00-09:00" rather than one cartoon. BOUNDARY: slots start ON-OR-AFTER their time. The programme running into a slot is never cut short, so a slot can begin a few minutes late. This is intended, not a bug. Translation examples: "news at 6pm" -> time_slot 18:00, days_mask 127, selector tag/asset "cartoons 6-9am weekdays"-> time_slot 06:00-09:00, days_mask 31, selector tag "these 5 in order" -> time_slot, selector_type playlist, selector_value [ids] "more music at night" -> daypart_weight 22:00-06:00, tag music, multiplier 3 "no kids stuff after 10" -> daypart_weight 22:00-06:00, tag kids, multiplier 0 Manual rows placed by batch-insert.php now SURVIVE regeneration (they used to be wiped). ## Launch requirements A channel goes live automatically when it has >= 4 ready assets AND >= 15 minutes of content (weight > 0). Transcoding is automatic after upload; poll assets/status.php. Then the stream is at https://{handle}.my-tv-channel.com ## Full agent guide https://my-tv-channel.com/api/mytvchannel/docs/AGENTS.md ## OpenAPI 3.1 schema https://my-tv-channel.com/openapi.yaml (also at /api/mytvchannel/docs/openapi.yaml) 23 operations with request/response shapes. Same surface as this file, machine-readable.