X-Pilot API Reference
Build powerful AI-driven course video workflows with X-Pilot's REST API. Authenticate with Bearer tokens, generate videos, manage async pipelines, and integrate via MCP tools or Skills.
https://api.x-pilot.aiBearer Tokenapplication/jsonOverview
X-Pilot exposes a RESTful HTTP API organized into eight functional groups. All requests require Content-Type: application/json. Protected endpoints require Authorization: Bearer <access_token>.
| Group | Base Path | Description |
|---|---|---|
| Auth & Identity | /auth · /users | Sign-in, sign-up, Google OAuth, user profile |
| Video Projects | /api/video/projects | Create, read, update, delete projects; chat & message history |
| Export & Delivery | /api/video/projects/{id} | Generate audio, export video, share links |
| Async Tasks | /api/tasks | Create, list, poll, regenerate background tasks |
| Knowledge Base | /knowledgeBase | Create KB, list, upload files, list files |
| Files & Parsing | /file · /api/document | Upload, parse documents, convert Word to HTML |
| Voice Models | /api/audio/model | List, create, get, update, delete voice models |
| Payments | /api/payments | Checkout sessions, billing portal |
Quickstart
Go from zero to a running video project in five steps.
- 1
Sign in and obtain an access token
POST to
/auth/sign_inwith your email and password. The response includesaccess_token(short-lived) andrefresh_token. - 2
Create a video project
POST to
/api/video/projectswith a title and optional knowledge base reference. Returns aproject_id. - 3
Send a generation request via chat
POST to
/api/video/projects/{id}/chat-messagedescribing your video content. The AI orchestrator creates async tasks. - 4
Poll until complete
GET
/api/tasks/{task_id}every 3–5 seconds untilstatus === "completed". - 5
Export the video
POST to
/api/video/projects/{id}/export-videoand poll the same URL (GET) until the export URL is ready.
Authentication
X-Pilot uses JWT Bearer tokens. Sign in to receive an access_token; include it as Authorization: Bearer <token> on every protected request. Tokens expire — use the refresh_token to obtain a new access token without re-authenticating.
/auth/sign_in Sign in with email + passwordRequest Body
| Field | Type | Required |
|---|---|---|
email | string | required |
password | string | required |
Response Fields
| Field | Type | Notes |
|---|---|---|
access_token | string | JWT, ~1h expiry |
refresh_token | string | Long-lived |
token_type | string | "bearer" |
expires_in | number | Seconds |
# Sign in and capture the access token
TOKEN=$(curl -s -X POST https://api.x-pilot.ai/auth/sign_in \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"yourpassword"}' \
| jq -r '.access_token')
# Use in subsequent requests
curl -X GET https://api.x-pilot.ai/users/me \
-H "Authorization: Bearer $TOKEN"const res = await fetch('https://api.x-pilot.ai/auth/sign_in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: '[email protected]', password: 'yourpassword' })
});
const { access_token, refresh_token, expires_in } = await res.json();
// Store tokens securely
localStorage.setItem('xpilot_token', access_token);
// Helper for authenticated requests
const api = (path, opts = {}) =>
fetch(`https://api.x-pilot.ai${path}`, {
...opts,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('xpilot_token')}`,
...opts.headers
}
}).then(r => r.json());import requests
BASE = 'https://api.x-pilot.ai'
resp = requests.post(f'{BASE}/auth/sign_in', json={
'email': '[email protected]',
'password': 'yourpassword'
})
data = resp.json()
access_token = data['access_token']
refresh_token = data['refresh_token']
# Session helper
session = requests.Session()
session.headers.update({
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
})/auth/sign_up Create a new account| Field | Type | Required | Notes |
|---|---|---|---|
email | string | required | Must be valid email format |
password | string | required | Min 8 characters |
name | string | optional | Display name |
invite_code | string | optional | Referral or invite code |
curl -X POST https://api.x-pilot.ai/auth/sign_up \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"securepass","name":"Jane Dev"}'const { access_token } = await api('/auth/sign_up', {
method: 'POST',
body: JSON.stringify({
email: '[email protected]',
password: 'securepass',
name: 'Jane Dev'
})
});resp = requests.post(f'{BASE}/auth/sign_up', json={
'email': '[email protected]',
'password': 'securepass',
'name': 'Jane Dev'
})/api/auth/google Exchange Google ID token for X-Pilot token| Field | Type | Required | Notes |
|---|---|---|---|
token | string | required | Google Sign-In ID token (from credential callback) |
// In Google Sign-In callback handler
function handleCredentialResponse(response) {
const googleToken = response.credential;
fetch('https://api.x-pilot.ai/api/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: googleToken })
})
.then(r => r.json())
.then(({ access_token }) => {
localStorage.setItem('xpilot_token', access_token);
});
}resp = session.post(f'{BASE}/api/auth/google', json={
'token': google_id_token
})
access_token = resp.json()['access_token']/users/me Get current authenticated userNo request body. Returns the authenticated user's profile including subscription tier, usage stats, and preferences.
| Response Field | Type | Description |
|---|---|---|
id | string | User UUID |
email | string | User email |
name | string | Display name |
plan | string | "free" | "pro" | "team" | "enterprise" |
credits | number | Remaining generation credits |
created_at | string | ISO 8601 timestamp |
curl https://api.x-pilot.ai/users/me \
-H "Authorization: Bearer $TOKEN"const user = await api('/users/me');
console.log(`Plan: ${user.plan}, Credits: ${user.credits}`);user = session.get(f'{BASE}/users/me').json()
print(f'Plan: {user["plan"]}, Credits: {user["credits"]}')Video Projects
Video projects are the core resource. Each project holds a conversation history with the AI, rendering configuration, and generated video assets. Projects are scoped to the authenticated user.
/api/video/projects List all projects| Query Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Items per page (max 100) |
sort | string | "updated_at" | "created_at" | "updated_at" | "title" |
curl "https://api.x-pilot.ai/api/video/projects?page=1&limit=20" \
-H "Authorization: Bearer $TOKEN"const { data, total, page } = await api('/api/video/projects?page=1&limit=20');projects = session.get(f'{BASE}/api/video/projects', params={'page': 1, 'limit': 20}).json()/api/video/projects Create a new video project| Field | Type | Required | Description |
|---|---|---|---|
title | string | required | Project display title |
knowledge_base_id | string | optional | Attach an existing KB for context |
voice_model_id | string | optional | Default voice model UUID |
language | string | optional | ISO language code, default "en" |
curl -X POST https://api.x-pilot.ai/api/video/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Python Async Course","language":"en"}'const project = await api('/api/video/projects', {
method: 'POST',
body: JSON.stringify({
title: 'Python Async Course',
language: 'en'
})
});
const projectId = project.id;project = session.post(f'{BASE}/api/video/projects', json={
'title': 'Python Async Course',
'language': 'en'
}).json()
project_id = project['id']/api/video/projects/{id} Get project detailscurl https://api.x-pilot.ai/api/video/projects/proj_abc123 \
-H "Authorization: Bearer $TOKEN"const project = await api(`/api/video/projects/${projectId}`);/api/video/projects/{id}/chat-message Send AI generation instructionThis is the primary generation trigger. Describe the video content you want; the AI orchestrator will plan and queue async tasks. Returns a task_id for polling.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | required | Natural language instruction for the video AI |
context_files | string[] | optional | File IDs to include as generation context |
mode | string | optional | "generate" (default) | "refine" | "regenerate" |
curl -X POST https://api.x-pilot.ai/api/video/projects/proj_abc123/chat-message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Create a 5-slide explainer on Python async/await with code examples and animations"}'const { task_id, message_id } = await api(
`/api/video/projects/${projectId}/chat-message`,
{
method: 'POST',
body: JSON.stringify({
message: 'Create a 5-slide explainer on Python async/await with code examples and animations',
mode: 'generate'
})
}
);resp = session.post(
f'{BASE}/api/video/projects/{project_id}/chat-message',
json={
'message': 'Create a 5-slide explainer on Python async/await',
'mode': 'generate'
}
).json()
task_id = resp['task_id']/api/video/projects/{id}/messages Get conversation historycurl "https://api.x-pilot.ai/api/video/projects/proj_abc123/messages?limit=50" \
-H "Authorization: Bearer $TOKEN"const { messages } = await api(`/api/video/projects/${projectId}/messages?limit=50`);Export & Delivery
Export endpoints trigger rendering jobs. Both audio generation and video export are asynchronous — POST to start, then GET the same URL to poll for completion.
/api/video/projects/{id}/generate-audio Trigger audio generationTriggers TTS rendering for all slides in the project. Poll via GET on the same path.
| Field | Type | Required | Description |
|---|---|---|---|
voice_model_id | string | optional | Override default voice |
speed | number | optional | Playback speed 0.5–2.0 (default 1.0) |
slide_ids | string[] | optional | Regenerate specific slides only |
# Trigger
curl -X POST https://api.x-pilot.ai/api/video/projects/proj_abc123/generate-audio \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"speed":1.1}'
# Poll
curl https://api.x-pilot.ai/api/video/projects/proj_abc123/generate-audio \
-H "Authorization: Bearer $TOKEN"// Trigger
await api(`/api/video/projects/${projectId}/generate-audio`, {
method: 'POST', body: JSON.stringify({ speed: 1.1 })
});
// Poll helper
async function pollAudio(projectId, interval = 3000) {
while (true) {
const status = await api(`/api/video/projects/${projectId}/generate-audio`);
if (status.state === 'completed') return status;
if (status.state === 'failed') throw new Error(status.error);
await new Promise(r => setTimeout(r, interval));
}
}/api/video/projects/{id}/export-video Trigger video export (returns job ID)| Field | Type | Default | Description |
|---|---|---|---|
resolution | string | "1080p" | "720p" | "1080p" | "4k" |
format | string | "mp4" | "mp4" | "webm" |
fps | number | 30 | 24 | 30 | 60 |
include_captions | boolean | false | Burn-in subtitles |
| Response Field | Description |
|---|---|
job_id | Export job identifier for polling |
state | "queued" | "rendering" | "completed" | "failed" |
download_url | Pre-signed download URL (present when completed) |
expires_at | ISO timestamp for download URL expiry |
# 1. Trigger export
JOB=$(curl -s -X POST https://api.x-pilot.ai/api/video/projects/proj_abc123/export-video \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"resolution":"1080p","fps":30}' | jq -r '.job_id')
# 2. Poll until done
while true; do
RESULT=$(curl -s https://api.x-pilot.ai/api/video/projects/proj_abc123/export-video \
-H "Authorization: Bearer $TOKEN")
STATE=$(echo $RESULT | jq -r '.state')
[ "$STATE" = "completed" ] && echo $RESULT | jq -r '.download_url' && break
sleep 5
doneasync function exportAndDownload(projectId) {
// Trigger
await api(`/api/video/projects/${projectId}/export-video`, {
method: 'POST',
body: JSON.stringify({ resolution: '1080p', fps: 30, include_captions: false })
});
// Poll with exponential backoff
let delay = 3000;
while (true) {
const job = await api(`/api/video/projects/${projectId}/export-video`);
if (job.state === 'completed') return job.download_url;
if (job.state === 'failed') throw new Error(`Export failed: ${job.error}`);
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 1.5, 15000); // max 15s
}
}import time
def export_video(project_id, resolution='1080p'):
url = f'{BASE}/api/video/projects/{project_id}/export-video'
# Trigger
session.post(url, json={'resolution': resolution, 'fps': 30})
# Poll
delay = 3
while True:
job = session.get(url).json()
if job['state'] == 'completed':
return job['download_url']
if job['state'] == 'failed':
raise RuntimeError(job.get('error'))
time.sleep(delay)
delay = min(delay * 1.5, 15)/api/v1/user_case/share-simple Create a shareable link| Field | Type | Required |
|---|---|---|
project_id | string | required |
expires_hours | number | optional (default 168 = 7 days) |
password | string | optional |
curl -X POST https://api.x-pilot.ai/api/v1/user_case/share-simple \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_id":"proj_abc123","expires_hours":72}'const { share_url } = await api('/api/v1/user_case/share-simple', {
method: 'POST',
body: JSON.stringify({ project_id: projectId, expires_hours: 72 })
});Async Task Pipeline
Long-running operations (script generation, code rendering, audio synthesis) run as async tasks. Each task transitions through states: queued → running → completed | failed. Poll at 3–5 second intervals.
/api/tasks/create Manually create a task| Field | Type | Required | Description |
|---|---|---|---|
project_id | string | required | Parent project UUID |
type | string | required | "script_gen" | "code_render" | "audio_sync" | "full_pipeline" |
payload | object | optional | Type-specific configuration |
priority | number | optional | 1–10 (default 5, higher = sooner) |
curl -X POST https://api.x-pilot.ai/api/tasks/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_id":"proj_abc123","type":"full_pipeline","priority":8}'const task = await api('/api/tasks/create', {
method: 'POST',
body: JSON.stringify({
project_id: projectId,
type: 'full_pipeline',
priority: 8
})
});task = session.post(f'{BASE}/api/tasks/create', json={
'project_id': project_id,
'type': 'full_pipeline',
'priority': 8
}).json()/api/tasks/{task_id} Poll task status| Response Field | Type | Description |
|---|---|---|
id | string | Task UUID |
status | string | "queued" | "running" | "completed" | "failed" |
progress | number | 0–100 percentage |
result | object | Output data (present when completed) |
error | string | Error message (present when failed) |
created_at | string | ISO timestamp |
completed_at | string | ISO timestamp (when done) |
curl https://api.x-pilot.ai/api/tasks/task_xyz789 \
-H "Authorization: Bearer $TOKEN"// Polling with exponential backoff
async function waitForTask(taskId, onProgress) {
let delay = 3000, maxDelay = 20000;
while (true) {
const task = await api(`/api/tasks/${taskId}`);
onProgress?.(task.progress, task.status);
if (task.status === 'completed') return task.result;
if (task.status === 'failed') throw new Error(task.error);
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 1.4, maxDelay);
}
}def wait_for_task(task_id, poll_interval=3):
delay = poll_interval
while True:
task = session.get(f'{BASE}/api/tasks/{task_id}').json()
print(f'Task {task["status"]}: {task.get("progress", 0)}%')
if task['status'] == 'completed':
return task['result']
if task['status'] == 'failed':
raise RuntimeError(task.get('error'))
time.sleep(delay)
delay = min(delay * 1.4, 20)/api/tasks/regenerate Retry a failed or specific task| Field | Type | Required |
|---|---|---|
task_id | string | required |
override_payload | object | optional — override original task params |
curl -X POST https://api.x-pilot.ai/api/tasks/regenerate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"task_id":"task_xyz789"}'const newTask = await api('/api/tasks/regenerate', {
method: 'POST', body: JSON.stringify({ task_id: taskId })
});Knowledge Base
Knowledge bases store course-specific reference documents that guide the AI during generation. Attach a KB to a project to improve accuracy and context relevance.
/knowledgeBase/create Create a new knowledge base| Field | Type | Required |
|---|---|---|
name | string | required |
description | string | optional |
tags | string[] | optional |
curl -X POST https://api.x-pilot.ai/knowledgeBase/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Python Fundamentals","description":"Core Python language concepts","tags":["python","programming"]}'const kb = await api('/knowledgeBase/create', {
method: 'POST',
body: JSON.stringify({ name: 'Python Fundamentals', tags: ['python'] })
});/knowledgeBase/file/upload/single Upload a file to a knowledge baseMultipart form upload. Supported formats: PDF, DOCX, PPTX, TXT, MD. Max file size: 50MB.
| Form Field | Type | Required |
|---|---|---|
file | File (binary) | required |
knowledge_base_id | string | required |
name | string | optional (defaults to filename) |
curl -X POST https://api.x-pilot.ai/knowledgeBase/file/upload/single \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/course-notes.pdf" \
-F "knowledge_base_id=kb_abc123"const form = new FormData();
form.append('file', fileBlob, 'course-notes.pdf');
form.append('knowledge_base_id', kbId);
const result = await fetch('https://api.x-pilot.ai/knowledgeBase/file/upload/single', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: form
}).then(r => r.json());with open('course-notes.pdf', 'rb') as f:
resp = session.post(
f'{BASE}/knowledgeBase/file/upload/single',
files={'file': ('course-notes.pdf', f, 'application/pdf')},
data={'knowledge_base_id': kb_id}
).json()Files & Parsing
Upload raw files, parse documents into structured content, or convert Word documents to HTML for downstream processing.
/file/upload Upload a file for use across the platformcurl -X POST https://api.x-pilot.ai/file/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/slides.pptx"const form = new FormData();
form.append('file', file);
const { file_id, url } = await fetch('https://api.x-pilot.ai/file/upload', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: form
}).then(r => r.json());/api/document/parse/multiple Parse documents into structured text| Field | Type | Required |
|---|---|---|
file_ids | string[] | required |
extract_images | boolean | optional (default false) |
output_format | string | "markdown" (default) | "json" | "plain" |
curl -X POST https://api.x-pilot.ai/api/document/parse/multiple \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_ids":["file_abc","file_def"],"output_format":"markdown"}'const { documents } = await api('/api/document/parse/multiple', {
method: 'POST',
body: JSON.stringify({ file_ids: [fileId], output_format: 'markdown' })
});Voice Models
Manage custom and built-in voice models for narration. Clone voices by uploading reference audio, then assign a voice model to any project or generation task.
/api/audio/model/list List available voice modelscurl https://api.x-pilot.ai/api/audio/model/list \
-H "Authorization: Bearer $TOKEN"const { models } = await api('/api/audio/model/list');
// model: { id, name, language, gender, type: "builtin"|"custom" }/api/audio/model Create / clone a custom voice| Form Field | Type | Required | Notes |
|---|---|---|---|
name | string | required | Display name |
reference_audio | File (binary) | required | WAV or MP3, 30s–10min, clear voice |
language | string | optional | ISO language code (auto-detected if omitted) |
description | string | optional | Internal notes |
curl -X POST https://api.x-pilot.ai/api/audio/model \
-H "Authorization: Bearer $TOKEN" \
-F "name=My Lecturer Voice" \
-F "reference_audio=@/path/to/sample.wav" \
-F "language=en"const form = new FormData();
form.append('name', 'My Lecturer Voice');
form.append('reference_audio', audioFile);
form.append('language', 'en');
const model = await fetch('https://api.x-pilot.ai/api/audio/model', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: form
}).then(r => r.json());with open('sample.wav', 'rb') as f:
model = session.post(
f'{BASE}/api/audio/model',
files={'reference_audio': ('sample.wav', f, 'audio/wav')},
data={'name': 'My Lecturer Voice', 'language': 'en'}
).json()/api/audio/model/{model_id} · DELETE /api/audio/model/{model_id}Update a voice model's metadata or permanently delete it. Deleting a model removes it from all future generations.
# Update name
curl -X PUT https://api.x-pilot.ai/api/audio/model/vm_abc123 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Updated Voice Name"}'
# Delete
curl -X DELETE https://api.x-pilot.ai/api/audio/model/vm_abc123 \
-H "Authorization: Bearer $TOKEN"// Update
await api(`/api/audio/model/${modelId}`, {
method: 'PUT', body: JSON.stringify({ name: 'Updated Voice' })
});
// Delete
await api(`/api/audio/model/${modelId}`, { method: 'DELETE' });Payments
Manage subscriptions and billing via Stripe-backed endpoints. Redirect users to the returned URL to complete the checkout or manage their billing portal.
/api/payments/create-checkout-session Create a Stripe checkout session| Field | Type | Required | Notes |
|---|---|---|---|
price_id | string | required | Stripe price ID (e.g. "price_pro_monthly") |
success_url | string | required | Redirect after payment success |
cancel_url | string | required | Redirect on cancellation |
curl -X POST https://api.x-pilot.ai/api/payments/create-checkout-session \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"price_id":"price_pro_monthly","success_url":"https://yourapp.com/success","cancel_url":"https://yourapp.com/cancel"}'const { checkout_url } = await api('/api/payments/create-checkout-session', {
method: 'POST',
body: JSON.stringify({
price_id: 'price_pro_monthly',
success_url: `${location.origin}/success`,
cancel_url: `${location.origin}/price`
})
});
window.location.href = checkout_url;/api/payments/billing-portal Open Stripe customer billing portalcurl -X POST https://api.x-pilot.ai/api/payments/billing-portal \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"return_url":"https://yourapp.com/settings"}'const { portal_url } = await api('/api/payments/billing-portal', {
method: 'POST',
body: JSON.stringify({ return_url: `${location.origin}/settings` })
});
window.location.href = portal_url;End-to-End Workflows
Complete, runnable code that chains multiple API calls into production-ready workflows.
Document → Video Full Pipeline
Upload a PDF/PPTX, create a project, trigger generation, wait for completion, then export and download.
const BASE = 'https://api.x-pilot.ai';
async function documentToVideo({ email, password, filePath, projectTitle, prompt }) {
// 1. Authenticate
const { access_token } = await fetch(`${BASE}/auth/sign_in`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
}).then(r => r.json());
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${access_token}`
};
const api = (path, opts = {}) =>
fetch(`${BASE}${path}`, { ...opts, headers: { ...headers, ...opts.headers } }).then(r => r.json());
// 2. Upload source document
const fileForm = new FormData();
fileForm.append('file', await fetch(filePath).then(r => r.blob()), 'source.pdf');
const { file_id } = await fetch(`${BASE}/file/upload`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${access_token}` },
body: fileForm
}).then(r => r.json());
// 3. Parse document
const { documents } = await api('/api/document/parse/multiple', {
method: 'POST', body: JSON.stringify({ file_ids: [file_id], output_format: 'markdown' })
});
// 4. Create project
const { id: projectId } = await api('/api/video/projects', {
method: 'POST', body: JSON.stringify({ title: projectTitle })
});
// 5. Trigger generation
const { task_id } = await api(`/api/video/projects/${projectId}/chat-message`, {
method: 'POST',
body: JSON.stringify({ message: prompt, context_files: [file_id] })
});
// 6. Poll until complete
let delay = 3000;
while (true) {
const task = await api(`/api/tasks/${task_id}`);
console.log(`[${task.status}] ${task.progress ?? 0}%`);
if (task.status === 'completed') break;
if (task.status === 'failed') throw new Error(`Task failed: ${task.error}`);
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 1.4, 15000);
}
// 7. Export video
await api(`/api/video/projects/${projectId}/export-video`, {
method: 'POST', body: JSON.stringify({ resolution: '1080p' })
});
delay = 5000;
while (true) {
const job = await api(`/api/video/projects/${projectId}/export-video`);
if (job.state === 'completed') return job.download_url;
if (job.state === 'failed') throw new Error(`Export failed: ${job.error}`);
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 1.5, 20000);
}
}
// Usage
documentToVideo({
email: '[email protected]',
password: 'yourpassword',
filePath: './course-notes.pdf',
projectTitle: 'Python Async Course',
prompt: 'Create a comprehensive 8-slide video on Python async/await with animated code examples'
}).then(url => console.log('Download:', url));import requests, time
BASE = 'https://api.x-pilot.ai'
def document_to_video(email, password, file_path, project_title, prompt):
# 1. Auth
data = requests.post(f'{BASE}/auth/sign_in', json={'email': email, 'password': password}).json()
s = requests.Session()
s.headers.update({
'Authorization': f'Bearer {data["access_token"]}',
'Content-Type': 'application/json'
})
# 2. Upload file
with open(file_path, 'rb') as f:
file_id = s.post(f'{BASE}/file/upload',
files={'file': f}, headers={'Content-Type': None}).json()['file_id']
# 3. Create project
project_id = s.post(f'{BASE}/api/video/projects', json={'title': project_title}).json()['id']
# 4. Trigger generation
task_id = s.post(f'{BASE}/api/video/projects/{project_id}/chat-message',
json={'message': prompt, 'context_files': [file_id]}).json()['task_id']
# 5. Poll generation
delay = 3
while True:
task = s.get(f'{BASE}/api/tasks/{task_id}').json()
print(f'[{task["status"]}] {task.get("progress", 0)}%')
if task['status'] == 'completed': break
if task['status'] == 'failed': raise RuntimeError(task.get('error'))
time.sleep(delay); delay = min(delay * 1.4, 15)
# 6. Export
url = f'{BASE}/api/video/projects/{project_id}/export-video'
s.post(url, json={'resolution': '1080p'})
delay = 5
while True:
job = s.get(url).json()
if job['state'] == 'completed': return job['download_url']
if job['state'] == 'failed': raise RuntimeError(job.get('error'))
time.sleep(delay); delay = min(delay * 1.5, 20)
# Run it
download_url = document_to_video(
email='[email protected]', password='yourpassword',
file_path='course-notes.pdf', project_title='Python Async Course',
prompt='Create a comprehensive 8-slide video on Python async/await'
)
print('Download:', download_url)MCP Integration
X-Pilot exposes its API surface as Model Context Protocol (MCP) tools. Each tool maps 1:1 to a REST endpoint, enabling AI agents (Claude, GPT, etc.) to call X-Pilot natively. Below are the complete JSON Schema definitions for six core MCP tools.
// MCP Tool Manifest — x-pilot-api/v2
const tools = [
{
name: 'xpilot_create_project',
description: 'Create a new X-Pilot video project. Returns a project ID for downstream operations.',
inputSchema: {
type: 'object',
required: ['title'],
properties: {
title: { type: 'string', description: 'Project title' },
knowledge_base_id: { type: 'string', description: 'Optional KB ID to attach' },
language: { type: 'string', default: 'en', description: 'ISO language code' }
}
}
},
{
name: 'xpilot_generate_video',
description: 'Send an AI generation instruction to a project and return the async task ID.',
inputSchema: {
type: 'object',
required: ['project_id', 'message'],
properties: {
project_id: { type: 'string', description: 'Target project UUID' },
message: { type: 'string', description: 'Natural language video generation instruction' },
context_files: {
type: 'array',
items: { type: 'string' },
description: 'Optional file IDs for generation context'
},
mode: {
type: 'string',
enum: ['generate', 'refine', 'regenerate'],
default: 'generate'
}
}
}
},
{
name: 'xpilot_poll_task',
description: 'Poll the status of an async task. Returns status, progress (0-100), and result when done.',
inputSchema: {
type: 'object',
required: ['task_id'],
properties: {
task_id: { type: 'string', description: 'Task UUID returned by generate or create endpoints' }
}
}
},
{
name: 'xpilot_export_video',
description: 'Trigger video export for a project. Returns job status and download URL when complete.',
inputSchema: {
type: 'object',
required: ['project_id'],
properties: {
project_id: { type: 'string' },
resolution: {
type: 'string',
enum: ['720p', '1080p', '4k'],
default: '1080p'
},
format: { type: 'string', enum: ['mp4', 'webm'], default: 'mp4' },
include_captions: { type: 'boolean', default: false }
}
}
},
{
name: 'xpilot_upload_to_kb',
description: 'Upload a document file to a knowledge base for use as generation context.',
inputSchema: {
type: 'object',
required: ['knowledge_base_id', 'file_content_base64', 'filename'],
properties: {
knowledge_base_id: { type: 'string' },
file_content_base64: { type: 'string', description: 'Base64-encoded file content' },
filename: { type: 'string', description: 'Original filename with extension' },
mime_type: { type: 'string', default: 'application/pdf' }
}
}
},
{
name: 'xpilot_list_voice_models',
description: 'List all available voice models (built-in and custom). Returns IDs for use in project/generation settings.',
inputSchema: {
type: 'object',
properties: {
type_filter: {
type: 'string',
enum: ['all', 'builtin', 'custom'],
default: 'all'
},
language_filter: { type: 'string', description: 'ISO language code filter' }
}
}
}
];REST-to-MCP Bridge Pattern: Each MCP tool handler should call the corresponding REST endpoint with the user's Bearer token, handle errors by returning them in the MCP error field, and for async tools (xpilot_generate_video, xpilot_export_video), implement polling internally and return the final result to the AI agent only when complete.
Skills Patterns
Skills are higher-level abstractions that encapsulate multi-step X-Pilot workflows for use in AI agent frameworks. Each Skill class wraps several API calls into a single callable interface.
VideoGenerationSkill
/**
* VideoGenerationSkill — orchestrates the full project → generate → export pipeline
*/
class VideoGenerationSkill {
constructor(apiClient) {
this.api = apiClient; // authenticated API client instance
}
/** @param {object} params - {title, prompt, resolution, contextFileIds} */
async execute({ title, prompt, resolution = '1080p', contextFileIds = [] }) {
// Step 1: Create project
const { id: projectId } = await this.api.post('/api/video/projects', { title });
// Step 2: Generate
const { task_id } = await this.api.post(`/api/video/projects/${projectId}/chat-message`, {
message: prompt, context_files: contextFileIds
});
// Step 3: Await generation
await this._waitForTask(task_id);
// Step 4: Export
return this._exportAndWait(projectId, resolution);
}
async _waitForTask(taskId, maxWait = 600000) {
const start = Date.now(); let delay = 3000;
while (Date.now() - start < maxWait) {
const t = await this.api.get(`/api/tasks/${taskId}`);
if (t.status === 'completed') return t.result;
if (t.status === 'failed') throw new Error(`Task ${taskId} failed: ${t.error}`);
await sleep(delay); delay = Math.min(delay * 1.4, 15000);
}
throw new Error('Task timed out');
}
async _exportAndWait(projectId, resolution) {
const url = `/api/video/projects/${projectId}/export-video`;
await this.api.post(url, { resolution });
let delay = 5000;
while (true) {
const job = await this.api.get(url);
if (job.state === 'completed') return { projectId, downloadUrl: job.download_url };
if (job.state === 'failed') throw new Error(job.error);
await sleep(delay); delay = Math.min(delay * 1.5, 20000);
}
}
}
// Usage
const skill = new VideoGenerationSkill(authenticatedApiClient);
const { downloadUrl } = await skill.execute({
title: 'React Hooks Deep Dive',
prompt: 'Create a 6-slide animated explainer covering useState, useEffect, useMemo, useCallback',
resolution: '1080p'
});DocumentToVideoSkill
class DocumentToVideoSkill {
constructor(apiClient) { this.api = apiClient; }
async execute({ file, projectTitle, generationPrompt, resolution = '1080p' }) {
// 1. Upload document
const form = new FormData(); form.append('file', file);
const { file_id } = await this.api.postForm('/file/upload', form);
// 2. Parse it
const { documents } = await this.api.post('/api/document/parse/multiple', {
file_ids: [file_id], output_format: 'markdown'
});
// 3. Delegate to VideoGenerationSkill
const vg = new VideoGenerationSkill(this.api);
return vg.execute({
title: projectTitle,
prompt: `${generationPrompt}
Source document (parsed):
${documents[0].content}`,
contextFileIds: [file_id],
resolution
});
}
}VoicePersonalizationSkill
class VoicePersonalizationSkill {
constructor(apiClient) { this.api = apiClient; }
/** Clone voice from sample, then apply to project audio generation */
async execute({ voiceName, sampleAudioFile, projectId, speed = 1.0 }) {
// 1. Clone voice
const form = new FormData();
form.append('name', voiceName);
form.append('reference_audio', sampleAudioFile);
const { id: voiceModelId } = await this.api.postForm('/api/audio/model', form);
// 2. Generate audio with custom voice
await this.api.post(`/api/video/projects/${projectId}/generate-audio`, {
voice_model_id: voiceModelId, speed
});
// 3. Poll audio generation
let delay = 3000;
while (true) {
const s = await this.api.get(`/api/video/projects/${projectId}/generate-audio`);
if (s.state === 'completed') return { voiceModelId, projectId };
if (s.state === 'failed') throw new Error(s.error);
await sleep(delay); delay = Math.min(delay * 1.4, 10000);
}
}
}Error Codes
All errors follow a consistent structure: HTTP status code + JSON body with error (machine-readable code) and message (human-readable explanation). Business-logic errors always return HTTP 200 with an error body.
// Error response structure
{
"error": "ERR_INVALID_TOKEN",
"message": "The provided access token has expired.",
"status": 401,
"request_id": "req_8f2a19c3" // Include in support tickets
}| HTTP Status | Error Code | Meaning | Action |
|---|---|---|---|
| 200 | SUCCESS | Request succeeded | — |
| 400 | ERR_VALIDATION | Missing or invalid request fields | Check request body against schema |
| 400 | ERR_FILE_TYPE | Unsupported file format | Use PDF, DOCX, PPTX, TXT, or MD |
| 400 | ERR_FILE_TOO_LARGE | File exceeds 50MB limit | Compress or split the file |
| 401 | ERR_UNAUTHORIZED | No Authorization header | Include Bearer <token> header |
| 401 | ERR_INVALID_TOKEN | Token invalid or malformed | Re-authenticate via /auth/sign_in |
| 401 | ERR_TOKEN_EXPIRED | Access token has expired | Use refresh_token to get a new access_token |
| 403 | ERR_FORBIDDEN | Resource belongs to another user | Verify you own the requested resource |
| 403 | ERR_PLAN_LIMIT | Action exceeds your plan quota | Upgrade plan or wait for quota reset |
| 404 | ERR_NOT_FOUND | Resource does not exist | Verify the ID is correct |
| 409 | ERR_CONFLICT | Duplicate resource or concurrent modification | Retry with exponential backoff |
| 422 | ERR_CREDITS_EXHAUSTED | No generation credits remaining | Purchase credits or upgrade plan |
| 429 | ERR_RATE_LIMIT | Too many requests (60 req/min default) | Implement exponential backoff; check Retry-After header |
| 500 | ERR_INTERNAL | Unexpected server error | Retry once; if persists, contact support with request_id |
| 503 | ERR_SERVICE_UNAVAILABLE | Downstream AI service overloaded | Retry with exponential backoff (up to 3×) |
Rate Limits
| Plan | Requests/min | Concurrent Exports | Monthly Generations |
|---|---|---|---|
| Free | 20 | 1 | 10 |
| Pro | 60 | 3 | 100 |
| Team | 200 | 10 | Unlimited |
| Enterprise | Custom | Custom | Unlimited |
FAQ
How do I handle token refresh?
Access tokens expire after ~1 hour. When you receive a 401 ERR_TOKEN_EXPIRED response, POST to /auth/refresh with your refresh_token:
curl -X POST https://api.x-pilot.ai/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"your_refresh_token"}'How long does video generation take?
Generation time varies by complexity: a 5-slide script typically takes 30–90 seconds. Full rendering (audio + export) adds 2–5 minutes per minute of video at 1080p. Implement polling with 3–5 second intervals and exponential backoff up to 15 seconds.
What file formats are supported for document upload?
Supported: PDF, DOCX, PPTX, TXT, MD, HTML. Maximum file size is 50MB per file. For large documents, we recommend splitting by chapter or section for better generation quality.
Can I use the API without a credit card?
Yes. Free plan accounts include 10 video generations per month. Sign up at app.x-pilot.ai, then use your email/password to authenticate via the API.
How do I get my download URL after export?
After triggering export (POST), poll GET on the same endpoint (/api/video/projects/{'{id}'}/export-video) until state === "completed". The response will include download_url (pre-signed S3 URL) and expires_at. Download links expire after 24 hours.
Is there a webhook alternative to polling?
Webhook support is on the roadmap for Team and Enterprise plans. Currently, the recommended pattern is polling with exponential backoff (3s initial delay, max 15s, timeout at 10 minutes). Join our developer newsletter to be notified when webhooks launch.
Ready to build with X-Pilot?
Start with a free account — 10 video generations per month, no credit card required. Access the full API within minutes.
Need enterprise support or higher rate limits? [email protected].