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.
Recommended iframe flow
- The user clicks Watch Video Solution in your application.
- Your frontend calls your backend, for example
POST /api/video/resolve. - Your backend checks whether this question already has a stored
think10xvideoId. - If no video exists yet, your backend calls
POST /external/v1/createVideo. - Your backend calls
POST /external/v1/createIframeTicketto mint a fresh single-use ticket. - 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 flow | Why |
|---|---|---|
| Ship the fastest, with the least frontend code | Flow A — Embedded iframe player | Think10x.ai hosts and plays the video; you embed one ticketed iframe URL. |
| Let Think10x.ai host the player, progress, and playback inside your app | Flow A — Embedded iframe player | Built-in player UI; the browser only ever loads the ticketed iframe. |
| Own the media files yourself to store, archive, or re-host them | Flow B — Backend media download | You download the HLS/MP4 and run them on your own infrastructure. |
| Process or replay videos offline or on your own CDN | Flow B — Backend media download | Pure backend-to-backend; the browser never calls Think10x.ai. |
| Build your own player and playback UI with your own branding and theme | Flow C — Use your own player | You develop the player and stream from our CDN; requires a partitioned-cookie browser. |
Flow comparison
| Concern | Flow A — Embedded iframe player | Flow B — Backend media download | Flow C — Use your own player |
|---|---|---|---|
| What you get | Think10x.ai-hosted interactive player | Raw HLS and MP4 media files | Media streamed into your own player from our CDN |
| Who plays the video | Think10x.ai iframe inside your page | Your own player or storage system | Your own player on your own page |
| Recommended for | Most customer integrations | Advanced media ownership use cases | Your own player UI without re-hosting media |
| Browser talks to Think10x.ai? | Yes, only through the iframe URL with a ticket | No | Yes, to fetch a cookie and stream from the CDN |
| API key exposed to browser? | No | No | No |
| APIs used | getLangOptions optional, createVideo, createIframeTicket | createVideo, getVideo optional, getVideoFiles | createVideo, createPlaybackTicket, getVideoPlayback, getCookie |
| Customer backend required? | Yes | Yes | Yes |
| Customer media storage required? | No | Yes | No |
| Browser support | All browsers | Not applicable | Partitioned-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.
| Endpoint | Method | Purpose | Called by | Browser-safe? |
|---|---|---|---|---|
/external/v1/getLangOptions | GET | Get enabled language / speech variants | Customer backend | No |
/external/v1/createVideo | POST | Start video generation | Customer backend | No |
/external/v1/getVideo/{videoId} | GET | Check video generation status | Customer backend | No |
/external/v1/getVideoFiles/{videoId} | GET | Get signed HLS / MP4 download links | Customer backend | No |
/external/v1/createIframeTicket | POST | Mint a single-use iframe ticket | Customer backend | No |
/external/v1/createPlaybackTicket | POST | Mint a single-use CDN playback ticket | Customer backend | No |
/external/v1/getVideoPlayback/{videoId} | GET | Get CDN base URL and relative media paths | Customer backend | No |
/external/converthtmltoimage | POST | Render HTML into an image | Customer backend | No |
/integration/getcookie/{videoId} | GET | Exchange a playback ticket for the CDN cookie | Browser | Yes 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.
| Value | Header / Usage | Description |
|---|---|---|
| API Base URL | Request host | The fully qualified base URL for your assigned environment, such as staging or production. Prepend it to every /external/... or /integration/... endpoint path. |
| App Host | Iframe host | The frontend host used for iframe playback, for example https://<think10x-app-host>. |
| CDN Host | Media host | The CDN host that serves the protected media for Flow C (use your own player). Your frontend receives it as cdnBaseUrl from getVideoPlayback. |
| CustomerId | CustomerId header | Your unique customer identifier. Sent on every backend API request to scope and authorize access to your data. |
| API key | Authorization header | Your 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
createVideorequest when you know who initiated generation. - Send this field on every
createIframeTicketrequest when you know who is viewing the video. - Use a stable, non-PII identifier from your own system, such as
student-7842or 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.
CDN signed cookie (partitioned)
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
getLangOptionsreturns a non-emptylanguagesarray, render a selector and send the selectedlanguageKeytocreateVideo. - If
getLangOptionsreturns an empty array, do not render a selector and do not sendlanguageKey. - If
languageKeyis 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:
- Maintain your own persistent mapping from your question ID to
think10xvideoId. - For language-enabled integrations, key the mapping by
(questionId, languageKey). - Before calling
createVideo, check whether a video already exists in your mapping. - Reuse the existing
think10xvideoIdwhen available. - 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
| Status | Meaning | Recommended action |
|---|---|---|
processing | Video generation is in progress | Continue polling or open the iframe and let Think10x.ai show progress |
completed | Video generation completed successfully | Play the video or download final media |
failed | Video generation failed | Show 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
statusisprocessing. - Stop polling when
statusis terminal:completedorfailed. - 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
getVideoPlaybackthrough your backend and start playing as soon ashls.readyistrue.
Recommended Flow A: Embedded Iframe Player
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:
- Authenticate and authorize the end user using your own identity system.
- Store the Think10x.ai
CustomerIdand API key as server-side secrets. - Maintain a persistent mapping from your question ID to Think10x.ai
think10xvideoId. - Build the
createVideopayload using your question content or rendered image. - Send
externalUserIdwhen available. - Mint a fresh iframe ticket immediately before playback.
- Return only
think10xvideoId,ticket, andexpiresAtto 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
- Call your backend endpoint that proxies
getLangOptions, for exampleGET /api/video/lang-options. - Render a language selector.
- Pre-select the entry where
isDefaultistrue. - Send the selected
languageKeyto your backend when resolving the video.
Backend mapping
Use a mapping key of (questionId, languageKey).
| questionId | languageKey | think10xvideoId | createdAt |
|---|---|---|---|
| Q-1023 | hinglish | 00cb7a5f-... | ... |
| Q-1023 | english | 7d2f3c14-... | ... |
| Q-1040 | hinglish | 9aa8b210-... | ... |
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
- Do not render a language selector.
- Do not send
languageKeyto your backend. - The video is generated in the customer's configured default language.
Backend mapping
Use a mapping key of questionId.
| questionId | think10xvideoId | createdAt |
|---|---|---|
| Q-1023 | 00cb7a5f-... | ... |
| Q-1040 | 9aa8b210-... | ... |
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.originevent.data.typeevent.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);
});
Recommended host-app behavior
- Keep the iframe mounted only while the dialog or overlay is open.
- Let the iframe's built-in close button drive the normal close flow.
- Listen for
THINK10X_CLOSEin the parent app. - Validate the Think10x.ai origin before reacting to iframe events.
- Remove the
messageevent listener when your component or page is destroyed. - Mint a new ticket for every user-initiated playback.
Ticket and iframe failure behavior
| Scenario | Expected behavior | Recommended customer action |
|---|---|---|
| Ticket expired | Iframe refuses playback | Request a new ticket from your backend |
| Ticket already used | Iframe refuses playback | Request a new ticket from your backend |
| Parent origin mismatch | Iframe refuses playback | Verify allowedParentOrigins uses exact origins, no paths or trailing slashes |
| Video still processing | Iframe may show processing/progress state | Keep iframe open or show customer-owned progress UI |
| Video generation failed | Iframe may show failure state | Offer 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
- Call
createVideoto start the job and get athink10xvideoId. - Poll
getVideoFilesto retrieve available HLS files and final MP4 when ready. - Download the playlist and all listed segments, preserving file names.
- If
statusis not terminal, wait a short interval and call again to pick up new segments. - When
mp4.readyistrue, download the MP4. - If signed links expire, call
getVideoFilesagain 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
fileNamefor the playlist and each segment. - Download signed links promptly. Links expire after a short window, approximately 10 minutes.
- If a link expires, call
getVideoFilesagain for fresh links. - Use HLS for progressive/adaptive playback.
- Use MP4 when you need one self-contained file after generation completes.
- Use
getVideoinstead ofgetVideoFileswhen 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:
| Browser | Flow C supported? |
|---|---|
Chrome / Edge 114+ | Yes |
| Firefox (current versions) | Yes |
Safari (macOS / iOS) 18.4+ (released March 2025) | Yes |
Chrome / Edge below 114 | No |
Safari below 18.4, including many older iPhones and iPads | No |
| Other old or unusual browsers and embedded webviews | Assume 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:
| Credential | Used by | Where it lives |
|---|---|---|
| API key | createPlaybackTicket, getVideoPlayback | Your backend only. Never sent to the browser. |
| Playback ticket | getCookie | The browser, briefly. Single-use, valid 5 minutes. |
| Signed cookie | Every CDN media request | The 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).
createVideo(backend) — once per question or topic, exactly as in every other flow. Store the returnedthink10xvideoIdand reuse it for repeat views.createPlaybackTicket(backend) — every time a user presses play. Send thevideoIdand your page origin inallowedOrigins. You receive a single-usept_ticket, valid for 5 minutes.getVideoPlayback(backend) — returns thecdnBaseUrl, the relativehls.playlistPathandmp4.path, theprobePath, and the current status. Return all of it plus the ticket to your frontend in one response.getCookie(browser) — call it with the ticket in theticketheader andcredentials: 'include'. The CDN cookies are now stored in the browser.- Optional cookie check (browser) — fetch
cdnBaseUrl + '/' + probePathwith credentials to confirm the cookie was accepted in this browser. - Play (browser) — point your player at
cdnBaseUrl + '/' + hls.playlistPath(or the MP4) with credentials enabled. Start as soon ashls.readyistrue; do not wait forstatus: 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
getCookiefetch, setcredentials: 'include'. This allows the browser to store the cookie. - On every media request, set
xhr.withCredentials = truein hls.js (viaxhrSetup), orcrossOrigin="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.comandhttps://your-site.comhttps://your-site.comandhttp://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
getVideoPlaybackthrough your backend on a short interval (every few seconds). - As soon as
hls.readyistrue, start playback — point your player atcdnBaseUrl + '/' + 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.readyistrue.
Optional cookie check (probePath)
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 result | Meaning |
|---|---|
200 | The cookie is valid in this browser. Safe to start the player. |
401 / 403 | The 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'.
Cookie refresh
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
| Scenario | Expected behavior | Recommended customer action |
|---|---|---|
| Ticket expired or already used | getCookie rejects the request | Request a fresh ticket from your backend |
| Playback ticket used as an iframe ticket (or vice versa) | Rejected — the two ticket types are not interchangeable | Mint the correct ticket type for the flow you are using |
Page origin not in allowedOrigins | getCookie rejects the request | Verify allowedOrigins uses exact origins — scheme, host, port, no trailing slash |
Missing credentials: 'include' | getCookie looks fine, then every CDN request returns 403 | Add credentials: 'include' to getCookie and withCredentials to media requests |
| Cookie blocked by the browser | CDN requests return 403; the probe returns 401 / 403 | Confirm the browser supports partitioned cookies (see the support table above) |
| Video still processing | The playlist is partial and grows | Start 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
| Field | Type | Description |
|---|---|---|
languages | array | Available speech variants. Empty when language selection is not enabled. |
languages[].languageKey | string | Opaque identifier passed to createVideo as languageKey. |
languages[].title | string | Human-readable label for the UI selector. |
languages[].isDefault | boolean | true for the variant that should be pre-selected. |
Usage notes
- If
languagesis non-empty, render a selector and send the selectedlanguageKeytocreateVideo. - If
languagesis empty, do not render a selector and do not sendlanguageKey. - 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
| Field | Type | Required | Description |
|---|---|---|---|
requestedBy | string | Yes | Published values:ADMIN, STUDENT. |
videoType | string | Yes | Published values:QUESTION_SOLUTION, TOPIC_EXPLANATION. |
externalQuestionId | string | No | Customer-owned question ID. Stored for traceability when supplied. |
externalUserId | string | No, recommended | Customer-owned user ID for the creator/requester. Maximum length:256. Use a stable non-PII value when possible. |
questionContent | string | Conditional | Required when questionImageBase64 is not supplied. HTML is accepted. |
questionImageBase64 | string | Conditional | Raw base64 image content without a data URL prefix. |
questionImageMimeType | string | Conditional | Required when questionImageBase64 is supplied. Allowed values: image/png, image/jpg, image/jpeg. |
questionImageName | string | No | Optional original filename for the uploaded image. |
languageKey | string | No | Speech variant identifier from getLangOptions. Omit when language selection is disabled or no variant is selected. |
Validation rules
requestedByis required.videoTypeis required.- If
questionImageBase64is not supplied,questionContentis required. - If
questionImageBase64is supplied,questionImageMimeTypeis required. questionImageBase64must contain raw base64 content without adata: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_SOLUTIONfor question-level answer explanations. - Use
TOPIC_EXPLANATIONfor broader concept, skill, or topic explanations.
Compatibility notes
- Mixed-case
requestedByvalues may be accepted at runtime, butADMINandSTUDENTare the published contract values. - Integrations should use the published uppercase values.
Recommended content format for image-heavy questions
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 configuration | Recommended behavior |
|---|---|
getLangOptions returns a non-empty languages array | Send the selected languageKey. If omitted, the default variant is used. |
getLangOptions returns an empty array | Do 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
| Field | Type | Description |
|---|---|---|
think10xvideoId | string | Unique video job ID. |
status | string | Current video generation status. |
progressPercent | number | Current generation progress from 0 to 100. |
insertedTimeStamp | number | UTC Unix timestamp in seconds when the video job was created. |
languageKey | string | null | Language 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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The think10xvideoId returned by createVideo. |
Behavior
- The video must belong to the supplied
CustomerId. - Use this endpoint for lightweight status and progress polling.
- Use
getVideoFilesonly 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:
.m3u8playlist and.tssegments - Final MP4 when generation is complete
- Current
statusandprogressPercent - 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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The 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
| Field | Type | Description |
|---|---|---|
think10xvideoId | string | Unique video job ID. |
status | string | Current job status. |
progressPercent | number | Current progress percentage. |
videoDurationInSeconds | number | null | Duration of the video in seconds when known; null until available. |
hls | object | HLS playlist and segment links. |
hls.ready | boolean | true when HLS files exist and links were generated. May be true before generation completes. |
hls.playlist | object | null | The .m3u8 playlist link, or null if no playlist exists yet. |
hls.playlist.fileName | string | Playlist file name. Preserve it when downloading. |
hls.playlist.url | string | Time-limited signed download URL for the playlist. |
hls.segments | array | Current .ts segment links. This list grows as generation continues. |
hls.segments[].fileName | string | Segment file name. Preserve it when downloading. |
hls.segments[].url | string | Time-limited signed download URL for the segment. |
mp4 | object | Final MP4 link. |
mp4.ready | boolean | true only when the MP4 is ready. |
mp4.url | string | null | Time-limited signed download URL for the MP4, or null until it is ready. |
urlExpiresAtInSeconds | number | UTC Unix timestamp in seconds when every link in this response expires. |
Download links and expiry
- Each
urlis an individually signed, time-limited HTTPS link served over the CDN. - The same
urlExpiresAtInSecondsapplies 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
fileNamefor 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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The think10xvideoId returned by createVideo. Must belong to the supplied CustomerId. |
externalUserId | string | No, recommended | Customer-owned user ID for the viewer. Maximum length:256. |
allowedParentOrigins | array | Yes | One 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
videoIdis required.videoIdmust reference a video owned by the suppliedCustomerId.allowedParentOriginsis required.allowedParentOriginsmust 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
| Field | Type | Description |
|---|---|---|
ticket | string | Opaque single-use credential. Pass it to the frontend as the ticket URL parameter. |
expiresAt | number | UTC 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:
- The iframe exchanges the ticket for an authenticated session.
- The ticket is consumed atomically.
- The iframe validates the embedding page's origin against
allowedParentOrigins. - The iframe proceeds with playback or progress rendering.
- 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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The think10xvideoId returned by createVideo. Must belong to the supplied CustomerId. |
allowedOrigins | array | Yes | One or more page origins allowed to call getCookie. Each entry must be scheme://host[:port] — no path, wildcard, or trailing slash. |
externalUserId | string | No, recommended | Customer-owned viewer ID, stored on the ticket for analytics. Maximum length: 256. |
Validation rules
videoIdis required and must reference a video owned by the suppliedCustomerId.allowedOriginsis 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
| Field | Type | Description |
|---|---|---|
ticket | string | Single-use credential for getCookie. Always prefixed pt_. Pass it to your frontend. |
expiresAt | number | UTC 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
statusandprogressPercent - The
cdnBaseUrland 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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The 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
| Field | Type | Description |
|---|---|---|
think10xvideoId | string | Unique video job ID. |
status | string | Current job status. |
progressPercent | number | Current progress percentage. |
videoDurationInSeconds | number | null | Duration in seconds when known; null until available. |
cdnBaseUrl | string | Base URL your frontend joins each path to. Full media URL = cdnBaseUrl + / + path. |
hls.ready | boolean | true when a playlist exists. May be true while still generating; the playlist is partial and grows. |
hls.playlistPath | string | null | Relative path of the .m3u8 playlist, or null if none exists yet. |
mp4.ready | boolean | true only when the final MP4 is complete. |
mp4.path | string | null | Relative path of the MP4, or null until ready. |
probePath | string | Relative path of the optional cookie-check file. |
8. Get Cookie
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
| Field | Type | Required | Description |
|---|---|---|---|
videoId | string | Yes | The think10xvideoId the ticket was minted for. Must match the ticket. |
Headers
| Header | Set by | Description |
|---|---|---|
ticket | Your frontend code | The pt_ ticket from createPlaybackTicket. |
Origin | Browser (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 theSet-Cookieheaders and every CDN request afterwards fails with403, while thegetCookiestep 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
| Field | Type | Description |
|---|---|---|
ok | boolean | true when the cookies were set. |
expiresAt | number | UTC 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 sendsOriginautomatically; it cannot be faked from page JavaScript. - Cookie expiry: when the cookies expire mid-session, mint a fresh ticket, call
getCookieagain, then resume playback.
Error responses
| HTTP | Code | When |
|---|---|---|
401 | UNAUTHORIZED | Any ticket problem: missing, expired, already used, wrong video, or a non-playback ticket. |
403 | FORBIDDEN_ORIGIN | The page origin is not in the ticket's allowedOrigins. |
500 | INTERNAL_SERVER_ERROR | Unexpected 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
| Field | Type | Required | Description |
|---|---|---|---|
questionhtml | string | Yes | HTML 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
imageBase64andmimeTypeasquestionImageBase64andquestionImageMimeTypeincreateVideo.
Example response
{
"imageBase64": "<base64-image-content>",
"mimeType": "image/png"
}
Response fields
| Field | Type | Description |
|---|---|---|
imageBase64 | string | Raw base64 image content without a data URL prefix. |
mimeType | string | MIME 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
| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code. |
error.message | string | Human-readable summary. |
error.details | array | Optional field-level details. |
error.details[].field | string | Field, header, path parameter, or request area that caused the error. |
error.details[].message | string | Detailed message for that field. |
Common error codes
| HTTP status | Code | Meaning | Retry? |
|---|---|---|---|
400 | INVALID_JSON | Request body is not valid JSON | No |
400 | VALIDATION_ERROR | Missing or malformed request fields, headers, or path parameters | No |
401 | UNAUTHORIZED | Invalid CustomerId or API key | No |
404 | VIDEO_NOT_FOUND | Video does not exist or does not belong to the supplied CustomerId | No |
403 | FORBIDDEN_ORIGIN | Page origin not allowed for the playback ticket (getCookie, Flow C) | No |
429 | RATE_LIMITED | Too many requests | Yes; retry after the indicated delay |
500 | INTERNAL_SERVER_ERROR | Unexpected server error | Yes, with backoff |
For
getCookie(Flow C), any ticket problem — missing, expired, already used, wrong video, or a non-playback (tk_) ticket — returns a single uniform401. 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
Recommended retry behavior
| Situation | Recommended behavior |
|---|---|
| Network timeout | Retry with exponential backoff |
HTTP 500 | Retry with exponential backoff |
HTTP 429 | Retry after the Retry-After value if present |
HTTP 400 | Do not retry without changing the request |
HTTP 401 | Do not retry; check credentials |
VIDEO_NOT_FOUND | Do not retry unless the ID may be stale or incorrect |
| Expired iframe ticket | Mint a new ticket |
| Used iframe ticket | Mint 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-Afterheader 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 area | Guidance |
|---|---|
questionImageBase64 | Recommended decoded image size under 8 MB. Raw base64 only, no data: prefix. |
questionImageMimeType | Must be image/png, image/jpg, or image/jpeg. |
questionContent | HTML is accepted. For complex or image-heavy content, render to image first. |
questionhtml | Must be valid JSON string content. Inline CSS is supported; scripts are ignored. |
externalUserId | Maximum length 256. Prefer stable non-PII IDs. |
allowedParentOrigins | Use exact origins only:scheme://host[:port]. No path, wildcard, or trailing slash. |
| Iframe ticket | Valid for 5 minutes and single-use. Mint immediately before iframe load. |
| Signed media links | Valid for approximately 10 minutes. Download promptly. |
allowedOrigins | Flow C. Use exact origins only: scheme://host[:port]. No path, wildcard, or trailing slash. |
| Playback ticket | Flow C. Valid for 5 minutes and single-use. Mint immediately before calling getCookie. |
| CDN signed cookie | Flow 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 → think10xvideoIdfor non-language integrations. - Store
(questionId, languageKey) → think10xvideoIdfor language-enabled integrations. - Store
createdAt,status, and any relevant metadata for support. - Reuse
think10xvideoIdfor 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
think10xvideoIdandticket. - Do not cache iframe tickets.
- If the iframe fails because of expired or reused ticket, request a new ticket.
- Validate iframe
postMessageevents before closing the host dialog.
Security, Privacy, and Data Handling
Customer responsibilities
The customer backend is responsible for:
- Authenticating the end user.
- Authorizing whether the user is allowed to view or generate a video for the given question.
- Protecting the Think10x.ai API key.
- Preventing unauthenticated public access to backend proxy endpoints.
- Applying customer-side rate limiting and abuse protection.
- 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:
- Render the full question into an image using
converthtmltoimage. - Send the rendered image to
createVideousingquestionImageBase64andquestionImageMimeType. - Include
externalQuestionIdfor 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
CustomerIdthink10xvideoId, 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.