# API keys Source: https://docs.agentcdn.com/api-keys Where keys come from, what each kind of key does, and which calls need no key. Most of what you put on public pages needs no key. You need a key to read or change your own account from a script or server. ## Calls that need no key * **Page blocks of a listed account.** `GET /public/creators/{slug}/page-blocks/{your video id}`. See [Put metadata on your pages](/guides/page-metadata). An unlisted account reads its own blocks with an account API key. See [Listed and unlisted accounts](/concepts/accounts). * **Public ARV records and catalogs.** The `/public/...` routes in the [public API](/api-reference/overview#public-api). * **The public MCP servers.** `https://mcp.agentcdn.com/c/{slug}/mcp` for one library, and `https://mcp.agentcdn.com/mcp` for the network. See [Connect an MCP](/guides/connect-mcp). * **The account MCP in an AI app.** ChatGPT, Claude and other apps connect with a sign-in, not a key. * **The Ask box.** It works without a key, with lower limits. See [Add Ask to your site](/guides/embed-ask). ## Account API keys Account API keys call the `/v1` account API: your videos, transcripts, answers, reports, settings and imports. **1. Open the keys card** Sign in at [app.agentcdn.com](https://app.agentcdn.com). Open **Settings**, then **API and MCP**. The **API keys** card is on that page. **2. Create a key** Choose **Create API key**. Give it a name, for example "Weekly report script". Pick the access: * **Read only.** Reads your library, questions and reports. * **Can make changes.** Also edits videos, AI access, featured questions, imports and webhooks. **3. Copy it now** The full key shows once. Store it in your secret manager. The console keeps only the key's prefix, so you can tell keys apart later. > [!NOTE] > > Your account needs a connected video source before you can create a key. The card says so and links to **Imports**. Send the key as a bearer token: ```bash theme={null} curl -s https://api.agentcdn.com/v1/account \ -H "Authorization: Bearer $AGENTCDN_API_KEY" ``` ### Scopes | Console access | Scope | Allows | | - | - | - | | Read only | `account:read` | Every read on `/v1`. | | Can make changes | `account:read` and `account:write` | Every read, plus changes. | ### Changes run in two calls Most changes do not run on the first call. The first call returns a preview of what would change and a `confirm_token`. Send the same request again with that `confirm_token` to run it. A few changes that only repeat safe work run in one call. The reference says which. ### Limits and errors The account API allows up to 120 calls a minute per account. Errors come back as `{ "error": "", "message": "" }`. | Status | `error` | What to do | | - | - | - | | 401 | `api_key_required` | Send the key in the `Authorization` header. | | 401 | `invalid_api_key` | The key is wrong or revoked. Create a new one. | | 403 | `insufficient_scope` | The key lacks a scope the call needs. Create a key with the right access. | | 429 | `rate_limited` | Wait for the `Retry-After` time, then retry. | ### Revoke a key Choose **Revoke** on the key's row. Tools that use it stop working at once. ## Ask keys Ask keys are a separate kind of key. They raise the Ask limits and count Ask answers to your account. They live in the console on the **Ask page** screen, in the **Ask keys** card. See [Embed Ask on your site](/guides/embed-ask#ask-keys). > [!WARNING] > > Never put an account API key in a web page or a client app. Use a browser Ask key there instead. # API reference Source: https://docs.agentcdn.com/api-reference/overview Every public and account endpoint, with links to the live OpenAPI descriptions. All endpoints live on `https://api.agentcdn.com`. Two live OpenAPI descriptions cover most of them. OpenAPI is a standard machine-readable format for describing an HTTP API. | Description | URL | Auth | | - | - | - | | Public API | [`/public/openapi.json`](https://api.agentcdn.com/public/openapi.json) | None | | Account API | [`/v1/openapi.json`](https://api.agentcdn.com/v1/openapi.json) | Account API key | Load either file into your API client or code generator. They are always current. ## Public API No key needed. Responses follow the Agent-Ready Video (ARV) standard where noted. Errors carry a 4xx status and the `PublicApiError` shape. The ARV routes and the Ask over-limit `429` use the ARV error envelope instead. ### Discovery | Method | Path | What it does | | - | - | - | | GET | `/.well-known/arv` | ARV discovery manifest. | | GET | `/.well-known/jwks.json` | Signing keys (JWKS) for receipts. | | GET | `/.well-known/agent-card.json` | A2A agent card. A2A is an open protocol for agents to describe themselves to other agents. | | GET | `/public/creators` | The public library registry. | ### Catalogs and videos | Method | Path | What it does | | - | - | - | | GET | `/public/creators/{slug}/page-blocks/{customer_video_id}` | The page block for one video by your own id. `format=json`, `head` or `body`. No key for a listed account. An unlisted account sends its account API key. See [Put metadata on your pages](/guides/page-metadata). | | GET | `/public/creators/{slug}/arv.json` | The library's ARV namespace manifest. | | GET | `/public/creators/{slug}/arv/catalog.json` | The library's ARV 1.0 catalog. Add `?page=N` for further pages. | | GET | `/public/arv/catalog.json` | One ARV 1.0 catalog over every public library. | | GET | `/public/creators/{slug}/videos/{video_id}/arv.json` | The ARV record for one video. An unlisted account sends its account API key. | | GET | `/public/creators/{slug}/llms.txt` | The library's index for language models. | ### Search and topics | Method | Path | What it does | | - | - | - | | GET | `/public/creators/{slug}/ask` | Search the library in natural language. Returns moments. | | GET | `/public/creators/{slug}/suggested-questions.json` | Suggested questions grounded in the library. | | GET | `/public/creators/{slug}/topics.json` | The library's topic map. | | GET | `/public/creators/{slug}/topics/{topic_id}/takes.json` | Takes on one topic. | | GET | `/public/creators/{slug}/topics/{topic_id}/channel.json` | A topic channel. | ### Ask `managed-ask`, `moment-agent` and `ask-widget.json` take an optional Ask key. `ask-events` takes none. See [Add Ask to your site](/guides/embed-ask#ask-keys). | Method | Path | What it does | | - | - | - | | POST | `/public/creators/{slug}/managed-ask` | An Ask answer with its moments. The Ask box uses this route. | | GET, POST | `/public/creators/{slug}/moment-agent` | The Moment Agent: an answer that leads to the moment. | | GET | `/public/creators/{slug}/ask-widget.json` | The Ask box configuration. | | POST | `/public/creators/{slug}/ask-events` | Report plays of moments an Ask answer cited, from your own player. No key and no cookies. Send the body as `text/plain` to avoid a browser preflight. | ### Usage | Method | Path | What it does | | - | - | - | | POST | `/public/creators/{slug}/usage` | Report a use of a moment in one library. Returns a signed receipt. | | POST | `/public/usage` | Report a use of a moment across the network. | ## Account API Send an account API key as `Authorization: Bearer `. Reads need `account:read`. Changes need `account:write` and run in two calls: a preview with a `confirm_token`, then the same call with the token. Up to 120 calls a minute per account. See [API keys](/api-keys). Every route is listed with its tool name in [Account API and MCP tools](/reference/account-tools). Request schemas are in [`/v1/openapi.json`](https://api.agentcdn.com/v1/openapi.json). The main groups: | Group | Endpoints | | - | - | | Account and profile | `GET /v1/account`, `GET` and `PATCH /v1/profile`, `GET` and `POST /v1/links` | | Members and Kajabi | `GET` and `POST /v1/members`, `POST /v1/members/collections`, `POST /v1/member-libraries/{library_id}`, and the routes under `/v1/membership/kajabi`. See [Paid and member libraries](/guides/member-libraries). | | Videos | `GET /v1/videos`, `GET` and `PATCH /v1/videos/{video_id}`, `GET /v1/videos/{video_id}/transcript`, `GET /v1/videos/{video_id}/timeline`, `GET /v1/videos/{video_id}/youtube-analytics`, `POST /v1/videos/{video_id}/actions`, `POST /v1/videos/{video_id}/range-link` | | Answers | `POST /v1/ask`, `POST /v1/moments`, `GET /v1/memory` | | Questions and reports | `GET /v1/questions`, `GET /v1/reports/ask`, `GET /v1/reports/agent-usage` | | AI access and answers | `GET` and `PUT /v1/agent-access`, `GET` and `PATCH /v1/answer-settings`, `PUT /v1/featured-questions`, `GET` and `POST /v1/discovery` | | Imports and catalog | `POST /v1/imports`, `GET /v1/imports/{import_id}`, `GET` and `POST /v1/catalog-source`, `POST /v1/catalog-source/imports`, `GET` and `POST /v1/sources` | | Website | `POST /v1/website`, `POST /v1/website/{site_id}/check` | | Keys, apps and webhooks | `GET` and `POST /v1/ask-keys`, `GET /v1/connected-apps`, `POST /v1/connected-apps/revoke`, `GET` and `POST /v1/webhooks`, `DELETE /v1/webhooks/{webhook_id}` | | AI Video Visibility | `GET /v1/visibility`, `POST /v1/visibility/brands`, and the report, answers, refresh, question and website check routes under `/v1/visibility/brands/{brand_id}` | The same operations are tools on the [account MCP](/guides/connect-mcp#account-mcp). # Listed and unlisted accounts Source: https://docs.agentcdn.com/concepts/accounts A listed account has public AgentTV pages and a public MCP server. An unlisted account publishes only on your own site, through your API key. An account is listed or unlisted. We set this with you when we set up the account. | | Listed | Unlisted (your site only) | | - | - | - | | Page block | Public. No key. Your CDN may cache it. | Your server sends your API key. Private, no shared cache. | | ARV record | Public | Your server sends your API key | | Links in the block | AgentTV watch page, plus discovery links to the ARV record and MCP server | Your own video page, opened at the moment by your player's start rule. No AgentTV links. | | AgentTV watch pages and library page | Yes | No | | Library MCP server for anyone | Yes | No | | Account API and account MCP for your team | Yes | Yes | | Ask box script on your pages | Yes | No. Build your own Ask on the account API. | ## Unlisted: read with your key Send an account API key with `account:read` as a bearer token. The key must belong to the account in the URL. ```bash theme={null} curl -s "https://api.agentcdn.com/public/creators/your-brand/page-blocks/catalog%3Avideo%3A1234" \ -H "Authorization: Bearer $AGENTCDN_API_KEY" ``` ```bash theme={null} curl -s "https://api.agentcdn.com/public/creators/your-brand/videos/vid_123/arv.json" \ -H "Authorization: Bearer $AGENTCDN_API_KEY" ``` * Call it from your server or build step only. Never put the key in a page. * The answer is `Cache-Control: private, no-store`. Keep it in your own server cache, not a shared CDN. * Without a key, or with another account's key, the answer is the same `404` as for an unknown video. * The video must be live and have its own page in your catalog. Otherwise the answer is `404`. * Each key read is logged against your account. * Nothing is added to an AgentTV sitemap, directory, watch page or catalog. ## Which to pick * Pick **listed** when you want the public MCP server, the Ask script and AgentTV watch pages, for example to demo playback in ChatGPT or Claude to anyone with the link. * Pick **unlisted** when every public surface must stay on your own domain. Your pages carry the metadata, and your team uses the account API and account MCP. Either way, your rights apply the same. See [Rights and access](/concepts/rights). # The ARV record Source: https://docs.agentcdn.com/concepts/arv-record One JSON record per video that tells AI what is in it, who owns it and how it may play. The page block and every AI answer come from it. ARV (Agent-Ready Video) is an open standard: one record per video that tells AI what is in the video, who owns it and how it may play. The spec lives at [agentreadyvideo.org](https://agentreadyvideo.org). AgentTV builds an ARV record for every video it indexes. ARV does not replace schema.org. Search engines keep reading `VideoObject` JSON-LD at crawl time. The ARV record carries what JSON-LD has no place for, such as timed moments, on-screen text and rights, for agents at inference time. See [Crawl time and inference time](/concepts/crawl-and-inference). ## One record, several views There is one record per video. AgentTV serves views of it in the places each reader looks: | View | Where | Who reads it | | - | - | - | | Page block, `head_html` | Your page head: JSON-LD plus link tags | Search engines and AI crawlers | | Page block, `body_html` | Your page body: chapter summaries and transcript | People, crawlers that read visible text | | ARV record | `GET /public/creators/{slug}/videos/{video_id}/arv.json` | Agents that follow the link tag | | MCP tools | `https://mcp.agentcdn.com/c/{slug}/mcp` | AI apps with the connector turned on | Your page template makes one request for the page block. You do not assemble the record yourself. ## What the record holds Read a live one: ```bash theme={null} curl -s https://api.agentcdn.com/public/creators/sandersaar/videos/video_t82C_EYja18/arv.json ``` The record sits under `data`: | Field | What it is | | - | - | | `asset` | The video id, duration, spoken and subtitle languages ([Languages](/concepts/languages)), where it came from and how it plays at its origin. | | `display` | Title, description, thumbnail, publish date and watch link. | | `chapters` | Titled sections with start and end in milliseconds, a summary, the words said and a description of what is shown. | | `moments` | Addressable spans, each with a `moment_uri` such as `arv:video_t82C_EYja18#t=0,38071` and a link that opens at the start. | | `transcript_cues` | Timed sentences with speaker labels and names when known. | | `visual` | Time windows with a description of what is on screen and the on-screen text. | | `speakers`, `people_spans` | Who speaks and who appears, and when. | | `entities`, `products`, `places`, `topics`, `labels` | What the video covers, with times. Entities link to Wikidata where they match. | | `rights` | The rights in force for the video: search, cite, summarize, quote, play, clip, train and more. See [Rights and access](/concepts/rights). | | `model_provenance` | Which models made each layer, with versions and dates. | All times in the record are milliseconds from the start of the video. The page block's JSON-LD uses seconds, as schema.org does. ## Freshness AgentTV rebuilds a record when a model improves or when you change a video's details or rights. Your page gets the newest record on its next fetch of the page block. Subscribe to the `video.updated` [webhook](/guides/webhooks) to know when to fetch again. ## What the record never holds A stream URL. Search and discovery never return one. A stream URL comes only from a playback call that checked the rights first. See [Play a moment in an AI app](/guides/play-in-chat). # Crawl time and inference time Source: https://docs.agentcdn.com/concepts/crawl-and-inference AI apps learn about your video at two moments: when a crawler reads your page, and when an agent answers a question. Each moment reads a different format. An AI app meets your video twice. * **Crawl time.** A search engine or AI crawler reads your page ahead of any question, and stores what it finds in its own index. It reads structured data: JSON-LD. * **Inference time.** A person asks a question. The AI app looks for an answer right then. It can read your ARV record, call MCP tools, or use tools your page offers. It reads rich data: every moment, the transcript, and the rights. JSON-LD is the structured data format search engines read from a ` ``` Video Ask, for one video's page. It answers from that video only: ```html theme={null} ``` Put the tag in the page body where the box should appear. The box renders right after the tag. You can load the tag more than once on a page. Each tag gets its own box. ## Attributes | Attribute | Required | Value | What it does | | - | - | - | - | | `data-creator` | Yes | Your account slug, for example `your-brand` | Picks the library Ask answers from. | | `data-video` | No | The AgentTV video id (for example `vid_123`) or the YouTube video id | Answers from that video only. If the video is not in your library, the box answers from the whole library. | | `data-target` | No | A CSS selector, for example `#video-ask` | Renders the box inside that element instead of after the tag. | | `data-theme` | No | `light` (default), `dark` or `auto` | `auto` follows the visitor's system setting. | | `data-key` | No | A browser Ask key | Raises the limits and counts answers to your account. See [Ask keys](#ask-keys). | > [!NOTE] > > `data-video` takes letters, digits, `_` and `-` only. Your own platform ids, such as `catalog:video:1234`, do not work there. Use the AgentTV video id. The page block's `video_id` field gives it. ## Example ```html theme={null}
``` The `min-height` keeps the page from moving when the box loads. ## Content Security Policy If your site sets a Content Security Policy, allow these: ```text theme={null} script-src https://agenttv.com connect-src https://api.agentcdn.com frame-src https://www.youtube-nocookie.com img-src https://i.ytimg.com ``` ## Match your look The box renders in its own isolated area. Your CSS does not change it, and its CSS does not change your page. To use your own colors or font, set these on the box: ```css theme={null} .agentcdn-ask { --agentcdn-ask-accent: #0b5cad; --agentcdn-ask-accent-fg: #ffffff; --agentcdn-ask-bg: #ffffff; --agentcdn-ask-fg: #20201e; --agentcdn-ask-muted: #62625d; --agentcdn-ask-border: #deded9; --agentcdn-ask-font: inherit; --agentcdn-ask-radius: 16px; } ``` The box carries `data-state` (`loading`, `ready` or `error`) if you want to style it while it loads. ## How moments play The Ask box plays moments from YouTube, in the box, starting at the cited second. A moment YouTube cannot play shows a link to the source page instead. The Ask box does not play your own player or your own HLS stream. For a catalog that is not on YouTube, each moment shows a link to your page. To play moments in your own player, build your own Ask. ## Build your own Ask Your page sends the question to AgentTV and plays each moment in your own player at its start second. You own the look, the player and the tracking. > [tab] Listed account > > Call the Ask route from your server with a server Ask key, or from the browser with a browser Ask key: > > ```bash theme={null} > curl -s -X POST https://api.agentcdn.com/public/creators/your-brand/managed-ask \ > -H "Authorization: Bearer $AGENTCDN_ASK_KEY" \ > -H "Content-Type: application/json" \ > -d '{"question": "Who won the women'\''s final?", "video_id": "vid_123"}' > ``` > > Leave out `video_id` to answer from the whole library. The body also takes `youtube_video_id` in place of `video_id`, `max_results`, and `cursor` for the next page. The answer holds a short text and the moments behind it, each with its video id and start and end time. > [tab] Unlisted account > > Call the account API from your server, with an account API key. Never from the browser. > > ```bash theme={null} > curl -s -X POST https://api.agentcdn.com/v1/ask \ > -H "Authorization: Bearer $AGENTCDN_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{"question": "Who won the women'\''s final?", "video_id": "vid_123", "include_private": false}' > ``` > > For a fast jump with no written answer, such as "take me to the second set", use `POST /v1/moments`. See [Build a rail from chapters](/guides/moment-rails#4-let-people-jump-by-asking). Moments name videos by the AgentTV `video_id`. The page block of each video gives you both ids, so map them once when you render the page. Then open your player at the moment's start with your start parameter. ## Ask keys Ask works without a key. A key raises your limits and counts every answer to your account, so you can see usage per key. Create Ask keys in the console on the **Ask page** screen, in the **Ask keys** card. A key looks like `agcdn_ask_...`. The full key shows once. | Kind | How you make it | Where it lives | Sent as | | - | - | - | - | | Browser key | List your sites under "Allowed websites" | In the script tag on those sites | `data-key="agcdn_ask_..."` | | Server key | Leave "Allowed websites" empty | Your server or agent only. Never in a web page. | `Authorization: Bearer agcdn_ask_...` | A browser key is visible to anyone who opens your page. That is expected. It works only from the sites you list. Rules for allowed websites: * One exact origin per line, for example `https://www.example.com`. No paths and no wildcards. * https only. http works for `localhost` and `127.0.0.1`, for local tests. * Up to 20 sites per key. A server key sent from a browser is refused with `access_key_requires_origin_allowlist`. If you see that error, the key is exposed. Rotate it and use a browser key. **Rotate** makes a new key with the same settings. The old key keeps working for 24 hours, then stops. **Revoke** stops a key within a minute. ### Limits | Caller | Ask answers | | - | - | | No key | A limit per visitor network | | Browser key | A limit per visitor network, plus the key's own limit | | Server key | The key's limit: 1,200 a minute, bursts of 200. Ask us to raise it. | These limits cover the Ask box and the Ask routes: `managed-ask`, `moment-agent` and `ask-widget.json`. Plain search (`GET /public/creators/{slug}/ask`) has its own limit of 60 calls a minute per caller. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers with the limits in force. Past a limit, Ask answers `429` with a `Retry-After` header in seconds. Your plan includes a number of Asks per calendar month. Every Ask that runs counts, also one that finds no answer. Errors and refused over-limit requests do not. ### Errors | Status | Message | What to do | | - | - | - | | 401 | `invalid_access_key` | The key is wrong, revoked or past its rotation window. | | 403 | `access_key_scope_denied` | Use a key with the `ask` scope. | | 403 | `access_key_creator_mismatch` | Use the key made for this library. | | 403 | `access_key_origin_not_allowed` | Add this site to the key's allowed websites. | | 429 | `public_ask_rate_limited`, `access_key_rate_limited` | Wait `Retry-After` seconds. | | 429 | `ASK_BUDGET_EXHAUSTED` | The month's Asks are used up and Ask is paused. This error comes in the ARV error envelope, with `retry_after_ms`. | ## What visitors see * A short answer, then the moments behind it, each with a Play button. * Up to three suggested questions. * If Ask is unavailable, the box says so and keeps its size. Your page keeps working. * The box fits its column, down to phone widths. ## What is measured The questions people ask show in the console on the **Questions** page. The box sets no cookies and loads no third-party trackers. It stores no IP addresses. Email addresses and long numbers in questions are masked. Question text is deleted after 90 days. # Import a catalog Source: https://docs.agentcdn.com/guides/import-catalog Bring your own video platform's catalog into AgentTV by your own video ids, and keep it in sync. The catalog import reads your video ids from your video sitemap or JSON Feed, reads each video's details from your metadata API, and stores each video under your own id. That id is what your page template uses later to fetch the [page block](/guides/page-metadata). Videos keep playing from your stream. AgentTV copies one rendition, the tallest at or under 1080p, into private storage for analysis only: transcript, chapters, moments and the rest. It remuxes and never re-encodes. It never serves that copy to viewers. Every play uses your player or your stream. The only file that leaves it is a [range link](/guides/moment-rails#need-a-file-of-one-range) your own team asks for with its key. To start, AgentTV needs only your video ids and a way to read each video. Everything else, from chapters to people on screen, is derived from the video. ## Two ways to connect > [tab] Platform import (sitemap plus API) > > For a brand with its own video platform and metadata API. You send us the details below, and AgentTV sets up the mapping for your account. The mapping is configuration, not custom code. > [tab] Website sitemap (self serve) > > For a site whose video sitemap already lists each video's HLS stream. You connect it yourself in the console: **Imports**, then the **Video catalog** card. Your site must be verified first under **Website setup**, and the sitemap must be on that domain or a subdomain of it. Each video imports from its `video:content_loc` when that is an HLS stream (`.m3u8`) on one of your allowed stream hosts. ## What you provide The import reads a list of your videos (a video sitemap or a JSON Feed) plus your metadata API. ### 1. Your video sitemap A standard video sitemap, or a sitemap index of them. Tell us: * **The sitemap URL.** It must be https. Child sitemaps must be on the same host. * **Where the id sits.** Either in the page URL (``) or in the player URL (``). * **A pattern for the id.** A regular expression with one capture group that pulls your id out of that URL. Entries it does not match are skipped. From each entry, AgentTV reads the title, description, publish date (`video:publication_date`), `lastmod`, duration and thumbnail when present. Publish dates help AI apps judge how fresh a video is, so include them. Large sitemaps are fine. AgentTV reads the sitemap in the background into a stored index and reads it again every 12 hours. Nothing waits on a live sitemap read. If your sitemap gives each video a `video:title`, search in the console covers your whole catalog at once. ### A JSON Feed instead of a sitemap In place of a sitemap, the list can be a [JSON Feed 1.1](https://jsonfeed.org/version/1.1). AgentTV tells the two apart by the document. Each item is one video: | Item field | What AgentTV reads | | - | - | | `id` | Your video id. No id pattern is needed. | | `url` (else `external_url`) | The video's page. | | `date_modified` | When the item changed. A moved date reads the item again. | | `title`, `summary`, `image`, `date_published` | Shown in the console browse. The import itself takes its details from your metadata API. | | `language` (item, else feed) | The language of the video's title, description and page. | | `attachments[].url` ending in `.m3u8` | The HLS stream, with its `duration_in_seconds`, for a source with no metadata API. | Three optional fields go under an `_agentcdn` object on the item: ```json theme={null} { "id": "1234", "url": "https://www.example.com/videos/1234", "title": "Final day replay", "date_modified": "2026-10-30T18:00:00Z", "language": "en", "_agentcdn": { "default_audio_language": "es", "window": { "start": 3600, "end": 9000 }, "expires": "2026-11-23T08:20:25Z" } } ``` * `default_audio_language` is the language the video is spoken in (BCP 47). It holds until the transcript detects the language. It can differ from `language`: an English page for a video spoken in Spanish. See [Languages](/concepts/languages). * `window` is the one time range of the video to import, in seconds of the source. The feed owns it: an item with a window imports only that range, and a console pick cannot change it. An item with no window imports whole. * `expires` is when your licence for the video ends. After it, the video is not played, listed or used in answers. A changed or removed `expires` moves or clears the end on the next read. An `expires` that is not a time keeps the item out of the list. A feed of several pages names the next one in `next_url`, on the same host. An item with no usable id or page URL is skipped. Caption tracks come from the HLS stream's subtitle renditions. ### 2. Read access to your metadata API AgentTV reads one item per video from your API. Tell us: * **The API origin**, for example `https://api.example.com`. Every request and redirect stays on this origin. * **The item path** with `{id}` where your id goes, for example `/v3/videos/{id}`. * **Any headers** the API needs, and an optional token step: one call at the start of each run that returns a token for the item reads. * **Where each field sits** in the response, as a dot path such as `data.attributes.title`: | Field | Required | Notes | | - | - | - | | Title | Yes | | | Description | No | | | Duration | No | In seconds, milliseconds or ISO 8601. Leave it out and AgentTV sums the segment lengths in the HLS media playlist. | | Poster | No | Public https only. Falls back to the sitemap's `video:thumbnail_loc`. | | Captions | No | Caption tracks the API lists. Subtitle tracks in the HLS playlist are read too. | | Playback URL | Yes, or a template | See below. | ### 3. The playback URL Pick one: * **A field in the API answer** that holds the HLS master playlist URL. * **A manifest URL template** when your playback URL follows from the id. It is an https URL with `{id}` in the path or query, for example `https://play.example.com/{id}/master.m3u8`. The id is URL-encoded. The host is fixed in the template, so an id can never pick the host. HLS (HTTP Live Streaming) is the streaming format that uses `.m3u8` playlists. ### 4. Your stream hosts AgentTV plays and reads streams only from hosts on your account's allow list. Send us each stream host, and each caption host if it differs. Rules: * https only. No ports, no credentials, no private addresses. * One account's list never serves another account's video. * To remove a host later, tell us. Videos on that host stop importing and playing through AgentTV. > [!NOTE] > > AgentTV reads your API and plays your streams only after you give your written OK. ## Pick what to import Nothing from your back catalog imports on its own. In the console, open **Imports** and the **Video catalog** card. You browse your catalog newest first, search it, and pick videos. One pick takes up to 100 videos. AgentTV copies and processes them in the background. The account MCP tool `import_catalog_items` (`POST /v1/catalog-source/imports`) does the same. See [Account tools](/reference/account-tools). Each video shows a status: | Status | Meaning | | - | - | | New | Not in your library yet. | | Queued | Picked and waiting, or still processing. | | Imported | In your library and ready. | | Changed | Ready, and the source changed since. Pick it again to refresh it. | | Failed | Processing stopped. It needs review. | | Removed | No longer in your catalog. | | Unavailable | It cannot import. The reason shows next to it. | ## New videos and the daily sync The day you connect the catalog sets the baseline. Every video listed then is back catalog. A video that first appears after that is a new video. Once a day, AgentTV reads your catalog again. It refreshes imported videos whose `lastmod` moved, and it handles new videos by your **new video policy**: | Policy | What the daily sync does | | - | - | | `review_first` | New videos wait for you to pick them. This is the default. | | `automatic` | New videos import on their own. Not available on the starter plan. | | `off` | No daily sync. The source is paused. | Under `automatic` and `review_first`, videos you already imported still refresh. ## What is refused A video does not import when its stream is: * **Encrypted or DRM protected.** AgentTV cannot read the media. * **Live.** Only finished video on demand imports. A live slot with no replay shows as "stream not found". * **Byte-range.** Playlists that address segments by byte range. * **On a host that is not on your list.** * **Too large.** The largest copy is 40 GB, enough for a 14 hour replay at 6 Mbps. AgentTV also refuses a child sitemap on another host, and an API redirect off your API origin. AgentTV also skips a feed item past its `expires` (`item_expired`) and a feed item whose `window` is not two numbers with the start before the end (`window_malformed`). The reason shows on the video in the console, for example `media_encrypted`, `live_playlist`, `media_byterange`, `stream_host_not_allowed` or `media_too_large`. A few reasons can pass on their own (`stream_unreachable`, `copy_failed`, `metadata_unreadable`). Pick those videos again to retry. ## Long videos The import handles event replays of 4 to 14 hours and several GB. A copy that stops halfway resumes where it stopped. Transcription takes at most 10 hours of audio per file. For a longer replay, import one time range of it. ### Import one time range You can import only part of a video, for example one day of a multi-day replay. In the console, set the range on the video before you pick it. With `import_catalog_items`, send `windows`: one `start_seconds` and `end_seconds` per picked video, in seconds of the video as your source plays it. * The imported video is that range. Its times start at 0 at the range's start. * HLS is cut on whole segments, so the range widens to the segment edges. * A range can be at most `window_max_seconds` long. `get_catalog_source` returns that limit. * A video your JSON Feed gives a `window` keeps the feed's range. # Measure usage Source: https://docs.agentcdn.com/guides/measure See which AI apps cite and play your videos, what people ask on your site, and which AI crawlers fetch your pages. Every use of your videos through AgentTV is logged against your account: citations, plays, Ask answers and key reads. Four reports read those logs. ## AI app usage Which AI apps cited or played your videos in a date range, and how often. ```bash theme={null} curl -s "https://api.agentcdn.com/v1/reports/agent-usage?from=2026-10-01&to=2026-10-28" \ -H "Authorization: Bearer $AGENTCDN_API_KEY" ``` The default range is the last 30 days. The account MCP tool `get_agent_usage` returns the same. ## Usage receipts An AI app that shows, quotes, cites or plays a moment can report it with the `record_usage` MCP tool, or with `POST /public/creators/{slug}/usage`. It gets back a signed receipt. Check a receipt against the public keys at `https://api.agentcdn.com/.well-known/jwks.json`. Receipts let you audit what an app says it did with each moment. ## Ask on your site * **Questions.** Every question people ask your Ask box or your library, grouped, with no-answer questions marked. In the console under **Questions**, or `GET /v1/questions`. * **Ask report.** Totals for a date range: asks, answered, no answer, plays, moments reached, and the top questions, moments, videos and topics. `GET /v1/reports/ask`. The Ask box measures each play in its player: the play, whether the cited moment was reached, and how much of it was watched. It sets no cookies and loads no third-party trackers. ## AI crawler visits Send your CDN logs to see which AI crawlers fetch your video pages. AgentTV keeps only AI crawler hits on your own sites, checks each crawler against its published IP ranges or reverse DNS, drops the IP, and adds the hit to daily counts. Unverified hits are counted apart. **1. Create a log source** In the console, open **Visibility**, then the **AI crawler visits** card, then **Set up the log feed**. Pick your CDN and your site hosts. You get an intake URL and a secret. The secret shows once. **2. Point your CDN at it** Supported: Vercel log drains, Cloudflare Logpush, Akamai DataStream 2 (JSON), Fastly HTTPS logging and Amazon CloudFront real-time logs through Firehose. The console gives the exact fields and settings for each. **3. Read the counts** The card shows one site at a time, never summed across sites. The Visibility report adds a "From crawl to citation" view: per crawler operator, its verified requests to your site and the video pages it fetched. You can only add hosts you have proven you own: by the DNS or TLS check, or by a Search Console property that covers the host. A fetched page is never called indexed: no one can see inside an AI app's index. ## Events to your systems To act on changes as they happen, use [webhooks](/guides/webhooks). # Paid and member libraries Source: https://docs.agentcdn.com/guides/member-libraries Make videos members-only, so only the people in your paid community, course or membership can search and ask on your member page, and watch in AI apps. Run a paid community, course or membership? Put those videos in a members-only collection. Only the members you give access to can search them, ask questions about them and watch them. They do this on your AgentTV member page and in AI apps like Claude and ChatGPT over MCP. Everyone else gets nothing from those videos, unless you let AI apps cite them free. Then a public answer can name a members-only video, and the person needs a membership to watch it. ## Get set up Member access needs setup on your account. [Ask us to set it up](https://agenttv.com/support). Say if you use Kajabi. Until it is on, **Settings, Members** in the [console](https://app.agentcdn.com/settings/members) shows **Member access is not on** with a **Request setup** button. ## How it fits together | Piece | What it is | | - | - | | **Collection** | A members-only library of videos. Its videos leave your public pages, public search and public AI answers. | | **Member** | A person, saved by email. A member sees nothing until you give them access. | | **Access** | One member's access to one collection, with an optional end date. Members can hold access to several collections. | Access is the only thing that opens a collection. It does not override the rights you set on each video: a member can search, cite and quote a video only where its rights allow it. ## Make a members-only collection **1. Open Collections** In the console, open **Settings, Members**. Under **Collections**, choose **Create collection**. **2. Pick what goes in it** Choose one: * **Choose videos**: pick a public library, then pick up to 50 of its live public videos and name the collection. * **Use a whole library**: pick a library and choose **Make library members only**. Every public video in it leaves public pages and discovery. **3. Give a reason and confirm** Every change asks for a reason. It goes in the activity log. Members do not see it. You need a public library first. If you have none, import videos, then come back. To change a collection later, choose **Manage** on its row: * **Move videos out** returns chosen videos to a public library. Members lose access to those videos. Each video shows publicly again only where its own settings allow. * **Delete collection** moves every video to a public library and ends all member access to it. Your default library cannot be deleted, but you can still move its videos out. ## Add members and give access **1. Add a member** Under **People**, choose **Add member**. Enter the member's name and email. To change a saved name, save the same email again. **2. Give access to a collection** Under **Who gets which**, choose **Grant access**. Pick the member and the collection. Set **Access ends** for a date, or leave it empty for no end date. **3. Send them the sign-in link** Saving a member sends no invitation. Under **People**, copy the **Sign-in link** and send it to the member yourself. It looks like `https://agenttv.com/members/your-brand/sign-in`. The link alone does not give access. To take access away: * **Revoke** on a row in **Who gets which** ends that one access. Other access the member holds still applies. * **Disable** on a row in **People** ends all of that member's access and signs them out at once. The **Status** column shows each access as **Active**, **Expired**, **Revoked** or **Member disabled**. ## Let AI apps cite members-only videos Free citation means AI apps may name your members-only videos in public answers. Only members can watch them. A public search result for such a video carries the video and chapter titles and the time range. It carries no transcript text and no link to the video. Turn it on for each collection: 1. In **Collections**, choose **Manage** on the collection. 2. Turn on **AI apps may mention this collection**. 3. Optional: add a **Join link**, the page where people can join. AI apps then offer a **Join to watch** action that opens it. 4. Optional: set **Free preview for non-members** to **30 s**, **1 min** or **2 min**. Non-members can then watch that much of a cited moment, never the full video. 5. Enter a reason and choose **Save**. Each collection row shows **AI apps can mention it** or **Hidden from AI apps**. Turning it off stops AI apps from naming the videos and ends any free preview. ## What a member gets > [tab] On your member page > > 1. The member opens your sign-in link and enters their email. > 2. They get an email with a sign-in link. They open it and choose **Continue**. > 3. They land on **Your member libraries**. They open a library to browse its videos and use **Ask the library** to get answers from those videos only. > > To watch a video, they ask for it in their AI app. > [tab] In an AI app > > Members use your public MCP URL. MCP (Model Context Protocol) is how AI apps connect to outside tools. > > ```text theme={null} > https://mcp.agentcdn.com/c/your-brand/mcp > ``` > > Add it in the app the same way as any public library. See [Library MCP](/guides/connect-mcp#library-mcp). > > When the member asks to watch a members-only video, the app asks them to sign in. The sign-in page asks for their email, then for a 6 digit code sent to it. It can also offer **Continue with Google**. The code expires in 10 minutes. The member must use the email you saved. > > After sign-in, search and answers include the collections the member has access to, and the member can play those videos in the chat. Nothing else changes: they still see your public videos as before. ## What a non-member sees | Collection setting | In public answers | When they try to watch | | - | - | - | | Free citation off | Nothing. The videos do not appear. | Not offered. | | Free citation on, no preview | The video named, with titles and time range only. | The app asks them to sign in as a member. **Join to watch** opens your join link, if you set one. | | Free citation on, with preview | The same, plus the preview length. | They watch up to 30 s, 1 min or 2 min of the cited moment. | Your public pages, public search and the public Ask never show members-only videos. ## Connect Kajabi Connect Kajabi and people who buy your Kajabi offers become members on their own. Each offer feeds a members collection. Kajabi does not share lesson videos, so you upload each course video once, and it lands in the right collection. > [!NOTE] > > Kajabi's API needs the Kajabi Pro plan. Kajabi has no app sign-in, so you paste a key. ### 1. Make a key in Kajabi 1. In Kajabi, open **Settings, Security, User API Keys**. 2. Make a key with read access to offers, products, courses, customers and purchases, and access to webhooks. 3. Copy the **Client ID** and the **Client secret**. ### 2. Connect it 1. In the console, open **Settings, Members**. Under **Membership platform**, choose **Connect Kajabi**. 2. Paste the **Client ID** and **Client secret**. Choose **Continue**. 3. AgentTV checks the key and shows the Kajabi site and how many offers it has. If the key reaches more than one site, pick one. 4. Choose **Connect**. The secret is stored encrypted and never shown again. Each offer gets its own new members collection, named after the offer, and its buyers get access. The first sync starts at once. ### 3. Choose where each offer goes Under **Offers**, each offer has a picker: * **New collection "Offer name"**: the default. * Any of your members collections. Several offers can share one. * **Don't sync**: the offer's buyers get no access from the sync. Moving an offer to another collection starts a sync. It ends the access the sync gave in the old collection. Access you gave by hand stays. ### 4. Upload your course videos Under **Course videos**, you see the lessons of every course whose offer goes to a collection, in course order. Each lesson shows **No video**, **Processing** or **Ready**. 1. Choose **Choose files** and pick your lesson video files. 2. AgentTV matches each file to a lesson: by the file name, or by a close title and a length that fits. Check each match, or pick the lesson yourself. 3. Check how much of your allowance the upload uses, then choose **Upload**. 4. Keep the page open until every file has sent. Then choose **Done**. Each video is members-only from the start, in the collection of its course. It never shows publicly. It gets the lesson's title, and its course becomes the series. A file that is already uploaded is not uploaded again. A course whose offers go to different collections cannot take videos, because a video lives in one collection. Send those offers to the same collection first. If a file did not arrive or failed, its lesson shows **Upload again**. A new file replaces it. A video that is ready is never replaced. ### How members stay in sync | When | What happens | | - | - | | Someone buys an offer | Kajabi tells AgentTV, and they become a member within seconds. | | Every hour | A full sync adds new buyers and ends access for purchases that ended. | | You choose **Sync now** | The same full sync runs at once. | Kajabi sends no event when a purchase ends, so access ends at the next hourly sync. The sync never removes access you gave by hand, and never turns a disabled member back on. If Kajabi answers only in part, the sync removes nothing. **Last sync** shows when it ran and what it changed, such as `3 added, 1 removed`. If **New buyers** says **Instant adds are off**, choose **Turn on instant adds** to register the purchase webhook again. ### Held for review If a sync would end more than half of a collection's synced access at once, it holds those removals for you. The console shows who would lose access, with **See who**. Then pick one: * **Remove** ends that access. Access you gave by hand stays. * **Keep them** keeps their access. Later syncs do not hold or remove it again. If Kajabi shows a purchase for them again, the keep ends. ### Check and disconnect * **Check again** tests the saved key. If Kajabi refuses the key, the status shows **Needs a new key**. Choose **Connect again** with a new key. * **Disconnect** deletes the saved key, and AgentTV stops reading your Kajabi site. Members keep the access they have now. To stop the key working, delete it in Kajabi under **Settings, Security, User API Keys**. ## Do it with the API or MCP Everything above also works through the [Account API](/api-reference/overview) and the [account MCP](/guides/connect-mcp#account-mcp). Changes return a preview first and run only after you confirm. See [Changes run in two calls](/api-keys#changes-run-in-two-calls). ### Members and collections | MCP tool | API route | What it does | | - | - | - | | `list_members` | `GET /v1/members` | Lists members and their access. | | `manage_member` | `POST /v1/members` | `add` a member by email, `grant` a collection (optional `expires_at`), `revoke_grant`, or `disable` a member. | | `manage_member_collection` | `POST /v1/members/collections` | `create` a collection from public videos, `release` videos back to a public library, or `delete` a collection. | | `manage_member_library` | `POST /v1/member-libraries/{library_id}` | `citation` sets free citation, `join_url` and `preview_seconds` (30, 60 or 120). `protect` makes a whole public library members-only. | ### Kajabi Use `kajabi` as `{platform}`. | MCP tool | API route | What it does | | - | - | - | | `connect_membership_platform` | `POST /v1/membership/{platform}` | Connects with `client_id` and `client_secret`. Add `account_id` when the key reaches several sites. | | `get_membership_connection` | `GET /v1/membership/{platform}` | State, site, last sync, and how many removals are held. Never returns the secret. | | `check_membership_connection` | `POST /v1/membership/{platform}/check` | Tests the saved key. No preview. | | `list_membership_products` | `GET /v1/membership/{platform}/products` | Offers and where each one's buyers go, plus your members collections. | | `map_membership_product` | `POST /v1/membership/{platform}/products/map` | Sends an offer to a `collection` (with `library_id`), to `skip`, or back to `default`. | | `sync_membership_members` | `POST /v1/membership/{platform}/sync` | Syncs now. `approve_held_revokes` lets held removals through. | | `list_membership_held` | `GET /v1/membership/{platform}/held` | Members whose removal is held for review. | | `keep_membership_held` | `POST /v1/membership/{platform}/held/keep` | Keeps the held access. | | `register_membership_webhooks` | `POST /v1/membership/{platform}/webhooks` | Registers the purchase webhook again. No preview. | | `disconnect_membership_platform` | `POST /v1/membership/{platform}/disconnect` | Deletes the saved key. | | `list_membership_lessons` | `GET /v1/membership/{platform}/lessons` | Lessons of the courses whose offers go to a collection, with each lesson's video state and the most files one upload takes. | | `prepare_membership_lesson_uploads` | `POST /v1/membership/{platform}/lessons/uploads` | Send each file's name and size, or the `lesson_id`. The preview shows the matches and the allowance used. The confirmed call returns an `upload_url` for each file. | | `complete_membership_lesson_uploads` | `POST /v1/membership/{platform}/lessons/uploads/complete` | After each file's `PUT` to its `upload_url` finishes, send its `upload_session_id`. Starts processing. No preview, and safe to call again. | > [!WARNING] > > When you connect through an AI app, the key you paste stays in that chat's history. Make a key with only the access listed above, and delete it in Kajabi if it should not stay. # Build a rail from chapters Source: https://docs.agentcdn.com/guides/moment-rails Turn the chapters of a long replay into a rail of tiles, one per match or segment, that each open your player at the right second. A long event replay is hard to navigate. AgentTV already divides it into chapters. You can show those chapters as a rail of tiles on your page, one tile per match or segment. Each tile opens the same video in your own player at the chapter's start. No editor cuts anything, and no new video files are made. ## 1. Get the chapters Pick one source: > [tab] Page block > > The page block you already fetch for the video page holds the chapters, as `Clip` items under `json_ld["@graph"][0].hasPart`: > > ```json theme={null} > { "@type": "Clip", "name": "Semi-final 1", "description": "...", "startOffset": 1840, "endOffset": 4210, "url": "..." } > ``` > > Times are seconds. See [Put metadata on your pages](/guides/page-metadata). > [tab] ARV record > > `GET /public/creators/{slug}/videos/{video_id}/arv.json` lists `data.chapters`, each with `title`, `summary`, `start_ms` and `end_ms`. Times are milliseconds. An unlisted account sends its API key. > [tab] Account API > > `GET /v1/videos/{video_id}/timeline` returns chapters with the shots, speaker turns and on-screen text around them, 10 minutes of video per call. Use it when you want to choose the tiles yourself, for example only the chapters where two named teams are on screen. > > ```bash theme={null} > curl -s "https://api.agentcdn.com/v1/videos/vid_123/timeline?start_ms=0" \ > -H "Authorization: Bearer $AGENTCDN_API_KEY" > ``` > > When `next_start_ms` is not null, call again with it as `start_ms`. ## 2. Link each tile to the moment Open your own player at the chapter's start second, with the start parameter your player takes: ```text theme={null} https://www.example.com/videos/1234?t=1840 ``` Use the same rule you gave us for [Start at the moment](/guides/playback#start-at-the-moment), so tiles, chapter links in your JSON-LD and AI answers all open the same way. ## 3. Pick a tile image AgentTV does not return an image per chapter today. Use a frame from your own player's thumbnail sprites at the chapter's start second. Most players already publish a WebVTT file that maps each time range to a sprite tile. Or make your own image template, for example both team names over a still. ## 4. Let people jump by asking A rail covers the matches you list. Ask covers the rest. Put [Ask](/guides/embed-ask) on the same page so a visitor can type "take me to the women's final" and land on that second. To answer that kind of question from your own UI, call `POST /v1/moments` from your server. It returns the matching moments fast, with no written answer: ```bash theme={null} curl -s https://api.agentcdn.com/v1/moments \ -H "Authorization: Bearer $AGENTCDN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"question": "women'\''s final", "video_id": "vid_123", "max_results": 3}' ``` Each moment has its video id, title, start and end in milliseconds, and `why` it matched. An empty result carries `no_answer`. The body also takes `library_id`, and `include_private` (default `true`: unlisted and private videos are included). Open your player at the start. ## Need a file of one range? Linking into the full replay keeps one source video for every tile. When your team needs the picture and sound of one range as a file, for example to edit a social cut, ask for a range link: ```bash theme={null} curl -s -X POST https://api.agentcdn.com/v1/videos/vid_123/range-link \ -H "Authorization: Bearer $AGENTCDN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"start_ms": 1840000, "end_ms": 1900000}' ``` The answer holds a signed MP4 `url` and its `expires_at`. Rules: * It needs a key that can make changes. It costs nothing and runs in one call, with no preview. * A range is at most 10 minutes. The file is copied from the stream without re-encoding, so the cut can land slightly off the times you asked for. * Anyone who holds the link can download the file until it expires. Share it only with people who may see the video. * It works for any video your account imported. It is the only call that returns a media file. Search and answers never do. The account MCP offers the same as `get_range_link`, so an AI app can pick a range from `get_video_timeline` and fetch only that range. # Put metadata on your pages Source: https://docs.agentcdn.com/guides/page-metadata Fetch one page block per video page by your own video id, and render it server side. Each of your video pages can carry the metadata AI and search engines read: JSON-LD with chapters, a visible transcript, and links agents follow. Your page template makes one request per page, with the id your platform already has for the video. Your CDN keeps the answer. The page block is the crawl-time view of the [ARV record](/concepts/arv-record). It is one JSON file with ready HTML for the page head and the page body. ## The request ```text theme={null} GET https://api.agentcdn.com/public/creators/{slug}/page-blocks/{customer_video_id}?format=json ``` **slug** `string` *required* Your account slug, for example `your-brand`. **customer\_video\_id** `string` *required* Your own video id, URL-encoded, for example `catalog%3Avideo%3A1234`. Up to 200 printable characters, no leading or trailing space. The [catalog import](/guides/import-catalog) stores this id. **format** `string` (default: json) * `json`: the whole block as JSON. See the [Quickstart](/quickstart#2-fetch-the-block-for-your-own-video) for the shape. * `head`: `text/html` for the page head. The JSON-LD script, the discovery link tags and, when on, the WebMCP tags. * `body`: `text/html` for the page body. Chapter summaries and the transcript. Empty when the rights allow neither. Any other value returns `404`. **Authorization** `string` Only for an unlisted account: `Bearer `. See [Listed and unlisted accounts](/concepts/accounts). The block never contains a stream URL. ## Render it server side Fetch the block in your server, edge function or build step, and write it into the HTML you send. Do not load it with a script in the browser. Many crawlers and AI fetchers read the HTML as sent and never run scripts, so metadata added in the browser stays invisible to them. ```js theme={null} // Fetch the block while rendering a video page. const base = "https://api.agentcdn.com/public/creators/your-brand/page-blocks/"; async function pageBlock(customerVideoId) { const response = await fetch(base + encodeURIComponent(customerVideoId)); if (response.status === 404) return null; // Render the page without the block. if (!response.ok) throw new Error(`page block ${response.status}`); return response.json(); } ``` Fetch at build time if you build pages ahead. Each build then gets the newest block, for example after AgentTV improves a transcript. ## One VideoObject per page A page should describe its video once. If your page already renders a `VideoObject`, merge the block into it. Do not add a second one. * **You render no VideoObject.** Put `head_html` (or the `format=head` response) in your page head as it is. * **You already render a VideoObject.** Use `format=json`. Take the fields you lack from `json_ld["@graph"][0]`, most of all `hasPart` (the chapters as `Clip` items), and add them to your own object. Keep your own `@id`, `url`, `embedUrl`, `contentUrl` and player fields. Then render each of `discovery.links` as a `` tag in your head. ```html theme={null} ``` ### Point chapter links at your own page Each `Clip` in `hasPart` has a `url` at the chapter's start second. For a listed account it points to the AgentTV watch page. Search engines show key moments best when each `Clip` url is your own page, opened at that second. When you merge, rewrite each url to your page with your player's start parameter, and keep `startOffset` as it is: ```json theme={null} { "@type": "Clip", "name": "The landing", "startOffset": 60, "endOffset": 120, "url": "https://www.example.com/videos/1234?t=60" } ``` For an unlisted account the block already does this, with the start rule we set for your player. See [Start at the moment](/guides/playback#start-at-the-moment). ### WebMCP tags when you merge When WebMCP site tools are on for your account and your site is verified, `head_html` ends with two more tags. They are not in the JSON fields. If you merge the JSON, copy them from the `format=head` response into your head: ```html theme={null} ``` They let an AI agent in the visitor's browser search your catalog and play a moment on the page. See [Crawl time and inference time](/concepts/crawl-and-inference#the-three-ways-in-at-inference-time). Copy them as served: the version and the ids can change. ## Chapters and the transcript * **Chapters** come as `Clip` items in the JSON-LD, with a name, a description, and start and end offsets in seconds. * **The transcript** comes as `transcript.cues`, each with `start_seconds` and `text`. It is never inside the JSON-LD. * **`body_html`** is ready HTML with the chapter summaries and the transcript, with plain timestamps and no links. Put it in your page body, or build your own section from `transcript.cues`. Keep the transcript as text in the HTML you send, not in a script or a data attribute. A toggle that collapses it is fine. ## Parts A long video often has its own structure: each match of an event, each talk of a conference, each segment of a show. AgentTV finds it from the chapters and serves it as `parts`. Each part is a run of the video's chapters in a row, with its own title, description and thumbnail. Use it to lay out a long video by its structure, for example a rail of matches. ```json theme={null} "parts": [ { "id": "part_1", "title": "Semifinal: Team A against Team B", "description": "The first semifinal, from the walk-on to the final point.", "start_ms": 0, "end_ms": 4980000, "thumbnail_url": "https://api.agentcdn.com/...", "source_ref": null, "chapters": [ { "id": "ch_1", "title": "Walk-on", "start_ms": 0, "end_ms": 240000, "thumbnail_url": "https://api.agentcdn.com/...", "source_ref": null } ] } ] ``` * `parts` is `[]` when the video has none. * Times are in milliseconds from the start of the video. * A `thumbnail_url` is `null` when your rights do not allow showing the picture. * On a read with your account key, each `thumbnail_url` is a signed frame URL that is valid for about an hour. Fetch the block again for fresh URLs. Do not store them as permanent links. In the JSON-LD, each part is a `Clip` in `hasPart`, with its chapters as nested `Clip` items. A video with no parts keeps its chapters as flat `Clip` items. ## Language The block states the video's spoken language and subtitle languages as `language_primary`, `language_source`, `language_confidence`, `subtitle_tracks` and `caption_languages`. The JSON-LD carries them as `inLanguage`, `subtitleLanguage` and `caption`, and `body_html` sets a `lang` attribute on the video's words. See [Languages](/concepts/languages). The transcript is present only when your rights allow quotes and the grant is not gated or limited to a territory. Chapter summaries show only when your rights allow summaries. See [Rights and access](/concepts/rights). ## Caching and traffic A listed account's `200` comes with these headers: ```text theme={null} Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=300 ETag: "sha256:..." ``` * Your CDN may keep a block for 5 minutes and serve it stale for 5 more while it refreshes. A rights change reaches your pages within 10 minutes. * Send the `ETag` back as `If-None-Match`. An unchanged block returns `304` with no body. Each format has its own `ETag`. * There is no purge call. To refresh at once, listen for the `video.updated` [webhook](/guides/webhooks) and fetch again. * Every `404` is `Cache-Control: private, no-store`. A newly published video shows on the next request. * An unlisted account's key read is always `private, no-store`. Cache it in your own server, keyed by video id, and refresh on the webhook. * A key read also has an `ETag` and answers a matching `If-None-Match` with `304`. Its `ETag` changes every hour, with the signed frame URLs, so a `304` means your cached URLs are still valid. To tell a real content change from the hourly re-signing, compare the `x-agentcdn-content-version` header. It changes only when the content changes. With a CDN in front, AgentTV sees about one request per video per CDN cache every 5 minutes, whatever your page traffic. ## When there is no block The route returns `404` with `{"error": "owned_web_artifact_unavailable"}`, never an empty `200`, when: * the id is unknown, or belongs to another account, * the video is not published, or not public on a listed account, * the video's rights block citation, * the `format` value is not one of the three above. Render the page without the block. Do not cache an empty block. # Play a moment in an AI app Source: https://docs.agentcdn.com/guides/play-in-chat How an AI app searches your moments, answers with time-coded citations, and plays the cited second inside the chat. With the AgentTV MCP server connected, an AI app can do three things with your library: 1. **Search moments.** Find the moments that answer a question, by what is said, shown and written on screen. 2. **Answer with time-coded citations.** Quote the evidence and cite the second it comes from. 3. **Play the moment.** Load a player card in the chat that starts at that second. MCP (Model Context Protocol) is how AI apps connect to outside tools. The person turns on a connector once. No code runs on your side. ## The flow ```mermaid theme={null} sequenceDiagram participant P as Person participant A as AI app participant M as AgentTV MCP participant S as Your stream host P->>A: Who won the final, and show me the run A->>M: search_videos(query) M-->>A: Moments with times, evidence, confidence. No stream URL. A->>P: Answer with the cited time A->>M: get_video_stream(video_id, segment_id) M->>M: Check rights, sign a session M-->>A: Signed playback, share link, player card A->>M: Player asks for the stream M->>S: Read segments from your allowed host S-->>A: Video plays from the cited second A->>M: record_usage(moment) M-->>A: Signed usage receipt ``` ## Connect a library Every listed library has its own MCP server. It needs no sign-in and no key. ```text theme={null} https://mcp.agentcdn.com/c/your-brand/mcp ``` * **Claude:** Settings, Connectors, Add custom connector. Paste the URL. * **ChatGPT:** Settings, Apps, Advanced settings, turn on Developer mode, then Create. Paste the URL. Developer mode needs a paid plan. * **Other apps:** see [Connect an MCP](/guides/connect-mcp). Any person you share the URL with can add it. You do not need an app directory listing. See [App directories](/guides/connect-mcp#app-directories). ## The tools | Tool | What it does | | - | - | | `search_videos` | Finds the moments that answer a question, best first, with matched text, confidence band and time range. | | `search`, `fetch` | The same search and full context, in the shape ChatGPT looks for. | | `get_moment_context` | Transcript, chapters, shot captions, on-screen text and entities for a time window. | | `get_video_stream` | A signed playback response for one moment, with the inline player card. | | `get_segment_rights` | The rights for quoting, clipping, training, licensing and playback. | | `record_usage` | Reports that the app showed, quoted, cited or played a moment. Returns a signed receipt. | | `get_memory_page` | A cited summary of the library, or of one video, topic or person. | | `get_creator_profile` | Who owns the library and what it holds. | The tool descriptions tell the AI app when to use each one. The app decides which to call. ## What plays where | AI app | Your HLS stream | Notes | | - | - | - | | Claude | Plays inline, from the cited second | | | ChatGPT | Does not play inline | ChatGPT plays MP4 files only today. For an HLS-only video, the card cannot play the file and keeps the link that opens the moment. | HLS (HTTP Live Streaming) is the streaming format that uses `.m3u8` playlists. Today the player card in the chat is the AgentTV player, so your own web player and its tags do not load inside the chat. When the AgentTV player plays your video, it reads the stream from your own hosts through AgentTV's stream proxy. Your CDN serves the bytes. The proxy reads only from hosts on your allow list. See [Play in your own player](/guides/playback). ## Playback sessions `get_video_stream` returns a signed session next to `stream_url`, `share_url` and `expires_at`. The session is signed for one video and one start time. Store `video_id` and `segment_id`, not the stream URL. After `expires_at`, call `get_video_stream` again. Search, `fetch` and the ARV record never return a stream URL. Only `get_video_stream` does, after it checks your rights. ## Tracking * Every signed play and every reported use is logged against your account. * The AI app usage report shows which AI apps cited or played your videos, and how often. Read it with `GET /v1/reports/agent-usage`. * Usage receipts are signed. Check them against the keys at `https://api.agentcdn.com/.well-known/jwks.json`. See [Measure usage](/guides/measure). ## Try it Ask in a new chat once the connector is on: * "Find the moment where the winning run starts, and play it." * "What happened in the second set? Cite the times." * "List every match in this replay with its start time." # Play in your own player Source: https://docs.agentcdn.com/guides/playback Choose where your videos play, start your player at the cited second, and stream from your own HLS hosts. For a catalog account, AgentTV never re-hosts your video for playback. A video plays either in your own web player, or in the AgentTV player from your own HLS stream. ## The Playback setting In the console, open **Settings**, then **Profile**, then **Playback**. "Play videos in" has two options: | Option | Value | What it does | | - | - | - | | Source player | `source` | Each video plays where it came from: YouTube, or your own web player. Where an app cannot run that player, the video plays in the AgentTV player with your name and a link to your page. | | AgentTV player | `agentcdn` | Videos play in the AgentTV player. | The setting is stored per account as `playback_player`. Today you change it only in the console. The account API and the account MCP do not offer it. ## Where your own player can run Your web player runs in a frame only on pages that can frame an outside player: | Surface | Your own player | YouTube | Link to your page | | - | - | - | - | | AgentTV console video page | Yes | Yes | Yes | | AgentTV watch page | No | Yes | Yes | | Ask box | No | Yes | Yes | | ChatGPT and Claude widgets | No | No | Yes | Everywhere your player cannot run, the AgentTV player plays the video from your stream, with a credit link to your page. ### What your player needs AgentTV frames your player only when all of these hold: * Your Playback setting is **Source player**. * Your video sitemap gives the video a `video:player_loc` on https. The [catalog import](/guides/import-catalog) stores it. * The video is public in your catalog. * Your rights allow playback and the source embed, and they have not expired. * The play is not members-only or a preview. > [!WARNING] > > Your player runs inside another site's iframe. It must play there without a consent banner. A cross-site frame drops cookies marked `SameSite=Lax` or `Strict`, so a player that reads consent from such a cookie shows its banner again, or a blank frame, and the moment never plays. Fix it one of two ways: serve an embed that plays without a consent step, or mark the consent cookies `SameSite=None; Secure`. The second works in Chrome only, because Safari and Firefox block third-party cookies. If your player cannot run in a frame, AgentTV can play your own HLS stream in its player instead, on the console video page, at the moment. Your player stays the fallback. We turn this on per catalog source, with your written OK. The source embed right is off by default for a catalog account. AgentTV turns it on only with your written OK. The frame runs with `sandbox="allow-scripts allow-same-origin allow-presentation"`. ## Start at the moment AgentTV starts YouTube embeds and `player.vimeo.com` players at the cited second with no setup. Your own player starts at a second through query parameters you name. Send us: * **The time parameter** (required). The query parameter that takes the start time, in whole seconds. * **The id parameter** (optional). The query parameter that takes the media id, taken from the last path segment of the player URL. Name it only if your player needs the id to start at a time. Each name is 1 to 64 letters, digits, `.`, `_` or `-`. There is no console control for this yet. AgentTV sets it for your catalog source. With a time parameter `t` and an id parameter `media`, a moment at 95 seconds on `https://player.example.com/embed/abc123` opens: ```text theme={null} https://player.example.com/embed/abc123?t=95&media=abc123 ``` Without a start rule, your player plays the full video, and the page names the moment's time, for example "Full video. The moment starts at 1:35." A change to the start rule shows on the next page load. Nothing needs to import again. ## Stream from your own HLS hosts When the AgentTV player plays your video, it reads the stream from your own hosts through AgentTV's stream proxy. The proxy reads only from the hosts on your account's allow list. See [Your stream hosts](/guides/import-catalog#4-your-stream-hosts). Nothing in search, discovery or the page block carries a raw stream URL. A stream URL comes only from a playback call that checked your rights first. The URL AgentTV hands out points at its proxy, never at your host, and it is short-lived. Each play reads its segments from your CDN, so your CDN serves the bytes and counts the traffic. AgentTV keeps no playback copy of your video. ## In AI apps * **Claude** plays HLS. Your stream plays inline in Claude, from the cited second. * **ChatGPT** plays MP4 only today. For a video you stream only as HLS, the ChatGPT card cannot play the file. It keeps the link that opens the moment. The player in both apps is the AgentTV player. AI apps do not run outside players in their cards, so your own player, and the analytics tags in it, do not load in the chat. See [Play a moment in an AI app](/guides/play-in-chat). # Track AI visibility Source: https://docs.agentcdn.com/guides/visibility Measure how often AI apps point to your videos for the questions you care about, before and after you publish. AI Video Visibility asks AI apps real questions about your topics and reads every answer. It shows which answers name or link your videos, which link the right moment, and who they cite instead. Run it before you publish your metadata, then compare as the weeks go by. ## What it measures * **Visibility.** How often AI apps point people to your video when they answer questions about your topics. Each number is a count of answers, such as "8 of 35 answers". * **Readiness.** Nine checks on your website, from AI agents opening your pages to playback and usage reporting, rated in levels from Level 0 (Not ready) to Level 5 (Agent-Ready). The two are never combined. Readiness is what you can fix. Visibility is what AI apps do with it. ## The AI apps it asks ChatGPT, Claude, Gemini, Perplexity and Grok, each through its API with web search on, plus Google AI Overviews and Google AI Mode, read from a Google search page. Every answer is a new conversation, asked from the US in US English. The questions never name your brand or your videos. ## Run it **1. Add your brand** In the console, open **Visibility**, or call `POST /v1/visibility/brands` with your website, YouTube channel or name. The first report starts at once and is free. **2. Set the questions you care about** By default, AgentTV picks questions from your own videos. To track your own, call `POST /v1/visibility/brands/{brand_id}/questions` with 1 to 10 questions of 6 to 20 words each. They are asked every week on every AI app. A new list starts a new trend line. **3. Publish, then compare** Your first full report is the baseline. Your tracked questions run every week. AgentTV also runs a full report again at day 30 and day 90, on the same AI apps. Refresh any time, once a day, with `POST /v1/visibility/brands/{brand_id}/refresh`. Read the results with `GET /v1/visibility`. Each brand comes with its readiness, its latest report, its history, and a list of fixes per question: whether AI apps cite you, which of your moments should answer, and what is missing. ## Read the numbers with care * AI answers change from run to run. Read one report as a sample, and compare reports over time. * Crawl-time changes take weeks to show. See [Crawl time and inference time](/concepts/crawl-and-inference). * No one can promise a citation. AI apps decide what they cite. ## See the crawl side To see whether AI crawlers fetch your video pages at all, send your CDN logs. See [Measure usage](/guides/measure#ai-crawler-visits). The report then shows, per crawler operator, the requests to your site and the video pages it fetched. # Webhooks Source: https://docs.agentcdn.com/guides/webhooks Get a signed POST when one of your videos goes live, fails, changes or leaves the public catalog. AgentTV can POST a signed JSON event to your https URL when one of your videos changes. Use it to rebuild a page when its metadata changes, instead of waiting for a cache to expire. ## Events | Event | When | | - | - | | `video.live` | A video finished processing and is ready. | | `video.failed` | Processing failed. | | `video.updated` | A public video's title, description, moments, transcript or rights changed. | | `video.removed` | A video left the public catalog. | ## Create a webhook ```bash theme={null} curl -s -X POST https://api.agentcdn.com/v1/webhooks \ -H "Authorization: Bearer $AGENTCDN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://www.example.com/hooks/agentcdn", "events": ["video.updated", "video.removed"]}' ``` The first call returns a preview and a `confirm_token`. Send it again with `confirm_token` to create the webhook. The confirmed answer holds the signing secret. It shows once: store it in your secret manager. * The URL must be public https. Private, loopback and local addresses are refused. * An account can have up to 10 webhooks. * Leave out `events` to get all four. ## The request ```json theme={null} { "id": "evt_...", "type": "video.live", "created_at": "2026-10-28T12:00:00.000Z", "data": { "video_id": "vid_123", "title": "Building the ramp", "status": "live", "library_id": "lib_..." } } ``` Each request carries an `AgentCDN-Signature` header: ```text theme={null} AgentCDN-Signature: t=1793222400,v1=5f2b... ``` `v1` is the hex HMAC-SHA256 of `.`, keyed with your webhook secret. ```js theme={null} import crypto from "node:crypto"; function verify(rawBody, header, secret, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(",").map((part) => part.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds; return fresh && parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); } ``` Check the signature over the raw body, before you parse it. `data` names the video by its AgentCDN `video_id`. Each page block holds both ids, so save the pair when you render the page. Then fetch the page block again by your own id. ## Delivery * Each event is delivered at least once, within about 30 seconds. * A failed POST is retried with backoff, for up to 12 attempts over about 20 hours. The body and its `id` are the same on every attempt, so drop a repeat by `id`. * Answer with any `2xx` to accept an event. * A webhook that fails 10 delivery rounds in a row is turned off. Create it again once your endpoint is back. ## Manage webhooks `GET /v1/webhooks` lists them, without secrets. `DELETE /v1/webhooks/{webhook_id}` removes one. The account MCP has the same tools. # Publish your own app in ChatGPT Source: https://docs.agentcdn.com/guides/your-ai-app Publish your video library as your own app in the ChatGPT app directory, and connect it to Claude and other AI apps. You can publish your library as your own app in ChatGPT. People find it in the ChatGPT app directory under your name. In a chat, it searches your videos, answers from them, and plays the exact moment. ## Before you start * **AgentTV pages and search** is on in [Discovery](/guides/discovery). The setup does not show while your account is private. * Your account has a profile picture. It becomes the app's logo. AgentTV takes it from your connected YouTube channel. * Your account name is 23 characters or fewer. The app name is your account name plus the word "Videos", and OpenAI allows 30 characters in total. Change the name in **Settings**. * You have access to the OpenAI platform at [platform.openai.com](https://platform.openai.com/) to submit the app. ## Publish the app In the console, open **AI assistants** and choose the **ChatGPT** tab. Scroll to **Publish as a ChatGPT app**. Direct link: `https://app.agentcdn.com/assistants?client=chatgpt#publish`. The section badge shows **Not started** until you claim your domain, then **In progress**. **1. Claim your domain** Your app gets its own address under AgentTV: domain `your-brand.mcp.agentcdn.com`, app address `https://your-brand.mcp.agentcdn.com/mcp`. Choose **Claim domain** to reserve it for your account. You claim it once. OpenAI fixes the domain when you submit. **2. Download the package** Choose **Download package (.zip)**. The package holds the app files and your profile picture as the logo. Upload it in the OpenAI platform. If this step shows **Needs a fix**, the console says why: | Message | Fix | | - | - | | Your app needs a logo | Connect your YouTube channel in **Imports**. AgentTV uses your channel picture. | | AgentTV could not load your profile picture | Check that your channel picture shows on YouTube, sync the channel in **Imports**, then reload. | | Your account name is too long for the app | Shorten your name to 23 characters in **Settings**. | **3. Paste the verification token** After you upload the package, OpenAI shows a domain verification token. Paste it under **Token from OpenAI** and choose **Save token**. Paste a new token only if OpenAI gives you one. **4. Submit for review** After OpenAI verifies your domain, submit the app for review in the OpenAI platform. Choose **Open OpenAI platform**. > [!NOTE] > > Use the app address only for your published ChatGPT app. For every other AI app, use your public video address from the same page (see [below](#connect-claude-and-other-ai-apps)). ## What review means OpenAI reviews every app before it appears in the ChatGPT app directory. The review is OpenAI's, not AgentTV's. OpenAI sets the timing, and AgentTV cannot promise a date. The package already carries what OpenAI asks for: the app name and logo, a short description ("Find and play video moments"), test prompts that show what the app does, and prompts that are out of scope (such as booking or shopping). Support, privacy and terms links point to AgentTV. ## What the app does for people The app connects ChatGPT to your library through one MCP server: your app address. MCP (Model Context Protocol) is the standard way AI apps call outside tools. | People ask | The app | | - | - | | "Find the moment where you explain X" | Searches your videos and plays the best moment from its start, naming the video and the timecode | | "What do your videos say about Y?" | Answers from what you say or show, and cites the moments. If your videos do not cover it, it says so. | | "Show me your videos about getting started" | Lists matching videos and offers to play a moment | | "What topics do you cover?" | Can read your [Library Memory](/concepts/library-memory) pages | The clip plays inside the chat. A person clicks play. What the app does not do: * It reads only your public videos, under the rights you set in **Agent access**. * People need no AgentTV account. * It does not post, buy, upload or change anything. Its tools include `search_videos`, `get_moment_context`, `get_video_stream` and `get_memory_page`. Search and answers never return a media link. Only `get_video_stream` starts playback, after your rights are checked. You see what people ask in **Questions**. ## Connect Claude and other AI apps You do not need a store listing to reach people in other AI apps. The top of the **AI assistants** page, **Connect your public videos**, gives you one address that works in any AI app that supports MCP: `https://mcp.agentcdn.com/c/your-brand/mcp` 1. Copy **Your public video address**. 2. Pick the AI app: **Claude**, **ChatGPT**, **Gemini CLI**, or another one under **More**. The page shows the setup steps or the config to paste for that app. 3. Follow the steps. Name the connector after your library, for example "Your Brand videos". What happens next depends on the AI app: | AI app | What people get | | - | - | | ChatGPT | The clip plays in the chat when an MP4 is available. An HLS-only video shows a link that opens the moment. A custom connector needs developer mode, on a Plus, Pro, Business, Enterprise or Edu plan. | | Claude | Claude finds the moment and plays it inline from the right second, with credit to you. | | Other MCP apps | Search, answers and cited moments. | To share less than your whole account, open **Limit what this connection can see** and choose **Whole account**, **One channel** or **One video**. **One library** shows when you have two or more libraries. The address changes to match. Your rights and privacy settings still apply. Step by step for each app: [Connect an MCP](/guides/connect-mcp). # Overview Source: https://docs.agentcdn.com/index AgentTV is the video layer for AI. It makes your videos findable, playable and credited in ChatGPT, Claude, Gemini, the other agents people use, and on your own site. AgentTV makes your video library usable by AI. A person asks ChatGPT, Claude, Gemini or your own site a question. The answer points to your video at the right second, names you, and links back to your page. AI apps do not watch video. They read the text that comes with it. AgentTV builds that text for every video you connect, and serves it in the forms search engines and agents read. Your videos keep playing from your own player and your own CDN, under your own rules. > [!NOTE] > > **For AI agents.** These docs have an index at [/llms.txt](/llms.txt), the full text at [/llms-full.txt](/llms-full.txt), and a Markdown copy of every page at its path plus `.md`. See [For AI agents](/guides/ai-agents). ## Pick your path > [card] [You run a video platform](/guides/import-catalog) > > Brands and media companies with their own player and CDN. Import your catalog by your own ids, put metadata on your pages, and choose how videos play in AI apps. > [card] [You build agents and apps](/quickstart) > > Builders. Make one request, get moments with exact times, and connect an AI app over MCP. No key for public libraries. > [card] [You are a creator](/guides/connect-sources) > > Connect your YouTube channel or other accounts, choose where your videos appear, and publish your own app in ChatGPT. > [card] [You run a paid membership](/guides/member-libraries) > > Make videos members-only, so only your members can search and ask on your member page, and watch in AI apps. Connect Kajabi. ## How it fits together ```mermaid theme={null} flowchart LR A["Your catalog
sitemap and API"] --> B["Import
private copy for analysis"] B --> C["Index
transcript, chapters,
moments, rights"] C --> D["Page block
JSON-LD on your page"] D --> E["Search engines and
AI crawlers"] C --> F["ARV record and
MCP tools"] E --> G["AI apps"] F --> G G --> H["Playback
your player or your HLS"] ``` 1. **Import.** AgentTV reads your video sitemap and your metadata API, and stores each video under your own id. See [Import a catalog](/guides/import-catalog). 2. **Index.** AgentTV builds the transcript, chapters, moments, people, places and rights for each video. See [The ARV record](/concepts/arv-record). 3. **Publish at crawl time.** Your page template fetches one block per video and renders it in your page. Search engines and AI crawlers read it. See [Put metadata on your pages](/guides/page-metadata). 4. **Answer at inference time.** When an AI app answers a question, it reads the ARV record or calls the MCP tools for the exact moment. See [Crawl time and inference time](/concepts/crawl-and-inference). 5. **Play.** The moment plays in your own player, or in the AgentTV player from your own stream. See [Play in your own player](/guides/playback). ## What AgentTV builds for each video * **Transcript.** Timed sentences of what is said, with speakers. * **Chapters.** Titled sections with a short summary each. * **Moments.** The start and end time that answers a question, with the words and on-screen text behind it. * **People, places, products and topics.** Who and what each video covers, so a question about a person finds the right moment. * **Rights.** Your rules for each video: whether AI apps may search, cite, quote, summarize, play or train on it. Every surface reads the same rules, and every use is logged. ## A typical brand setup **1. Import your catalog** Give us your video sitemap and read access to your metadata API. Pick the videos to import. See [Import a catalog](/guides/import-catalog). **2. Put metadata on your pages** Your page template fetches one block per video page and renders it server side. See [Put metadata on your pages](/guides/page-metadata). **3. Choose how videos play** Play in your own player, or in the AgentTV player from your own HLS stream. See [Play in your own player](/guides/playback). **4. Connect AI apps and your site** Connect the MCP server, add Ask to your pages, and build rails from chapters. See [Play a moment in an AI app](/guides/play-in-chat), [Add Ask to your site](/guides/embed-ask) and [Build a rail from chapters](/guides/moment-rails). **5. Set access and measure** Allow or block AI use, then track AI answers before and after. See [Control AI access](/guides/access-and-rights) and [Track AI visibility](/guides/visibility). No partnership with an AI company is needed. AgentTV works through the open formats search engines and AI apps already read. ## Hosts | Host | What it serves | | - | - | | `https://api.agentcdn.com` | The REST API: public routes and the `/v1` account API. | | `https://mcp.agentcdn.com` | The MCP servers. | | `https://app.agentcdn.com` | The console: your account, keys and settings. | | `https://agenttv.com` | Public pages, watch pages and the Ask script. | ## The open standard The metadata AgentTV publishes follows the Agent-Ready Video (ARV) standard. ARV is one open record that tells AI what is in a video, who owns it and how it may play. The spec lives at [agentreadyvideo.org](https://agentreadyvideo.org). # Quickstart Source: https://docs.agentcdn.com/quickstart Find the exact moment in a video with one request, then fetch the metadata block for one of your own videos. This page makes two requests. The first runs now, with no account and no key. The second runs once your catalog is imported. ## 1. Find a moment Search a public library in plain words. This one is a public sample library, `sandersaar`. ```bash curl theme={null} curl -s "https://api.agentcdn.com/public/creators/sandersaar/ask?q=robot%20pouring%20latte%20art&max_results=1" ``` ```js JavaScript theme={null} const url = new URL("https://api.agentcdn.com/public/creators/sandersaar/ask"); url.searchParams.set("q", "robot pouring latte art"); url.searchParams.set("max_results", "1"); const results = await (await fetch(url)).json(); ``` ```python Python theme={null} import requests results = requests.get( "https://api.agentcdn.com/public/creators/sandersaar/ask", params={"q": "robot pouring latte art", "max_results": 1}, timeout=30, ).json() ``` The response, as returned on 2026-10-28, shortened (each item also carries `description` and `creator`): ```json theme={null} { "@context": "https://schema.org", "@type": "ItemList", "name": "AgentTV results for \"robot pouring latte art\"", "itemListElement": [ { "@type": "ListItem", "position": 1, "relevanceScore": 0.440559440559441, "matchField": "transcript", "item": { "@type": "VideoObject", "@id": "https://agenttv.com/creators/sandersaar#video_t82C_EYja18", "name": "Top 10 Tech Experiences in China - Future of Tech", "url": "https://www.youtube.com/watch?v=t82C_EYja18&t=38s", "thumbnailUrl": "https://i.ytimg.com/vi/t82C_EYja18/hqdefault.jpg", "uploadDate": "2026-04-08T18:20:30.000Z", "duration": "PT2M59S", "potentialAction": { "@type": "WatchAction", "target": "https://api.agentcdn.com/public/creators/sandersaar/videos/video_t82C_EYja18/watch#t=38,90", "startOffset": 38.071, "endOffset": 89.093 } } } ] } ``` The answer is at 38 seconds into a 3 minute video. `startOffset` and `endOffset` are the moment, in seconds. `url` opens the video at that second. The response holds no stream URL: search never returns one. Set a client timeout of at least 30 seconds. When the library has no answer, the list is empty and the response carries `no_answer`. ## 2. Fetch the block for your own video Once [your catalog is imported](/guides/import-catalog), fetch the page block for one video by the id your own platform uses for it. The page block is the metadata search engines and AI apps read for that video. URL-encode the id: colons become `%3A`. ```bash theme={null} curl -s "https://api.agentcdn.com/public/creators/your-brand/page-blocks/catalog%3Avideo%3A1234" ``` A `200` returns one JSON object. This example is shortened: real blocks carry every chapter and every transcript line. ```json theme={null} { "contract_version": "video-page-block/1", "customer_video_id": "catalog:video:1234", "video_id": "vid_123", "json_ld": { "@context": "https://schema.org", "@graph": [ { "@type": "VideoObject", "name": "Building the ramp", "uploadDate": "2026-10-01T09:00:00.000Z", "duration": "PT4M0S", "hasPart": [ { "@type": "Clip", "name": "The landing", "startOffset": 60, "endOffset": 120, "url": "https://agenttv.com/creators/your-brand/videos/vid_123/watch?t=60" } ] }, { "@type": "BreadcrumbList", "itemListElement": [] } ] }, "transcript": { "cues": [{ "start_seconds": 63, "text": "The rider lands the backflip on the second attempt." }] }, "discovery": { "links": [{ "rel": "agent-ready-video", "type": "application/json", "href": "https://api.agentcdn.com/public/creators/your-brand/arv.json" }] }, "head_html": "\n", "body_html": "
...
", "fingerprint": "sha256:...", "cache_policy": { "visibility": "public", "max_age_seconds": 300, "stale_while_revalidate_seconds": 300 } } ``` A `404` with `{"error": "owned_web_artifact_unavailable"}` means the id is not in your library, the video is not public, or its rights block citation. If your account is unlisted, send your API key. See [Listed and unlisted accounts](/concepts/accounts). ## Next > [card] [Put metadata on your pages](/guides/page-metadata) > > Render the block in your page, with caching. > [card] [Play a moment in an AI app](/guides/play-in-chat) > > Connect Claude or ChatGPT and play the cited second. > [card] [Concepts](/concepts/crawl-and-inference) > > Crawl time, inference time, the ARV record and rights. > [card] [API reference](/api-reference/overview) > > Every public and account endpoint. # Account API and MCP tools Source: https://docs.agentcdn.com/reference/account-tools Every operation on your AgentTV account, as a /v1 API route and as an account MCP tool. Each operation on this page works two ways. It is a route on `https://api.agentcdn.com/v1`, and it is a tool on the account MCP at `https://mcp.agentcdn.com/account/mcp`. Same name, same inputs, same result. ## Before you call * **Auth.** Send an API key as `Authorization: Bearer `. Reads need the `account:read` scope. Changes also need `account:write`. See [API keys](/api-keys). On the account MCP you can sign in instead ([connect your account](/guides/connect-mcp#account-mcp)). An app that can only read sees only the read tools. * **Changes run in two calls.** The first call changes nothing. It returns a preview and a `confirm_token`. Send the same request again with `confirm_token` to run it. See [changes run in two calls](/api-keys#changes-run-in-two-calls). A few safe, repeatable changes run in one call with no preview. The tables mark them. * **Rate limits.** The API takes up to 120 calls a minute per account. Changes are capped at 20 a minute per account, across the API and the MCP together. Calls that reach an outside address (a website check, a range link, a Kajabi check) are capped at 10 a minute. Over a limit you get `429` with a `retry-after` header. * **Errors** come back as `{ "error": "", "message": "" }`. * **Full schemas.** The live OpenAPI document is at [`https://api.agentcdn.com/v1/openapi.json`](https://api.agentcdn.com/v1/openapi.json). It lists every input of every route. In paths, `{video_id}` and the like are path parameters. A `GET` takes its other inputs as query parameters. Every other method takes them as a JSON body. **Kind** in the tables: | Kind | Meaning | | - | - | | Read | Changes nothing. Needs `account:read`. | | Change (preview first) | Needs `account:write`. Runs in two calls: preview, then confirm. | | Change (one call) | Needs `account:write`. Runs at once, with no preview. Safe to call again. | > [!NOTE] > > No tool on this page charges you. Some use your monthly allowance: `start_import`, `manage_source` and `prepare_membership_lesson_uploads` use the import allowance, and `ask_library` counts toward the monthly answer allowance. The preview shows what a change would use, and refuses what the allowance cannot take. `get_account` shows what is left this month. ## Account and profile | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_account` | `GET /v1/account` | Read | Your account: name, libraries, how many videos are live or still processing, and this month's allowances. Call it first. | | `get_profile` | `GET /v1/profile` | Read | Your public profile (name, public address, bio, picture) and your website setup, with the DNS record still to add. | | `update_profile` | `PATCH /v1/profile` | Change (preview first) | Changes your public name, address, bio or picture link. The address can change only while nothing of yours is public. | ## Videos | Tool | Method and path | Kind | What it does | | - | - | - | - | | `list_videos` | `GET /v1/videos` | Read | Lists your videos, newest first, with status, visibility and length. Filter by title words, status or visibility. | | `get_video` | `GET /v1/videos/{video_id}` | Read | One video in full: title, description, summary, chapters, parts (each match, talk or segment, as a run of chapters; `[]` when it has none), index status and visibility. | | `get_transcript` | `GET /v1/videos/{video_id}/transcript` | Read | The transcript as timed lines with speakers, in pages. | | `get_video_timeline` | `GET /v1/videos/{video_id}/timeline` | Read | Everything stored about a time range of a video: words, sentences, shots, speakers, chapters and on-screen text. Up to 10 minutes a call. | | `get_video_youtube_analytics` | `GET /v1/videos/{video_id}/youtube-analytics` | Read | Your own YouTube Analytics for one of your videos, as YouTube reports them to you as the channel owner. Empty until you turn on YouTube Analytics in the console. | | `update_video` | `PATCH /v1/videos/{video_id}` | Change (preview first) | Changes a video's title, description or visibility (public, unlisted or private). | | `video_action` | `POST /v1/videos/{video_id}/actions` | Change (preview first) | Runs one action on a video: `retry` processing, `reindex`, `remove` from the library, or `restore` a removed video. | | `get_range_link` | `POST /v1/videos/{video_id}/range-link` | Change (one call) | A short-lived signed MP4 of one range of your video, up to 10 minutes. Anyone with the link can play it until it expires. | > [!WARNING] > > `get_range_link` is the only tool that returns a media link. Search and answers never do. Share a range link only with people who may see that video. ## Answers and memory | Tool | Method and path | Kind | What it does | | - | - | - | - | | `ask_library` | `POST /v1/ask` | Read | Answers a question from your own videos, with the exact moments that back it. Includes unlisted and private videos unless you turn that off. | | `find_moments` | `POST /v1/moments` | Read | Finds the moments a question points to, fast, with no written answer. | | `get_memory_page` | `GET /v1/memory` | Read | A Library Memory page: what your library, one video, one topic or one person or thing is about, with each line cited to a moment. | ## Questions and reports | Tool | Method and path | Kind | What it does | | - | - | - | - | | `list_questions` | `GET /v1/questions` | Read | The questions people asked about your videos, on your Ask page and in AI apps. Latest, most asked, or with no answer. | | `get_ask_report` | `GET /v1/reports/ask` | Read | Totals for a date range: asks, answered, no answer, plays, moments reached, and the top questions, moments, videos and topics. | | `get_agent_usage` | `GET /v1/reports/agent-usage` | Read | Which AI apps cited or played your videos in a date range, and how often. | | `set_featured_questions` | `PUT /v1/featured-questions` | Change (preview first) | Sets the questions your library page, or one video page, offers first. Each is answered ahead of time, so it answers fast. | Date ranges are `YYYY-MM-DD`. The default is the last 30 days. ## AI access and answers | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_agent_access` | `GET /v1/agent-access` | Read | How AI apps may use your videos for citation, playback and training, for the account and per video. | | `set_agent_access` | `PUT /v1/agent-access` | Change (preview first) | Allows or blocks citation, playback and training, as the account default or for chosen videos. Blocked playback can keep a free preview of 30, 60 or 120 seconds. | | `get_answer_settings` | `GET /v1/answer-settings` | Read | How every answer is shown: written or quotes only, the note shown with every answer, and the topics you do not answer. | | `set_answer_settings` | `PATCH /v1/answer-settings` | Change (preview first) | Changes the answer mode, the answer note, the blocked topics or the reply those topics get. Send only what changes. | ## Discovery | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_discovery` | `GET /v1/discovery` | Read | Where your public videos appear: AgentTV pages and search, AgentTV apps in AI assistants, and Google and Bing. Lists each video left out, with the reason and the fix. | | `set_discovery` | `POST /v1/discovery` | Change (preview first) | Turns one Discovery switch on or off. Apps and search engines need pages on. Turning pages off takes your watch pages offline. | ## Imports and sources | Tool | Method and path | Kind | What it does | | - | - | - | - | | `start_import` | `POST /v1/imports` | Change (preview first) | Imports one video from a YouTube, YouTube Shorts, TikTok or Instagram link. The preview shows what it uses of your allowance. | | `get_import` | `GET /v1/imports/{import_id}` | Read | The progress of one import: status, video, and the processing steps done. | | `list_sources` | `GET /v1/sources` | Read | Your connected sources (YouTube channels, TikTok and Instagram accounts), their state and what happens to new uploads. | | `manage_source` | `POST /v1/sources` | Change (preview first) | Connects a TikTok or Instagram account, sets what happens to new uploads, checks a source for new videos now, or removes a source and keeps its videos. | A YouTube channel connects with a Google sign-in in the browser. `list_sources` gives the address to open. ## Brand catalog | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_catalog_source` | `GET /v1/catalog-source` | Read | Your catalog source and one page of its videos, each marked new, queued, imported, changed, unavailable or removed. | | `connect_catalog_source` | `POST /v1/catalog-source` | Change (preview first) | Connects your own verified website as your catalog source. The preview finds its video sitemap and says what it lists. | | `import_catalog_items` | `POST /v1/catalog-source/imports` | Change (preview first) | Imports the videos you picked from your catalog source. Optional `windows` imports one time range of a picked video. See [Import one time range](/guides/import-catalog#import-one-time-range). | ## Website | Tool | Method and path | Kind | What it does | | - | - | - | - | | `manage_website` | `POST /v1/website` | Change (preview first) | Sets up or removes a branded address on your own domain, such as `agents.example.com`. The preview gives the DNS record to add. | | `check_website` | `POST /v1/website/{site_id}/check` | Change (one call) | Checks your branded address for the DNS record and certificate, and turns it on once both are ready. | DNS and certificates can take minutes to hours. Run `check_website` again later if it says not yet. Get `site_id` from `get_profile`. ## Links | Tool | Method and path | Kind | What it does | | - | - | - | - | | `list_links` | `GET /v1/links` | Read | The booking, service and signup links AI answers can offer, for the account or one video. | | `manage_links` | `POST /v1/links` | Change (preview first) | Adds, changes, reorders or deletes those links. | ## Members and member libraries Member libraries need setup on your account. [Ask us to set them up](https://agenttv.com/support). See [Paid and member libraries](/guides/member-libraries). | Tool | Method and path | Kind | What it does | | - | - | - | - | | `list_members` | `GET /v1/members` | Read | Your members and their library access, with expiry and whether access was withdrawn. | | `manage_member` | `POST /v1/members` | Change (preview first) | Adds a member by email, gives a member a library (with an optional end date), withdraws one grant, or disables a member. | | `manage_member_library` | `POST /v1/member-libraries/{library_id}` | Change (preview first) | Changes a members library's settings: let AI apps cite it free, or make a public library members only. | | `manage_member_collection` | `POST /v1/members/collections` | Change (preview first) | Makes, empties or deletes a members-only collection of videos. The preview shows what moves and who loses access. | ## Kajabi membership These tools connect your Kajabi site so the people who buy your offers become members. In every path, `{platform}` is `kajabi`. | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_membership_connection` | `GET /v1/membership/{platform}` | Read | Your Kajabi connection: state, site, last check, last member sync, and how many withdrawals wait for review. Never returns a secret. | | `list_membership_products` | `GET /v1/membership/{platform}/products` | Read | Your Kajabi offers and the members collection each one feeds. | | `list_membership_held` | `GET /v1/membership/{platform}/held` | Read | Members whose access the sync held for review instead of withdrawing. | | `connect_membership_platform` | `POST /v1/membership/{platform}` | Change (preview first) | Connects your Kajabi site with a key you make in Kajabi. The preview checks the key. The secret is stored encrypted and never returned. | | `check_membership_connection` | `POST /v1/membership/{platform}/check` | Change (one call) | Tests the stored key with Kajabi now and records the result. | | `map_membership_product` | `POST /v1/membership/{platform}/products/map` | Change (preview first) | Chooses where the buyers of one offer go: an existing members collection, a new one named after the offer, or nowhere. | | `sync_membership_members` | `POST /v1/membership/{platform}/sync` | Change (preview first) | Syncs your buyers into your members now. Access you gave by hand is never withdrawn. | | `keep_membership_held` | `POST /v1/membership/{platform}/held/keep` | Change (preview first) | Keeps the access the sync held for review. | | `register_membership_webhooks` | `POST /v1/membership/{platform}/webhooks` | Change (one call) | Registers the Kajabi purchase webhook again, so a new buyer becomes a member within seconds. | | `disconnect_membership_platform` | `POST /v1/membership/{platform}/disconnect` | Change (preview first) | Disconnects Kajabi and deletes the stored secret. Delete the key in Kajabi too if it should stop working. | | `list_membership_lessons` | `GET /v1/membership/{platform}/lessons` | Read | The lessons of the courses whose offers feed a members collection, in order, with each lesson's video state and the most files one upload takes. | | `prepare_membership_lesson_uploads` | `POST /v1/membership/{platform}/lessons/uploads` | Change (preview first) | Matches your lesson video files to lessons. The preview shows each match and what the upload uses of your allowance. The confirmed call makes members-only videos and returns an `upload_url` for each file. | | `complete_membership_lesson_uploads` | `POST /v1/membership/{platform}/lessons/uploads/complete` | Change (one call) | After each file's `PUT` to its `upload_url` finishes, send its `upload_session_id`. Starts processing. A file that has not arrived is refused and uses nothing. | Members also sync every hour on their own. Kajabi does not share lesson videos, so you upload each one once: `list_membership_lessons`, then `prepare_membership_lesson_uploads`, then `PUT` each file, then `complete_membership_lesson_uploads`. ## Keys, connected apps and webhooks | Tool | Method and path | Kind | What it does | | - | - | - | - | | `list_ask_keys` | `GET /v1/ask-keys` | Read | Your public Ask keys: name, prefix, scopes, allowed origins, rate limit, state and last use. Never the secret. | | `manage_ask_key` | `POST /v1/ask-keys` | Change (preview first) | Creates, rotates or revokes a public Ask key. A new key's secret is shown once, in the confirmed result. | | `list_connected_apps` | `GET /v1/connected-apps` | Read | The AI apps signed in to your account through the account MCP, what each may do, and when it was last seen. | | `revoke_connected_app` | `POST /v1/connected-apps/revoke` | Change (preview first) | Disconnects one AI app at once. It must sign in again to come back. | | `list_webhooks` | `GET /v1/webhooks` | Read | Your webhooks: URL, events, and whether failed deliveries turned one off. | | `create_webhook` | `POST /v1/webhooks` | Change (preview first) | Sends your video events (`video.live`, `video.failed`, `video.updated`, `video.removed`) to an https URL. The signing secret is shown once. | | `delete_webhook` | `DELETE /v1/webhooks/{webhook_id}` | Change (preview first) | Deletes a webhook. Events stop going to its URL. | ## AI Video Visibility AI Video Visibility shows how often AI apps such as ChatGPT and Perplexity name or cite your brand and videos. These tools are free. | Tool | Method and path | Kind | What it does | | - | - | - | - | | `get_visibility` | `GET /v1/visibility` | Read | Each of your brands with its website readiness, latest report, history, tracked questions and the fixes per question. | | `get_visibility_snapshot` | `GET /v1/visibility/brands/{brand_id}/snapshots/{snapshot_id}` | Read | One report in full, per question and per AI app. | | `get_visibility_answers` | `GET /v1/visibility/brands/{brand_id}/snapshots/{snapshot_id}/questions/{question_id}/answers` | Read | What each AI app answered to one question, with the links it cited. | | `add_visibility_brand` | `POST /v1/visibility/brands` | Change (preview first) | Adds a website or YouTube channel as a brand and starts its first report. | | `refresh_visibility` | `POST /v1/visibility/brands/{brand_id}/refresh` | Change (preview first) | Starts a new report for a brand. Once a day per brand. | | `set_tracked_questions` | `POST /v1/visibility/brands/{brand_id}/questions` | Change (preview first) | Sets the 1 to 10 questions tracked for a brand. An empty list goes back to questions picked for you. | | `check_visibility_website` | `POST /v1/visibility/brands/{brand_id}/website-check` | Change (preview first) | Checks a brand's website readiness again, without asking any AI app. Use it after a website fix. |