Think10x.ai Video Generation API Documentation

This document describes the Think10x.ai video generation APIs for creating AI-generated videos, checking generation status, retrieving generated media, and delivering videos to end users through an embedded iframe player, backend media download, or playback in your own player streaming directly from our CDN.

The most common integration is the embedded iframe player. In that model, Think10x.ai hosts the player and streams the generated video, while the customer application embeds the player inside its own product.


Quick Start

Most customers integrate Think10x.ai using the embedded iframe player.

In this model, your frontend never calls the Think10x.ai API directly. Your backend stores the Think10x.ai CustomerId and API key, creates or reuses a video, mints a fresh iframe ticket, and returns only the think10xvideoId and single-use ticket to your frontend.

  1. The user clicks Watch Video Solution in your application.
  2. Your frontend calls your backend, for example POST /api/video/resolve.
  3. Your backend checks whether this question already has a stored think10xvideoId.
  4. If no video exists yet, your backend calls POST /external/v1/createVideo.
  5. Your backend calls POST /external/v1/createIframeTicket to mint a fresh single-use ticket.
  6. Your frontend opens the iframe:
https://<think10x-app-host>/video-ai-integration/<videoId>?ticket=<ticket>

The API key must remain on your backend. Never expose the Authorization header, API key, or raw Think10x.ai API endpoints to a browser, mobile app, or end-user client.

Minimal backend sequence

sequenceDiagram
    autonumber
    participant FE as Customer frontend
    participant BE as Customer backend
    participant T as Think10x.ai API

    FE->>BE: POST /api/video/resolve
    BE->>BE: Look up questionId → think10xvideoId

    alt No existing video
        BE->>T: POST /external/v1/createVideo
        T-->>BE: think10xvideoId
        BE->>BE: Save questionId → think10xvideoId
    end

    BE->>T: POST /external/v1/createIframeTicket
    T-->>BE: ticket, expiresAt
    BE-->>FE: think10xvideoId, ticket, expiresAt
    FE->>FE: Open iframe with ticket

Minimal response from your backend to your frontend

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "ticket": "tk_8f3c19e2b7a04d56...",
  "expiresAt": 1748312560
}

Choose Your Integration Flow

Think10x.ai supports three ways to deliver a generated video.

Flow A (Embedded iframe player) is the fastest path and the best fit for most integrations. Think10x.ai hosts the player and handles progress and playback, so you can ship quickly and let us manage the viewing experience. The hosted player still supports custom branding and theming, so it can be styled to match your product's look and feel, including your brand colors, logo, and overall theme.

Flow B (Backend media download) is best when you need to own the media files outright. Your backend downloads the HLS/MP4 files so you can store, archive, re-host, or process them on your own infrastructure, including offline use.

Flow C (Use your own player) is best when you want to build your own video player and media frontend. You develop the playback UI yourself, so you can fully customize it for your own branding, colors, and theme, and stream the media directly from our CDN with no iframe and no re-hosting. The trade-off is that it only works in browsers that support partitioned cookies.

If you are unsure, start with Flow A. You can adopt Flow B or Flow C later without changing how videos are created.

If you want to…Recommended flowWhy
Ship the fastest, with the least frontend codeFlow A — Embedded iframe playerThink10x.ai hosts and plays the video; you embed one ticketed iframe URL.
Let Think10x.ai host the player, progress, and playback inside your appFlow A — Embedded iframe playerBuilt-in player UI; the browser only ever loads the ticketed iframe.
Own the media files yourself to store, archive, or re-host themFlow B — Backend media downloadYou download the HLS/MP4 and run them on your own infrastructure.
Process or replay videos offline or on your own CDNFlow B — Backend media downloadPure backend-to-backend; the browser never calls Think10x.ai.
Build your own player and playback UI with your own branding and themeFlow C — Use your own playerYou develop the player and stream from our CDN; requires a partitioned-cookie browser.

Flow comparison

ConcernFlow A — Embedded iframe playerFlow B — Backend media downloadFlow C — Use your own player
What you getThink10x.ai-hosted interactive playerRaw HLS and MP4 media filesMedia streamed into your own player from our CDN
Who plays the videoThink10x.ai iframe inside your pageYour own player or storage systemYour own player on your own page
Recommended forMost customer integrationsAdvanced media ownership use casesYour own player UI without re-hosting media
Browser talks to Think10x.ai?Yes, only through the iframe URL with a ticketNoYes, to fetch a cookie and stream from the CDN
API key exposed to browser?NoNoNo
APIs usedgetLangOptions optional, createVideo, createIframeTicketcreateVideo, getVideo optional, getVideoFilescreateVideo, createPlaybackTicket, getVideoPlayback, getCookie
Customer backend required?YesYesYes
Customer media storage required?NoYesNo
Browser supportAll browsersNot applicablePartitioned-cookie browsers only (see Flow C)

Endpoint Summary

All API endpoints below are called from the customer backend, except the iframe integration URL, which is loaded by the browser after a backend-minted ticket is returned.

EndpointMethodPurposeCalled byBrowser-safe?
/external/v1/getLangOptionsGETGet enabled language / speech variantsCustomer backendNo
/external/v1/createVideoPOSTStart video generationCustomer backendNo
/external/v1/getVideo/{videoId}GETCheck video generation statusCustomer backendNo
/external/v1/getVideoFiles/{videoId}GETGet signed HLS / MP4 download linksCustomer backendNo
/external/v1/createIframeTicketPOSTMint a single-use iframe ticketCustomer backendNo
/external/v1/createPlaybackTicketPOSTMint a single-use CDN playback ticketCustomer backendNo
/external/v1/getVideoPlayback/{videoId}GETGet CDN base URL and relative media pathsCustomer backendNo
/external/converthtmltoimagePOSTRender HTML into an imageCustomer backendNo
/integration/getcookie/{videoId}GETExchange a playback ticket for the CDN cookieBrowserYes with ticket

Note: Most API endpoints are versioned under /external/v1. The HTML-to-image utility currently uses /external/converthtmltoimage. If a versioned utility endpoint is later introduced, Think10x.ai will provide migration guidance.


Provisioning and Credentials

The following values are provisioned and supplied by Think10x.ai during onboarding. They are not self-service and cannot be generated from these APIs.

ValueHeader / UsageDescription
API Base URLRequest hostThe fully qualified base URL for your assigned environment, such as staging or production. Prepend it to every /external/... or /integration/... endpoint path.
App HostIframe hostThe frontend host used for iframe playback, for example https://<think10x-app-host>.
CDN HostMedia hostThe CDN host that serves the protected media for Flow C (use your own player). Your frontend receives it as cdnBaseUrl from getVideoPlayback.
CustomerIdCustomerId headerYour unique customer identifier. Sent on every backend API request to scope and authorize access to your data.
API keyAuthorization headerYour secret API key. Sent as Bearer <api-key>. Treat this as a server-side secret.

Example placeholders used in this document:

API Base URL: https://<think10x-api-base-url>
App Host:     https://<think10x-app-host>

If you have not received these values, or need credentials rotated, contact your Think10x.ai representative.


Authentication

Required headers for every backend API request:

CustomerId: <customer-id>
Authorization: Bearer <api-key>

For JSON requests, also send:

Content-Type: application/json

Header matching is case-insensitive.

The API validates the supplied API key against the configured key for the supplied CustomerId.

Important security rule

The API key grants video-generation privileges for your customer account. It must never be sent to:

  • Browser JavaScript
  • Mobile apps
  • End-user clients
  • Public frontend configuration
  • Unauthenticated proxy endpoints

Your own backend should authenticate the end user, authorize the action, apply rate limits if needed, and then call Think10x.ai on the user's behalf.


Core Concepts

think10xvideoId

The unique Think10x.ai video identifier returned by createVideo.

Use this value to:

  • Check generation status with getVideo
  • Retrieve media links with getVideoFiles
  • Create iframe tickets with createIframeTicket
  • Open the iframe player URL

externalQuestionId

An optional customer-owned question identifier stored on the video record.

Recommended usage:

  • Send your internal question ID when calling createVideo.
  • Use it for traceability and support.

externalUserId

An optional customer-owned user identifier.

Recommended usage:

  • Send this field on every createVideo request when you know who initiated generation.
  • Send this field on every createIframeTicket request when you know who is viewing the video.
  • Use a stable, non-PII identifier from your own system, such as student-7842 or an internal UUID.
  • Avoid sending names, email addresses, phone numbers, or other directly identifying values unless required by your own compliance process.

Maximum length: 256 characters.

Iframe ticket

A short-lived, single-use credential created by createIframeTicket.

A ticket:

  • Authorizes exactly one iframe view of one video
  • Is valid for 5 minutes from creation
  • Is consumed atomically when the iframe loads
  • Cannot be reused after refresh, replay, or opening in a second tab
  • Is bound to one or more allowedParentOrigins

Playback ticket

A short-lived, single-use credential created by createPlaybackTicket for Flow C (use your own player).

A playback ticket:

  • Authorizes the browser to obtain the CDN playback cookie for exactly one video
  • Is valid for 5 minutes from creation and is consumed the first time it is used
  • Is bound to one or more allowedOrigins (the page origins allowed to use it)
  • Is always prefixed pt_, to distinguish it from an iframe ticket (tk_)

Playback tickets and iframe tickets are not interchangeable. A pt_ ticket cannot open the iframe player, and a tk_ ticket cannot obtain a CDN cookie.

The credential that authorizes Flow C media requests. It is set on the browser by getCookie and is then sent automatically to the Think10x.ai CDN with every media request.

  • It is a partitioned cookie (CHIPS), which is what lets it work as a cross-site cookie on your own domain. Flow C therefore requires a browser that supports partitioned cookies.
  • It is HttpOnly: page JavaScript cannot read it. The browser attaches it to CDN requests for you.
  • It is longer-lived than the playback ticket. The ticket guards obtaining the cookie; the cookie guards playback.

CDN base URL and relative media paths

getVideoPlayback returns a cdnBaseUrl plus relative media paths (hls.playlistPath, mp4.path, probePath). Build a full URL by joining them: cdnBaseUrl + / + path. The relative paths carry no signature, the signed cookie is what authorizes the request.

Language options

Some customers have language / speech variant selection enabled.

  • If getLangOptions returns a non-empty languages array, render a selector and send the selected languageKey to createVideo.
  • If getLangOptions returns an empty array, do not render a selector and do not send languageKey.
  • If languageKey is omitted, the customer's configured default language is used.

Idempotency and duplicate video prevention

Unless Think10x.ai has explicitly enabled idempotency for your account, treat createVideo as not idempotent.

That means calling createVideo multiple times for the same question will create multiple video jobs.

Recommended pattern:

  1. Maintain your own persistent mapping from your question ID to think10xvideoId.
  2. For language-enabled integrations, key the mapping by (questionId, languageKey).
  3. Before calling createVideo, check whether a video already exists in your mapping.
  4. Reuse the existing think10xvideoId when available.
  5. Mint a fresh iframe ticket for every playback, even when reusing an existing video.

Status Lifecycle

Video generation is asynchronous. createVideo accepts the request and immediately returns a think10xvideoId with an initial status.

Published status values

StatusMeaningRecommended action
processingVideo generation is in progressContinue polling or open the iframe and let Think10x.ai show progress
completedVideo generation completed successfullyPlay the video or download final media
failedVideo generation failedShow an error to the user or retry by creating a new video

progressPercent is a number from 0 to 100.

Polling guidance

Use getVideo when you only need status or progress. Use getVideoFiles only when you need signed HLS / MP4 media links.

Recommended backend polling behavior:

  • Poll every 3–5 seconds while status is processing.
  • Stop polling when status is terminal: completed or failed.
  • Do not poll Think10x.ai directly from the browser because that would require exposing API credentials.
  • If you are using the iframe player, the iframe can handle progress rendering internally.
  • For Flow C (use your own player), poll getVideoPlayback through your backend and start playing as soon as hls.ready is true.

This is the recommended integration pattern for most customers.

Your backend creates or reuses a video, mints a single-use iframe ticket, and returns the video ID and ticket to your frontend. Your frontend opens the Think10x.ai-hosted player inside an iframe.

Custom branding and theming

Even though Think10x.ai hosts the player, the iframe experience is not fixed to a default look. It supports custom branding and theming so the player can be aligned with your product's visual identity, including:

  • Your brand colors and accent theme
  • Your logo and product name
  • Light or dark presentation to match the surrounding page

Branding and theme settings are configured for your account during onboarding and applied automatically whenever the player loads, so you get a player that looks like part of your own product without building or maintaining one yourself. To set up or update your branding, contact your Think10x.ai representative.

Security model

The API key never leaves the customer backend. The frontend only receives:

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "ticket": "tk_8f3c19e2b7a04d56...",
  "expiresAt": 1748312560
}

Recommended topology:

End user → Customer frontend → Customer backend → Think10x.ai API
                                 holds API key
                                 owns question → video mapping
                                 authorizes end-user access

Backend responsibilities

Your backend should:

  1. Authenticate and authorize the end user using your own identity system.
  2. Store the Think10x.ai CustomerId and API key as server-side secrets.
  3. Maintain a persistent mapping from your question ID to Think10x.ai think10xvideoId.
  4. Build the createVideo payload using your question content or rendered image.
  5. Send externalUserId when available.
  6. Mint a fresh iframe ticket immediately before playback.
  7. Return only think10xvideoId, ticket, and expiresAt to the frontend.

Backend orchestration endpoint

Most customer frontends should call one customer-owned endpoint, for example:

POST /api/video/resolve

The endpoint should return the same response shape whether the video already exists or needs to be created.

Example request from customer frontend to customer backend

{
  "questionId": "Q-1023",
  "externalUserId": "student-7842",
  "languageKey": "hinglish",
  "parentOrigin": "https://customer-lms.com"
}

Example response from customer backend to customer frontend

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "ticket": "tk_8f3c19e2b7a04d56...",
  "expiresAt": 1748312560
}

Flow A1: Customer has language selection enabled

Use this flow when GET /external/v1/getLangOptions returns a non-empty languages array.

Frontend behavior

  1. Call your backend endpoint that proxies getLangOptions, for example GET /api/video/lang-options.
  2. Render a language selector.
  3. Pre-select the entry where isDefault is true.
  4. Send the selected languageKey to your backend when resolving the video.

Backend mapping

Use a mapping key of (questionId, languageKey).

questionIdlanguageKeythink10xvideoIdcreatedAt
Q-1023hinglish00cb7a5f-......
Q-1023english7d2f3c14-......
Q-1040hinglish9aa8b210-......

Backend pseudo-code

async function resolveVideo({ questionId, languageKey, externalUserId, parentOrigin, extraMetadata }) {
  let mapping = await db.videoMapping.find({ questionId, languageKey });

  if (!mapping) {
    const question = await db.questions.findById(questionId);
    const createBody = buildCreateVideoPayload(question, extraMetadata);

    createBody.languageKey = languageKey;
    createBody.externalUserId = externalUserId;

    const { think10xvideoId } = await think10x.createVideo(createBody);

    mapping = await db.videoMapping.insert({
      questionId,
      languageKey,
      think10xvideoId,
      createdAt: Date.now()
    });
  }

  const { ticket, expiresAt } = await think10x.createIframeTicket({
    videoId: mapping.think10xvideoId,
    externalUserId,
    allowedParentOrigins: [parentOrigin]
  });

  return {
    think10xvideoId: mapping.think10xvideoId,
    ticket,
    expiresAt
  };
}

Flow A2: Customer does not have language selection enabled

Use this flow when GET /external/v1/getLangOptions returns an empty languages array, or when your product does not expose language selection.

Frontend behavior

  1. Do not render a language selector.
  2. Do not send languageKey to your backend.
  3. The video is generated in the customer's configured default language.

Backend mapping

Use a mapping key of questionId.

questionIdthink10xvideoIdcreatedAt
Q-102300cb7a5f-......
Q-10409aa8b210-......

Backend pseudo-code

async function resolveVideo({ questionId, externalUserId, parentOrigin, extraMetadata }) {
  let mapping = await db.videoMapping.find({ questionId });

  if (!mapping) {
    const question = await db.questions.findById(questionId);
    const createBody = buildCreateVideoPayload(question, extraMetadata);

    createBody.externalUserId = externalUserId;

    const { think10xvideoId } = await think10x.createVideo(createBody);

    mapping = await db.videoMapping.insert({
      questionId,
      think10xvideoId,
      createdAt: Date.now()
    });
  }

  const { ticket, expiresAt } = await think10x.createIframeTicket({
    videoId: mapping.think10xvideoId,
    externalUserId,
    allowedParentOrigins: [parentOrigin]
  });

  return {
    think10xvideoId: mapping.think10xvideoId,
    ticket,
    expiresAt
  };
}

Frontend iframe embedding guide

The frontend opens the Think10x.ai player only after your backend returns a fresh ticket.

Integration URL

https://<think10x-app-host>/video-ai-integration/<videoId>?ticket=<ticket>

Required iframe attributes

<iframe
  src="https://<think10x-app-host>/video-ai-integration/<videoId>?ticket=<ticket>"
  allow="autoplay; fullscreen; microphone"
  allowfullscreen
  title="Think10x.ai Tutor"
></iframe>

microphone is required if voice features are enabled for the player.

Iframe close event contract

When the user closes the Think10x.ai session inside the iframe, the iframe sends this message to the parent window:

{
  "type": "THINK10X_CLOSE",
  "source": "think10x-integration",
  "videoId": "7d2f3c14-8a6e-4dcb-9eb7-2c3f52a1b4f0"
}

The parent application should validate:

  • event.origin
  • event.data.type
  • event.data.source

Only after validation should the parent app close the dialog or overlay.

Vue example

<template>
  <div v-if="open" class="think10x-dialog">
    <iframe
      :src="think10xIframeSrc"
      class="think10x-iframe"
      allow="autoplay; fullscreen; microphone"
      allowfullscreen
      title="Think10x.ai Tutor"
    ></iframe>
  </div>
</template>

<script>
const THINK10X_APP_HOST = "https://<think10x-app-host>";
const THINK10X_CLOSE_EVENT = "THINK10X_CLOSE";
const THINK10X_SOURCE = "think10x-integration";

export default {
  name: "Think10xTutorDialog",
  props: {
    open: { type: Boolean, required: true },
    videoId: { type: String, required: true },
    ticket: { type: String, required: true },
  },
  emits: ["close"],

  computed: {
    think10xIframeSrc() {
      const encodedVideoId = encodeURIComponent(this.videoId);
      const encodedTicket = encodeURIComponent(this.ticket);
      return `${THINK10X_APP_HOST}/video-ai-integration/${encodedVideoId}?ticket=${encodedTicket}`;
    },
  },

  mounted() {
    window.addEventListener("message", this.handleThink10xMessage);
  },

  beforeUnmount() {
    window.removeEventListener("message", this.handleThink10xMessage);
  },

  methods: {
    handleThink10xMessage(event) {
      if (event.origin !== THINK10X_APP_HOST) {
        return;
      }

      const payload = event.data ?? {};

      if (
        payload.type !== THINK10X_CLOSE_EVENT ||
        payload.source !== THINK10X_SOURCE
      ) {
        return;
      }

      this.$emit("close");
    },
  },
};
</script>

<style scoped>
.think10x-dialog {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(15, 17, 23, 0.92);
}

.think10x-iframe {
  width: 100%;
  height: 100%;
  border: none;
}
</style>

Plain JavaScript example

<button id="open-think10x-tutor" type="button">Watch Video Solution</button>
const THINK10X_APP_HOST = "https://<think10x-app-host>";
const THINK10X_CLOSE_EVENT = "THINK10X_CLOSE";
const THINK10X_SOURCE = "think10x-integration";

let activeThink10xOverlay = null;

function buildThink10xIframeUrl(videoId, ticket) {
  const encodedVideoId = encodeURIComponent(videoId);
  const encodedTicket = encodeURIComponent(ticket);
  return `${THINK10X_APP_HOST}/video-ai-integration/${encodedVideoId}?ticket=${encodedTicket}`;
}

function openThink10xTutor(videoId, ticket) {
  if (activeThink10xOverlay) return;

  const overlay = document.createElement("div");
  overlay.style.position = "fixed";
  overlay.style.inset = "0";
  overlay.style.zIndex = "9999";
  overlay.style.background = "rgba(15, 17, 23, 0.92)";

  const iframe = document.createElement("iframe");
  iframe.src = buildThink10xIframeUrl(videoId, ticket);
  iframe.title = "Think10x.ai Tutor";
  iframe.allow = "autoplay; fullscreen; microphone";
  iframe.setAttribute("allowfullscreen", "");
  iframe.style.width = "100%";
  iframe.style.height = "100%";
  iframe.style.border = "none";

  overlay.appendChild(iframe);
  document.body.appendChild(overlay);
  activeThink10xOverlay = overlay;
}

function closeThink10xTutor() {
  if (!activeThink10xOverlay) return;
  activeThink10xOverlay.remove();
  activeThink10xOverlay = null;
}

function handleThink10xMessage(event) {
  if (event.origin !== THINK10X_APP_HOST) {
    return;
  }

  const payload = event.data ?? {};

  if (
    payload.type !== THINK10X_CLOSE_EVENT ||
    payload.source !== THINK10X_SOURCE
  ) {
    return;
  }

  closeThink10xTutor();
}

window.addEventListener("message", handleThink10xMessage);

document
  .getElementById("open-think10x-tutor")
  .addEventListener("click", async () => {
    const response = await fetch("/api/video/resolve", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        questionId: "Q-1023",
        externalUserId: "student-7842",
        parentOrigin: window.location.origin,
      }),
    });

    if (!response.ok) {
      // Show a customer-owned error message.
      return;
    }

    const { think10xvideoId, ticket } = await response.json();
    openThink10xTutor(think10xvideoId, ticket);
  });
  1. Keep the iframe mounted only while the dialog or overlay is open.
  2. Let the iframe's built-in close button drive the normal close flow.
  3. Listen for THINK10X_CLOSE in the parent app.
  4. Validate the Think10x.ai origin before reacting to iframe events.
  5. Remove the message event listener when your component or page is destroyed.
  6. Mint a new ticket for every user-initiated playback.

Ticket and iframe failure behavior

ScenarioExpected behaviorRecommended customer action
Ticket expiredIframe refuses playbackRequest a new ticket from your backend
Ticket already usedIframe refuses playbackRequest a new ticket from your backend
Parent origin mismatchIframe refuses playbackVerify allowedParentOrigins uses exact origins, no paths or trailing slashes
Video still processingIframe may show processing/progress stateKeep iframe open or show customer-owned progress UI
Video generation failedIframe may show failure stateOffer retry or contact support with think10xvideoId

Flow B: Backend Media Download

Use this flow when you want to own the media files. Your backend downloads generated HLS and/or MP4 files and stores, re-hosts, or replays them using your own infrastructure.

This is a pure backend-to-backend flow. The browser never calls Think10x.ai.

Sequence

  1. Call createVideo to start the job and get a think10xvideoId.
  2. Poll getVideoFiles to retrieve available HLS files and final MP4 when ready.
  3. Download the playlist and all listed segments, preserving file names.
  4. If status is not terminal, wait a short interval and call again to pick up new segments.
  5. When mp4.ready is true, download the MP4.
  6. If signed links expire, call getVideoFiles again to obtain fresh links.
sequenceDiagram
    autonumber
    participant BE as Customer backend
    participant CDN as Think10x.ai CDN
    participant T as Think10x.ai API

    BE->>T: POST /external/v1/createVideo
    T-->>BE: think10xvideoId

    loop Until mp4.ready is true or status is terminal
        BE->>T: GET /external/v1/getVideoFiles/{videoId}
        T-->>BE: status, progressPercent, HLS links, MP4 link if ready

        alt HLS ready
            BE->>CDN: Download playlist and segments
            Note over BE: Preserve returned fileName values
        end

        alt MP4 ready
            BE->>CDN: Download MP4
            Note over BE: Store, re-host, or replay. Done.
        else Not ready
            Note over BE: Wait 3–5 seconds, then poll again
        end
    end

Best practices

  • Preserve the returned fileName for the playlist and each segment.
  • Download signed links promptly. Links expire after a short window, approximately 10 minutes.
  • If a link expires, call getVideoFiles again for fresh links.
  • Use HLS for progressive/adaptive playback.
  • Use MP4 when you need one self-contained file after generation completes.
  • Use getVideo instead of getVideoFiles when you only need status.

Flow C: Use Your Own Player

Use this flow when you want to play the generated video with your own player, on your page, streaming the media directly from the Think10x.ai CDN. There is no iframe and no downloading or re-hosting. Your page fetches a short-lived cookie from Think10x.ai once, and from then on the browser sends that cookie automatically with every media request to the CDN.

This is the most advanced flow. Before choosing it, read the browser support note directly below.

Browser support — read this first

Flow C protects the media with a cross-site cookie: a cookie set by Think10x.ai that is used while your page is open on your own domain. Modern browsers only allow this through partitioned cookies, also called CHIPS (Cookies Having Independent Partitioned State).

Flow C works only in browsers that support partitioned cookies:

BrowserFlow C supported?
Chrome / Edge 114+Yes
Firefox (current versions)Yes
Safari (macOS / iOS) 18.4+ (released March 2025)Yes
Chrome / Edge below 114No
Safari below 18.4, including many older iPhones and iPadsNo
Other old or unusual browsers and embedded webviewsAssume no

Flow C is only suitable when your audience runs on the supported browsers above. The optional cookie check described later in this section lets your page confirm, before playback starts, whether the cookie was accepted in the current browser, so you can decide how to proceed for that user.

How it is protected

Three credentials are involved, each used in exactly one place:

CredentialUsed byWhere it lives
API keycreatePlaybackTicket, getVideoPlaybackYour backend only. Never sent to the browser.
Playback ticketgetCookieThe browser, briefly. Single-use, valid 5 minutes.
Signed cookieEvery CDN media requestThe browser, set by getCookie. Your JavaScript cannot read it; the browser attaches it automatically.

The rule of thumb is the same as every other flow: anything authenticated with the API key is backend-to-backend only. The browser only ever holds a short-lived ticket and the CDN cookies.

Security model

The API key never leaves the customer backend. After your backend mints a playback ticket, the frontend receives only:

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "ticket": "pt_8f3c19e2b7a04d56...",
  "cdnBaseUrl": "https://cdn.think10x.ai/vids",
  "hls": { "ready": true, "playlistPath": "videos/00cb7a5f/index.m3u8" },
  "mp4": { "ready": false, "path": null },
  "probePath": "probe.txt"
}

Recommended topology:

End user → Customer frontend → Customer backend → Think10x.ai API
                                 holds API key
                                 owns question → video mapping
                                 authorizes end-user access

What calls what, in order

Steps 1–3 happen on your backend (with the API key). Steps 4–6 happen in the browser (no API key, ever).

  1. createVideo (backend) — once per question or topic, exactly as in every other flow. Store the returned think10xvideoId and reuse it for repeat views.
  2. createPlaybackTicket (backend) — every time a user presses play. Send the videoId and your page origin in allowedOrigins. You receive a single-use pt_ ticket, valid for 5 minutes.
  3. getVideoPlayback (backend) — returns the cdnBaseUrl, the relative hls.playlistPath and mp4.path, the probePath, and the current status. Return all of it plus the ticket to your frontend in one response.
  4. getCookie (browser) — call it with the ticket in the ticket header and credentials: 'include'. The CDN cookies are now stored in the browser.
  5. Optional cookie check (browser) — fetch cdnBaseUrl + '/' + probePath with credentials to confirm the cookie was accepted in this browser.
  6. Play (browser) — point your player at cdnBaseUrl + '/' + hls.playlistPath (or the MP4) with credentials enabled. Start as soon as hls.ready is true; do not wait for status: completed. The playlist keeps growing while generation finishes.

Minimal backend sequence

sequenceDiagram
    autonumber
    participant FE as Customer frontend
    participant BE as Customer backend
    participant T as Think10x.ai API
    participant CDN as Think10x.ai CDN

    FE->>BE: User clicks Watch, resolve playback
    BE->>BE: Look up questionId → think10xvideoId

    alt No existing video
        BE->>T: POST /external/v1/createVideo
        T-->>BE: think10xvideoId
        BE->>BE: Save questionId → think10xvideoId
    end

    BE->>T: POST /external/v1/createPlaybackTicket
    T-->>BE: ticket (pt_..., 5 min), expiresAt
    BE->>T: GET /external/v1/getVideoPlayback/{videoId}
    T-->>BE: status, cdnBaseUrl, hls path, mp4 path, probePath
    BE-->>FE: ticket, cdnBaseUrl, paths, status

    FE->>T: GET /integration/getcookie/{videoId} (ticket header, credentials include)
    T-->>FE: 200 + Set-Cookie (CDN cookies stored)

    opt Optional cookie check
        FE->>CDN: GET cdnBaseUrl + probePath (with credentials)
        CDN-->>FE: 200 means the cookie works
    end

    FE->>CDN: Player loads cdnBaseUrl + hls path (with credentials)
    CDN-->>FE: playlist and segments, player follows the growing playlist

Backend orchestration endpoint

As in Flow A, your frontend should call one customer-owned endpoint, for example:

POST /api/video/playback

The endpoint creates or reuses the video, mints a fresh playback ticket, fetches the playback paths, and returns one consolidated response.

Backend pseudo-code

async function resolvePlayback({ questionId, externalUserId, pageOrigin, extraMetadata }) {
  let mapping = await db.videoMapping.find({ questionId });

  if (!mapping) {
    const question = await db.questions.findById(questionId);
    const createBody = buildCreateVideoPayload(question, extraMetadata);

    createBody.externalUserId = externalUserId;

    const { think10xvideoId } = await think10x.createVideo(createBody);

    mapping = await db.videoMapping.insert({
      questionId,
      think10xvideoId,
      createdAt: Date.now()
    });
  }

  const { ticket } = await think10x.createPlaybackTicket({
    videoId: mapping.think10xvideoId,
    externalUserId,
    allowedOrigins: [pageOrigin]
  });

  const playback = await think10x.getVideoPlayback(mapping.think10xvideoId);

  return {
    think10xvideoId: mapping.think10xvideoId,
    ticket,
    cdnBaseUrl: playback.cdnBaseUrl,
    hls: playback.hls,
    mp4: playback.mp4,
    probePath: playback.probePath,
    status: playback.status
  };
}

Mint a fresh playback ticket for every playback session, even when reusing an existing think10xvideoId. A playback ticket is single-use and expires in 5 minutes.

Frontend playback guide

Your page exchanges the ticket for the cookie and then plays from the CDN. Both the getCookie call and every media request that follows it must be credentialed, which means they explicitly opt in to sending and storing cookies. Most Flow C integration issues come from this, so it is worth setting up correctly from the start.

// 1. One call to YOUR backend (it runs createPlaybackTicket + getVideoPlayback,
//    and createVideo first if this question has no video yet).
const { think10xvideoId, ticket, cdnBaseUrl, hls, probePath } = await fetch("/api/video/playback", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ questionId: "Q-1023" }),
}).then((r) => r.json());

// 2. Exchange the ticket for the CDN cookie.
//    credentials: 'include' is REQUIRED. Without it the cookie is silently dropped.
await fetch(`https://<think10x-api-base-url>/integration/getcookie/${think10xvideoId}`, {
  headers: { ticket },
  credentials: "include",
});

// 3. (Optional) Probe: confirms the cookie was accepted in THIS browser.
const probe = await fetch(`${cdnBaseUrl}/${probePath}`, { credentials: "include" });
if (!probe.ok) {
  // The cookie was not stored in this browser. Decide how to handle this user.
  return;
}

// 4. Play with hls.js. withCredentials on every request, so the cookie rides along.
const player = new Hls({
  xhrSetup: (xhr) => { xhr.withCredentials = true; },
});
player.loadSource(`${cdnBaseUrl}/${hls.playlistPath}`);
player.attachMedia(videoElement);

For a plain <video> tag — for example, playing the MP4 once mp4.ready is true — set crossOrigin="use-credentials" on the element instead of xhrSetup.

Credential and origin requirements

1. Send credentials on every request that uses the cookie.

A cross-site cookie is only stored, and only sent, when the request explicitly asks for it. This applies to each request, not only the first one.

  • On the getCookie fetch, set credentials: 'include'. This allows the browser to store the cookie.
  • On every media request, set xhr.withCredentials = true in hls.js (via xhrSetup), or crossOrigin="use-credentials" on a plain <video> element. This sends the cookie back to the CDN.

If either is missing, the symptoms are misleading: getCookie returns 200 as normal, but every CDN request then returns 403, with nothing to indicate that a credential was the cause.

2. List your exact page origin in allowedOrigins.

An origin is scheme://host[:port], in lowercase, with no path and no trailing slash. The browser matches it exactly, so the following are treated as separate origins:

  • https://www.your-site.com and https://your-site.com
  • https://your-site.com and http://your-site.com

Include every origin you serve the player from. If the page origin is not listed, getCookie returns 403 FORBIDDEN_ORIGIN.

3. Keep the API key on your backend.

In Flow C the frontend only needs the short-lived pt_ ticket; the API key is never required in the browser, the same as in Flow A and Flow B. If the key is ever exposed to client code, rotate it.

4. Do not read the cookies from JavaScript.

The CDN cookies are HttpOnly, so they will not appear in document.cookie. This is by design. There is no need to read or attach them manually; once getCookie succeeds, the browser sends them automatically to the CDN on every credentialed request.

5. Call getCookie once per session, not per segment.

One successful getCookie covers the playlist and all of its segments until the cookie expires. It does not need to be called for each segment. If the cookie expires during playback, request a fresh ticket from your backend, call getCookie again, and resume.

Waiting for the video — start playback early

Because HLS is produced incrementally, hls.ready becomes true before generation finishes — the .m3u8 playlist is partial and keeps growing. The recommended pattern:

  • Poll getVideoPlayback through your backend on a short interval (every few seconds).
  • As soon as hls.ready is true, start playback — point your player at cdnBaseUrl + '/' + hls.playlistPath. The player then follows the growing playlist for you.
  • Do not wait for status: completed. The viewer starts watching early while the rest of the video generates.
  • Use the MP4 only when you want a single self-contained file, and only once mp4.ready is true.

The cookie check is an optional step that lets your page confirm the signed cookie was accepted before you start the player. Without it, a blocked or missing cookie only becomes visible later, as a failed media request.

getVideoPlayback returns a probePath for this purpose. Joined to cdnBaseUrl, it forms the URL of a small static file on the CDN that is protected by the same signed cookie as the media. The file is not an API; it is simply an object your frontend can request to check whether the cookie is in place.

To run the check, call getCookie first, then make a credentialed GET request for cdnBaseUrl + '/' + probePath:

Probe resultMeaning
200The cookie is valid in this browser. Safe to start the player.
401 / 403The cookie was not stored — typically an older browser without partitioned-cookie support, or a missing credentials: 'include'.

The check is optional, and playback works without it, but it lets you detect a blocked cookie up front instead of through failing media requests. Use the same credentialed request as the media, with credentials: 'include'.

The signed cookie is short-lived. When it expires mid-session, ask your backend for a fresh ticket, call getCookie again, then resume playback — exactly like minting a fresh ticket per session.

Playback failure behavior

ScenarioExpected behaviorRecommended customer action
Ticket expired or already usedgetCookie rejects the requestRequest a fresh ticket from your backend
Playback ticket used as an iframe ticket (or vice versa)Rejected — the two ticket types are not interchangeableMint the correct ticket type for the flow you are using
Page origin not in allowedOriginsgetCookie rejects the requestVerify allowedOrigins uses exact origins — scheme, host, port, no trailing slash
Missing credentials: 'include'getCookie looks fine, then every CDN request returns 403Add credentials: 'include' to getCookie and withCredentials to media requests
Cookie blocked by the browserCDN requests return 403; the probe returns 401 / 403Confirm the browser supports partitioned cookies (see the support table above)
Video still processingThe playlist is partial and growsStart playback at hls.ready; the player follows the growing playlist

API Reference

1. Get Language Options

Returns the language / speech variants configured for the customer.

GET /external/v1/getLangOptions

Headers

CustomerId: <customer-id>
Authorization: Bearer <api-key>

Request

No request body. No path parameters.

Example response

{
  "languages": [
    {
      "languageKey": "hinglish",
      "title": "Hinglish",
      "isDefault": true
    },
    {
      "languageKey": "english",
      "title": "English",
      "isDefault": false
    }
  ]
}

Example response when language selection is disabled

{
  "languages": []
}

Response fields

FieldTypeDescription
languagesarrayAvailable speech variants. Empty when language selection is not enabled.
languages[].languageKeystringOpaque identifier passed to createVideo as languageKey.
languages[].titlestringHuman-readable label for the UI selector.
languages[].isDefaultbooleantrue for the variant that should be pre-selected.

Usage notes

  • If languages is non-empty, render a selector and send the selected languageKey to createVideo.
  • If languages is empty, do not render a selector and do not send languageKey.
  • If no entry is marked default, the first entry may be used.

2. Create Video

Starts an asynchronous video generation job.

POST /external/v1/createVideo

Headers

Content-Type: application/json
CustomerId: <customer-id>
Authorization: Bearer <api-key>

Request body

{
  "requestedBy": "ADMIN",
  "videoType": "QUESTION_SOLUTION",
  "externalQuestionId": "5ba3abd3-9bb9-4057-8cf7-f9d4b7679721",
  "externalUserId": "student-7842",
  "questionContent": "<p>Explain why option A is correct and eliminate the other options.</p>",
  "questionImageBase64": "<base64-image-content>",
  "questionImageMimeType": "image/png",
  "questionImageName": "question-image.png",
  "languageKey": "hinglish"
}

Request fields

FieldTypeRequiredDescription
requestedBystringYesPublished values:ADMIN, STUDENT.
videoTypestringYesPublished values:QUESTION_SOLUTION, TOPIC_EXPLANATION.
externalQuestionIdstringNoCustomer-owned question ID. Stored for traceability when supplied.
externalUserIdstringNo, recommendedCustomer-owned user ID for the creator/requester. Maximum length:256. Use a stable non-PII value when possible.
questionContentstringConditionalRequired when questionImageBase64 is not supplied. HTML is accepted.
questionImageBase64stringConditionalRaw base64 image content without a data URL prefix.
questionImageMimeTypestringConditionalRequired when questionImageBase64 is supplied. Allowed values: image/png, image/jpg, image/jpeg.
questionImageNamestringNoOptional original filename for the uploaded image.
languageKeystringNoSpeech variant identifier from getLangOptions. Omit when language selection is disabled or no variant is selected.

Validation rules

  • requestedBy is required.
  • videoType is required.
  • If questionImageBase64 is not supplied, questionContent is required.
  • If questionImageBase64 is supplied, questionImageMimeType is required.
  • questionImageBase64 must contain raw base64 content without a data: URL prefix.
  • Recommended decoded image size: under 8 MB. Larger images may fail or take longer to process.
  • HTML is accepted in questionContent.

Video type guidance

  • Use QUESTION_SOLUTION for question-level answer explanations.
  • Use TOPIC_EXPLANATION for broader concept, skill, or topic explanations.

Compatibility notes

  • Mixed-case requestedBy values may be accepted at runtime, but ADMIN and STUDENT are the published contract values.
  • Integrations should use the published uppercase values.

For questions that include images, diagrams, figures, equations rendered as graphics, charts, or complex formatting, we strongly recommend rendering the full question as a single image with the converthtmltoimage utility API and supplying the result via questionImageBase64.

This produces the most visually faithful video output.

Language behavior

languageKey is optional.

Customer language configurationRecommended behavior
getLangOptions returns a non-empty languages arraySend the selected languageKey. If omitted, the default variant is used.
getLangOptions returns an empty arrayDo not send languageKey. The customer's default language is used.

Example response

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "status": "processing",
  "progressPercent": 0,
  "insertedTimeStamp": 1742812200,
  "languageKey": "hinglish"
}

Response fields

FieldTypeDescription
think10xvideoIdstringUnique video job ID.
statusstringCurrent video generation status.
progressPercentnumberCurrent generation progress from 0 to 100.
insertedTimeStampnumberUTC Unix timestamp in seconds when the video job was created.
languageKeystring | nullLanguage variant used for generation, or null when language selection is not applicable.

3. Get Video

Returns the current stored status snapshot for a video.

GET /external/v1/getVideo/{videoId}

Headers

CustomerId: <customer-id>
Authorization: Bearer <api-key>

Path parameters

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId returned by createVideo.

Behavior

  • The video must belong to the supplied CustomerId.
  • Use this endpoint for lightweight status and progress polling.
  • Use getVideoFiles only when media download links are needed.

Example response

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "status": "processing",
  "progressPercent": 50,
  "insertedTimeStamp": 1742812200,
  "languageKey": "hinglish"
}

4. Get Video Files

Returns time-limited signed download links for generated media.

GET /external/v1/getVideoFiles/{videoId}

Headers

CustomerId: <customer-id>
Authorization: Bearer <api-key>

Purpose

Use this endpoint for backend-to-backend media download.

It returns:

  • HLS files: .m3u8 playlist and .ts segments
  • Final MP4 when generation is complete
  • Current status and progressPercent
  • A shared expiry timestamp for all links in the response

HLS files may be available partially while generation is still in progress. The final MP4 is available only after generation completes.

Path parameters

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId returned by createVideo.

Request

No request body. No query parameters.

Example response

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "status": "processing",
  "progressPercent": 50,
  "videoDurationInSeconds": 120,
  "hls": {
    "ready": true,
    "playlist": {
      "fileName": "index.m3u8",
      "url": "https://<cdn-host>/videos/00cb7a5f/index.m3u8?<signature>"
    },
    "segments": [
      {
        "fileName": "seg-0.ts",
        "url": "https://<cdn-host>/videos/00cb7a5f/seg-0.ts?<signature>"
      },
      {
        "fileName": "seg-1.ts",
        "url": "https://<cdn-host>/videos/00cb7a5f/seg-1.ts?<signature>"
      }
    ]
  },
  "mp4": {
    "ready": false,
    "url": null
  },
  "urlExpiresAtInSeconds": 1748312560
}

Response fields

FieldTypeDescription
think10xvideoIdstringUnique video job ID.
statusstringCurrent job status.
progressPercentnumberCurrent progress percentage.
videoDurationInSecondsnumber | nullDuration of the video in seconds when known; null until available.
hlsobjectHLS playlist and segment links.
hls.readybooleantrue when HLS files exist and links were generated. May be true before generation completes.
hls.playlistobject | nullThe .m3u8 playlist link, or null if no playlist exists yet.
hls.playlist.fileNamestringPlaylist file name. Preserve it when downloading.
hls.playlist.urlstringTime-limited signed download URL for the playlist.
hls.segmentsarrayCurrent .ts segment links. This list grows as generation continues.
hls.segments[].fileNamestringSegment file name. Preserve it when downloading.
hls.segments[].urlstringTime-limited signed download URL for the segment.
mp4objectFinal MP4 link.
mp4.readybooleantrue only when the MP4 is ready.
mp4.urlstring | nullTime-limited signed download URL for the MP4, or null until it is ready.
urlExpiresAtInSecondsnumberUTC Unix timestamp in seconds when every link in this response expires.
  • Each url is an individually signed, time-limited HTTPS link served over the CDN.
  • The same urlExpiresAtInSeconds applies to every link in a single response.
  • Links are valid for a short window, approximately 10 minutes.
  • Links are not single-use; they may be downloaded multiple times until expiry.
  • Preserve the returned fileName for the playlist and each segment.
  • Think10x.ai does not rewrite playlist paths after you download the files.

5. Create Iframe Ticket

Mints a single-use, short-lived ticket for iframe playback.

POST /external/v1/createIframeTicket

Headers

Content-Type: application/json
CustomerId: <customer-id>
Authorization: Bearer <api-key>

Request body

{
  "videoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "externalUserId": "student-9921",
  "allowedParentOrigins": [
    "https://customer-lms.com",
    "https://students.customer-lms.com"
  ]
}

Request fields

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId returned by createVideo. Must belong to the supplied CustomerId.
externalUserIdstringNo, recommendedCustomer-owned user ID for the viewer. Maximum length:256.
allowedParentOriginsarrayYesOne or more origins on which the iframe is allowed to render. Each entry must be scheme://host[:port]. No path, wildcard, or trailing slash.

Validation rules

  • videoId is required.
  • videoId must reference a video owned by the supplied CustomerId.
  • allowedParentOrigins is required.
  • allowedParentOrigins must contain at least one syntactically valid origin.
  • Origins should be lowercase and exact.

Ticket lifetime

Each ticket is valid for 5 minutes from the moment it is issued. Ticket lifetime is controlled by the service and is not configurable from the request.

Example response

{
  "ticket": "tk_8f3c19e2b7a04d56...",
  "expiresAt": 1748312560
}

Response fields

FieldTypeDescription
ticketstringOpaque single-use credential. Pass it to the frontend as the ticket URL parameter.
expiresAtnumberUTC Unix timestamp in seconds when the ticket expires.

Where the ticket is used

https://<think10x-app-host>/video-ai-integration/<videoId>?ticket=<ticket>

When the iframe loads:

  1. The iframe exchanges the ticket for an authenticated session.
  2. The ticket is consumed atomically.
  3. The iframe validates the embedding page's origin against allowedParentOrigins.
  4. The iframe proceeds with playback or progress rendering.
  5. The externalUserId, if supplied, is recorded against the view for analytics.

A ticket authorizes exactly one iframe view. Refreshing the iframe with the same ticket, opening the URL in a second tab, or replaying the URL will be rejected.


6. Create Playback Ticket

Mints a single-use, short-lived ticket the browser uses to obtain the CDN playback cookie (Flow C).

POST /external/v1/createPlaybackTicket

Headers

Content-Type: application/json
CustomerId: <customer-id>
Authorization: Bearer <api-key>

Request body

{
  "videoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "allowedOrigins": ["https://customer-lms.com"],
  "externalUserId": "student-7842"
}

Request fields

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId returned by createVideo. Must belong to the supplied CustomerId.
allowedOriginsarrayYesOne or more page origins allowed to call getCookie. Each entry must be scheme://host[:port] — no path, wildcard, or trailing slash.
externalUserIdstringNo, recommendedCustomer-owned viewer ID, stored on the ticket for analytics. Maximum length: 256.

Validation rules

  • videoId is required and must reference a video owned by the supplied CustomerId.
  • allowedOrigins is required and must contain at least one syntactically valid origin.
  • Origins should be lowercase and exact.

Ticket lifetime

Each ticket is valid for 5 minutes from the moment it is issued and is consumed the first time it is used. Ticket lifetime is controlled by the service and is not configurable from the request.

Example response

{
  "ticket": "pt_8f3c19e2b7a04d56...",
  "expiresAt": 1748312560
}

Response fields

FieldTypeDescription
ticketstringSingle-use credential for getCookie. Always prefixed pt_. Pass it to your frontend.
expiresAtnumberUTC Unix timestamp in seconds when the ticket expires.

7. Get Video Playback

Returns the CDN base URL and the relative media paths your frontend needs to play in your own player (Flow C).

GET /external/v1/getVideoPlayback/{videoId}

Headers

CustomerId: <customer-id>
Authorization: Bearer <api-key>

Purpose

Use this endpoint for Flow C. It returns:

  • The current status and progressPercent
  • The cdnBaseUrl and the relative HLS and MP4 paths
  • The optional cookie-check probePath

The relative paths carry no signatures; the signed cookie set by getCookie authorizes the CDN requests. Both HLS and MP4 are always returned; you pick which to play.

Path parameters

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId returned by createVideo.

Request

No request body. No query parameters.

Example response

{
  "think10xvideoId": "00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7",
  "status": "processing",
  "progressPercent": 50,
  "videoDurationInSeconds": 120,
  "cdnBaseUrl": "https://cdn.think10x.ai/vids",
  "hls": {
    "ready": true,
    "playlistPath": "videos/00cb7a5f/index.m3u8"
  },
  "mp4": {
    "ready": false,
    "path": null
  },
  "probePath": "probe.txt"
}

Response fields

FieldTypeDescription
think10xvideoIdstringUnique video job ID.
statusstringCurrent job status.
progressPercentnumberCurrent progress percentage.
videoDurationInSecondsnumber | nullDuration in seconds when known; null until available.
cdnBaseUrlstringBase URL your frontend joins each path to. Full media URL = cdnBaseUrl + / + path.
hls.readybooleantrue when a playlist exists. May be true while still generating; the playlist is partial and grows.
hls.playlistPathstring | nullRelative path of the .m3u8 playlist, or null if none exists yet.
mp4.readybooleantrue only when the final MP4 is complete.
mp4.pathstring | nullRelative path of the MP4, or null until ready.
probePathstringRelative path of the optional cookie-check file.

Exchanges a playback ticket for the CDN signed cookie. This is the only Think10x.ai endpoint your frontend (browser) calls directly.

GET /integration/getcookie/{videoId}

Authentication

Authenticated by the playback ticket, never the API key. The API key must not appear in frontend code.

Path parameters

FieldTypeRequiredDescription
videoIdstringYesThe think10xvideoId the ticket was minted for. Must match the ticket.

Headers

HeaderSet byDescription
ticketYour frontend codeThe pt_ ticket from createPlaybackTicket.
OriginBrowser (automatic)Checked against the ticket's allowedOrigins. You do not set this yourself.

How to call it

const response = await fetch(
  `https://<think10x-api-base-url>/integration/getcookie/${videoId}`,
  {
    headers: { ticket: playbackTicket },
    credentials: "include"   // REQUIRED. Without it the cookies are silently dropped.
  }
);

credentials: 'include' is mandatory. Without it the browser discards the Set-Cookie headers and every CDN request afterwards fails with 403, while the getCookie step itself shows no error.

Example response

{ "ok": true, "expiresAt": 1748319999 }

The response also carries the Set-Cookie headers that store the CDN cookies in the browser. The cookies are HttpOnly: your JavaScript cannot read them, and that is intended.

Response fields

FieldTypeDescription
okbooleantrue when the cookies were set.
expiresAtnumberUTC Unix timestamp in seconds when the cookies expire.

Behavior and rules

  • Single-use: each ticket works exactly once. Refreshing or retrying means minting a fresh ticket on your backend first.
  • Origin-bound: the call only succeeds from a page whose origin is in the ticket's allowedOrigins. The browser sends Origin automatically; it cannot be faked from page JavaScript.
  • Cookie expiry: when the cookies expire mid-session, mint a fresh ticket, call getCookie again, then resume playback.

Error responses

HTTPCodeWhen
401UNAUTHORIZEDAny ticket problem: missing, expired, already used, wrong video, or a non-playback ticket.
403FORBIDDEN_ORIGINThe page origin is not in the ticket's allowedOrigins.
500INTERNAL_SERVER_ERRORUnexpected server error.

9. Utility: Convert HTML to Image

Renders HTML content into a PNG image and returns the result as raw base64.

POST /external/converthtmltoimage

Headers

Content-Type: application/json
CustomerId: <customer-id>
Authorization: Bearer <api-key>

Purpose

Use this endpoint when your question content contains complex formatting, diagrams, math rendering, tables, screenshots, or other visual elements that should be preserved exactly in the generated video.

The endpoint returns an image and does not create a video.

Request body

{
  "questionhtml": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>Question Render</title>\n  <style>\n    body {\n      font-family: Arial, sans-serif;\n      margin: 0;\n      padding: 32px;\n      background: #f5f5f5;\n      color: #333333;\n    }\n\n    .card {\n      background: #ffffff;\n      padding: 24px;\n      border-radius: 12px;\n    }\n\n    h1 {\n      margin-top: 0;\n      font-size: 28px;\n    }\n\n    p {\n      font-size: 18px;\n      line-height: 1.6;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"card\">\n    <h1>Question</h1>\n    <p>Explain this question step by step.</p>\n  </div>\n</body>\n</html>"
}

Request fields

FieldTypeRequiredDescription
questionhtmlstringYesHTML content to render as an image. Inline CSS is supported.

Notes

  • The request body must be valid JSON.
  • Inline CSS is supported.
  • <script> tags are ignored during rendering.
  • For best results, use complete HTML with explicit dimensions, fonts, spacing, and background color.
  • Use the returned imageBase64 and mimeType as questionImageBase64 and questionImageMimeType in createVideo.

Example response

{
  "imageBase64": "<base64-image-content>",
  "mimeType": "image/png"
}

Response fields

FieldTypeDescription
imageBase64stringRaw base64 image content without a data URL prefix.
mimeTypestringMIME type of the generated image. Current output is image/png.

Error Handling

Errors use a standard response shape.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed.",
    "details": [
      {
        "field": "allowedParentOrigins",
        "message": "allowedParentOrigins must contain at least one entry."
      }
    ]
  }
}

Error fields

FieldTypeDescription
error.codestringMachine-readable error code.
error.messagestringHuman-readable summary.
error.detailsarrayOptional field-level details.
error.details[].fieldstringField, header, path parameter, or request area that caused the error.
error.details[].messagestringDetailed message for that field.

Common error codes

HTTP statusCodeMeaningRetry?
400INVALID_JSONRequest body is not valid JSONNo
400VALIDATION_ERRORMissing or malformed request fields, headers, or path parametersNo
401UNAUTHORIZEDInvalid CustomerId or API keyNo
404VIDEO_NOT_FOUNDVideo does not exist or does not belong to the supplied CustomerIdNo
403FORBIDDEN_ORIGINPage origin not allowed for the playback ticket (getCookie, Flow C)No
429RATE_LIMITEDToo many requestsYes; retry after the indicated delay
500INTERNAL_SERVER_ERRORUnexpected server errorYes, with backoff

For getCookie (Flow C), any ticket problem — missing, expired, already used, wrong video, or a non-playback (tk_) ticket — returns a single uniform 401. The response never reveals which check failed.

Example unauthorized error

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authorization failed for the supplied customerId.",
    "details": [
      {
        "field": "headers.Authorization",
        "message": "Authorization header does not match the configured API key for this customerId."
      }
    ]
  }
}

Example video not found error

{
  "error": {
    "code": "VIDEO_NOT_FOUND",
    "message": "Video not found for the supplied videoId.",
    "details": [
      {
        "field": "pathParameters.videoId",
        "message": "No video found for videoId 00cb7a5f-f119-4f8a-bf9a-0b10ff4f3df7."
      }
    ]
  }
}

Operational Guidance

SituationRecommended behavior
Network timeoutRetry with exponential backoff
HTTP 500Retry with exponential backoff
HTTP 429Retry after the Retry-After value if present
HTTP 400Do not retry without changing the request
HTTP 401Do not retry; check credentials
VIDEO_NOT_FOUNDDo not retry unless the ID may be stale or incorrect
Expired iframe ticketMint a new ticket
Used iframe ticketMint a new ticket

Recommended backoff pattern for retryable errors:

1 second → 2 seconds → 4 seconds → 8 seconds → stop or surface error

Rate limits

Think10x.ai may apply customer-specific rate limits to protect the platform and ensure fair usage.

If a request is rate limited, the API may return:

HTTP/1.1 429 Too Many Requests
Retry-After: 10

Recommended customer behavior:

  • Respect the Retry-After header when present.
  • Apply customer-side rate limiting on your own backend endpoint.
  • Avoid creating duplicate video jobs for repeated user clicks.
  • Disable or debounce the frontend Watch Video button while a resolve request is in progress.

Your provisioned rate limits, if applicable, will be provided during onboarding or in your customer agreement.

Payload limits and constraints

Payload areaGuidance
questionImageBase64Recommended decoded image size under 8 MB. Raw base64 only, no data: prefix.
questionImageMimeTypeMust be image/png, image/jpg, or image/jpeg.
questionContentHTML is accepted. For complex or image-heavy content, render to image first.
questionhtmlMust be valid JSON string content. Inline CSS is supported; scripts are ignored.
externalUserIdMaximum length 256. Prefer stable non-PII IDs.
allowedParentOriginsUse exact origins only:scheme://host[:port]. No path, wildcard, or trailing slash.
Iframe ticketValid for 5 minutes and single-use. Mint immediately before iframe load.
Signed media linksValid for approximately 10 minutes. Download promptly.
allowedOriginsFlow C. Use exact origins only: scheme://host[:port]. No path, wildcard, or trailing slash.
Playback ticketFlow C. Valid for 5 minutes and single-use. Mint immediately before calling getCookie.
CDN signed cookieFlow C. Set by getCookie; sent automatically to the CDN. Requires a partitioned-cookie-capable browser.

Customer backend mapping recommendations

To avoid duplicate video generation:

  • Store questionId → think10xvideoId for non-language integrations.
  • Store (questionId, languageKey) → think10xvideoId for language-enabled integrations.
  • Store createdAt, status, and any relevant metadata for support.
  • Reuse think10xvideoId for repeat views.
  • Always mint a new iframe ticket for every playback.

Frontend click-handling recommendations

  • Disable the Watch Video button while the backend resolve call is in progress.
  • Show a loading state while waiting for think10xvideoId and ticket.
  • Do not cache iframe tickets.
  • If the iframe fails because of expired or reused ticket, request a new ticket.
  • Validate iframe postMessage events before closing the host dialog.

Security, Privacy, and Data Handling

Customer responsibilities

The customer backend is responsible for:

  1. Authenticating the end user.
  2. Authorizing whether the user is allowed to view or generate a video for the given question.
  3. Protecting the Think10x.ai API key.
  4. Preventing unauthenticated public access to backend proxy endpoints.
  5. Applying customer-side rate limiting and abuse protection.
  6. Avoiding unnecessary personally identifiable information in request payloads.

API key handling

  • Store the API key in a secure secret manager or encrypted environment variable.
  • Do not log the API key.
  • Do not include the API key in client-side bundles.
  • Do not expose raw Think10x.ai API endpoints directly to browsers.
  • Rotate the key if it may have been exposed.

externalUserId privacy guidance

Use a stable opaque identifier from your own system, such as:

student-7842
user-8f91a2

Avoid directly identifying values unless required by your internal policy, such as:

  • Student name
  • Email address
  • Phone number
  • External account username

Question content guidance

Only send the content needed to generate the requested video.

For complex question layouts, the recommended pattern is:

  1. Render the full question into an image using converthtmltoimage.
  2. Send the rendered image to createVideo using questionImageBase64 and questionImageMimeType.
  3. Include externalQuestionId for traceability.

Data retention

Generated video records, media files, iframe ticket records, and associated metadata may be stored by Think10x.ai to provide video playback, support, analytics, and customer operations.

Retention terms may vary by customer agreement. Contact your Think10x.ai representative if you need specific retention, deletion, or compliance terms documented for your account.

Logging guidance

Customer systems should avoid logging:

  • API keys
  • Bearer tokens
  • Iframe tickets
  • Playback tickets
  • CDN signed cookie values
  • Signed media URLs
  • Raw base64 image payloads
  • Sensitive question content, unless required for internal debugging and protected appropriately

Support

For provisioning, credential rotation, integration help, data-retention questions, or production support, contact your Think10x.ai representative.

When contacting support, include:

  • Environment: staging or production
  • CustomerId
  • think10xvideoId, if available
  • Timestamp of the request
  • Endpoint called
  • Error code and message, if available

Do not send API keys, bearer tokens, iframe tickets, or signed media URLs in support tickets unless explicitly requested through a secure channel.