> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentcdn.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Put metadata on your pages

> 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](https://docs.agentcdn.com/concepts/arv-record.md). 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](https://docs.agentcdn.com/guides/import-catalog.md) stores this id.

**format** `string` (default: json)

* `json`: the whole block as JSON. See the [Quickstart](https://docs.agentcdn.com/quickstart.md#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 <account API key>`. See [Listed and unlisted accounts](https://docs.agentcdn.com/concepts/accounts.md).

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 `<link>` tag in your head.

```html theme={null}
<!-- Discovery links, from discovery.links -->
<link rel="agent-ready-video" type="application/json" href="https://api.agentcdn.com/public/creators/your-brand/arv.json">
<link rel="mcp-server" href="https://mcp.agentcdn.com/c/your-brand/mcp">
```

### 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](https://docs.agentcdn.com/guides/playback.md#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}
<script id="agentcdn-webmcp-config" type="application/json">{"creator_slug":"your-brand","video_id":"vid_123","endpoint_base":"https://api.agentcdn.com/api/webmcp/v1/sites/your-brand"}</script>
<script type="module" src="https://agenttv.com/webmcp/v1/adapter.js?v=1.2.0"></script>
```

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](https://docs.agentcdn.com/concepts/crawl-and-inference.md#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](https://docs.agentcdn.com/concepts/languages.md).

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](https://docs.agentcdn.com/concepts/rights.md).

## 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](https://docs.agentcdn.com/guides/webhooks.md) 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.