Skip to content
Search Documentation
Search across all documentation pages
Video SEO

Video SEO

Overview

Search engines cannot watch your video. They read the page around it. A video page cannot rank or be cited for what the video says unless the page carries that content in HTML that crawlers can read on the first request. That is necessary, not sufficient: the rest is the usual work of relevance and links.

Transcodely already has what that page needs: the transcript, and with insights enabled, a summary, key points and chapters. GetSeoMarkup packages them for your own watch page in one call:

  • json_ld: a schema.org VideoObject, with chapter key moments when the video has insights.
  • sitemap_entry: a <url> entry for your video sitemap.
  • html_fragment: server-renderable HTML with the summary, key points, chapter links and the full transcript.

The rest of this guide is the checklist for the page you paste them into.


Get the SEO kit

POST /transcodely.v1.VideoService/GetSeoMarkup
ParameterTypeRequiredDescription
video_idstringYesVideo ID (vid_ prefix).
watch_urlstringYesAbsolute https:// URL of your page that shows the video. Used as the sitemap location and as the base of every chapter link.
deep_link_paramstringNoQuery parameter your page reads to start playback at a time, in whole seconds. Default t, which gives chapter links like https://example.com/watch/42?t=95.
languagestringNoISO 639-2 language of the transcript and insights to use, when the video has more than one. Default: the video’s first generated transcript.

It works with an API key or from the dashboard: the Copy SEO kit button on a video page calls it for you. The video must be ready and public or unlisted. A private or unfinished video is refused with failed_precondition, because the markup points search engines at the hosted player. watch_url is only written into the markup, never fetched.

curl -X POST https://api.transcodely.com/transcodely.v1.VideoService/GetSeoMarkup 
  -H "Authorization: Bearer ak_abc123" 
  -H "Content-Type: application/json" 
  -d '{
    "video_id": "vid_a1b2c3d4e5f6g7",
    "watch_url": "https://example.com/watch/searing-steak"
  }'

The response holds three strings. Each one is complete and escaped for where it goes, so paste it as-is:

{
  "json_ld": "{"@context":"https://schema.org","@type":"VideoObject",…}",
  "sitemap_entry": "<url><loc>https://example.com/watch/searing-steak</loc><video:video>…</video:video></url>",
  "html_fragment": "<section class="tx-insights">…</section>"
}

What each piece contains

json_ld goes inside a <script type="application/ld+json"> tag in your page’s <head> or body. It carries name, description, thumbnailUrl, uploadDate, duration (ISO 8601), embedUrl, inLanguage (when the transcript language is known) and, when the video has ready insights, hasPart with one Clip per chapter. Clip offsets are whole seconds:

{
  "@context": "https://schema.org",
  "@type": "VideoObject",
  "name": "How to Sear a Steak",
  "description": "A beginner walkthrough of searing steak in a home kitchen.",
  "thumbnailUrl": ["https://play.transcodely.com/v/vid_a1b2c3d4e5f6g7/poster"],
  "uploadDate": "2026-10-24T10:00:00Z",
  "duration": "PT8M32S",
  "embedUrl": "https://play.transcodely.com/v/vid_a1b2c3d4e5f6g7",
  "inLanguage": "en",
  "hasPart": [
    {
      "@type": "Clip",
      "name": "Heating the pan",
      "startOffset": 0,
      "endOffset": 95,
      "url": "https://example.com/watch/searing-steak?t=0"
    },
    {
      "@type": "Clip",
      "name": "Searing",
      "startOffset": 95,
      "endOffset": 410,
      "url": "https://example.com/watch/searing-steak?t=95"
    }
  ]
}

embedUrl and thumbnailUrl are stable URLs on the hosted player that never expire: the thumbnail redirects to the video’s current poster. Unlike the signed CDN URLs elsewhere in the API, the markup can be stored and served as-is.

There is deliberately no transcript property. Google ignores it. The transcript belongs in the page HTML, which is what html_fragment is for.

sitemap_entry is one <url> element to add to your sitemap’s <urlset>. Declare the video namespace on the <urlset> element: xmlns:video="http://www.google.com/schemas/sitemap-video/1.1".

html_fragment is plain semantic HTML with no scripts and no styles, ready to render on the server. It holds a <section class="tx-insights"> with the summary, key points and chapter links when insights are ready, and the full transcript inside <details><summary>Transcript</summary>…</details>. Empty sections are left out. Style it with your own CSS through the tx-insights class.

Without insights, the kit still works: you get the VideoObject without key moments, and the fragment holds the transcript only.


Page anatomy checklist

These are the parts a video page needs before search engines and AI assistants can use it, all in the HTML of the first response. They do not guarantee a ranking; without them, the video’s content is invisible.

  1. One video, one URL. Give every video its own watch page. Do not put several videos on one page and expect each to rank.
  2. Server-rendered content. Render the title, description, JSON-LD and transcript on the server. No AI crawler (GPTBot, ClaudeBot, PerplexityBot) executes JavaScript, so text injected by client-side code is invisible to them.
  3. The VideoObject JSON-LD from json_ld, in the initial HTML.
  4. The transcript in the HTML. A transcript collapsed in <details> is indexed, as long as it is in the initial HTML, and it is not a spam problem. A transcript that only loads after a click or a scroll is missed. One fetched by client-side code is never seen by AI crawlers, and Google, which does render JavaScript, indexes it late if at all.
  5. A summary and key points near the player, so a reader and a crawler both get the point without the video. html_fragment has both.
  6. Quotes and numbers in the text. Pages with direct quotes and statistics tend to be cited by AI assistants more often. The insights highlights are verbatim quotes with timestamps, ready to use.
  7. Key moments that link to a time. See the next section.
  8. A video sitemap entry from sitemap_entry.
  9. One language per URL. A translated transcript gets its own URL, linked to the others with hreflang.

Google shows key moments for a video when the page marks them up with Clip (or SeekToAction) and the watch page actually starts playback at the linked time. The Clip URLs in json_ld look like https://example.com/watch/searing-steak?t=95, so your page must read t and seek:

// On your watch page, after the player is created.
const t = Number(new URLSearchParams(location.search).get('t'));
if (Number.isFinite(t) && t > 0) {
	player.currentTime = t;
}

If your page already uses another parameter name, pass it as deep_link_param and the links follow it. The Markdown transcript uses #t=95 fragments for the same purpose: handle both if you publish the Markdown as-is.

Let the search bots in

Check your robots.txt. These are the crawlers that put pages into search results and AI answers:

User-agent: Googlebot
Allow: /

User-agent: OAI-SearchBot
Allow: /

User-agent: Claude-SearchBot
Allow: /

User-agent: PerplexityBot
Allow: /

They are separate from the crawlers that collect training data, such as GPTBot and ClaudeBot. You can block the training crawlers and still allow the search ones.


What not to bother with

  • A transcript property in JSON-LD. Google ignores it. Put the transcript in the HTML.
  • FAQ schema. FAQ rich results no longer appear in Google Search. A questions-and-answers block built from the insights questions is still good content for readers and AI assistants. Mark it up as plain HTML.
  • llms.txt. No search engine or AI assistant honors it.
  • Lazy-loaded transcripts. Text that only appears after a click or a scroll is missed. Text from a client-side fetch is invisible to AI crawlers, and Google indexes it late if at all. Render it on the server and collapse it with <details> if it is long.

The hosted player page

The Transcodely player page (embed_url) is a page too. For public videos it embeds VideoObject JSON-LD, with Clip entries from the chapters when the video has ready insights. The player page honors ?t=SECONDS (and #t=SECONDS), so each key moment starts playback at its chapter. Unlisted videos get none and carry noindex; private videos have no player page at all. The player page is for embedding. For search traffic, the page that should rank is your own watch page, with the kit above.


Run this against your own file

A free account includes 30-second test encodes, so the request on this page can be tried before anything is configured.