videoseed API & MCP
From minting a key to fetching analysis text and a verified processed MP4 link. The API and MCP share the web app’s credits, queue, and history. We charge actual duration only on success.
5-minute quick start
This Node.js sample creates a link analysis job, polls every 3 seconds, and prints the result. Requires Node.js 18+.
Sign in and create an API Key
Open Account → API keys, name the key and create it. The secret is shown once — save it immediately.
Export an environment variable
export VIDEOSEED_API_KEY='YOUR_API_KEY'
Run the sample
Replace the Douyin URL with a real video, save as analyze.mjs, then run node analyze.mjs.
const API_KEY = process.env.VIDEOSEED_API_KEY;
const BASE_URL = "https://www.videoseed.cc";
const created = await fetch(`${BASE_URL}/api/v1/analyze`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://www.douyin.com/video/VIDEO_ID",
}),
});
if (!created.ok) throw new Error(await created.text());
const job = await created.json();
while (true) {
await new Promise((resolve) => setTimeout(resolve, 3000));
const response = await fetch(`${BASE_URL}/api/v1/jobs/${job.id}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!response.ok) throw new Error(await response.text());
const current = await response.json();
if (current.status === "done") {
console.log(current.result);
break;
}
if (current.status === "error" || current.status === "cancelled") {
throw new Error(current.error_code ?? "ANALYSIS_FAILED");
}
}Create and manage API Keys
An API Key represents your account. Do not put it in front-end code, public repos, or logs. Revoke and mint a new one if it leaks. Web session cookies cannot call /api/v1/*; API Keys cannot call web /api/*. Key management lives on the Account page.
- Sign in to the web app.
- Create a key under Account → API keys and narrow scopes if needed.
- Copy the secret once and store it in server env.
You will be asked to sign in if needed; create and revoke happen on the Account page.
HTTP API
All business endpoints use Authorization: Bearer YOUR_API_KEY. Requests and responses are JSON except the presigned upload URL.
Analyze a video link
Supports Douyin, Xiaohongshu, and Bilibili pages/short links. For other platforms, download first and use the upload flow.
curl -X POST 'https://www.videoseed.cc/api/v1/analyze' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"url":"https://www.douyin.com/video/VIDEO_ID"}'Response: 202 Accepted
{
"id": "c9aa82c5-79ac-4cb8-9a91-9223dc83b6b8",
"status": "queued",
"queued": true
}Upload and analyze a local file
MP4, MOV, WebM, M4V. Max 1024 MB; files over ~300 MB must be ≤ 30 minutes. Bytes go straight to object storage.
# 1. Presigned upload URL
curl -X POST 'https://www.videoseed.cc/api/v1/uploads' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"filename":"video.mp4","size_bytes":12345678}'
# 2. PUT the file to upload_url
curl -X PUT --upload-file './video.mp4' 'UPLOAD_URL'
# 3. Analyze with upload_key
curl -X POST 'https://www.videoseed.cc/api/v1/analyze' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"upload_key":"UPLOAD_KEY","title":"video.mp4"}'Upload address response: 201 Created
{
"upload_key": "uploads/USER_ID/FILE_ID.mp4",
"upload_url": "https://storage.example.com/...",
"method": "PUT",
"expires_in": 1800,
"max_bytes": 1073741824
}Optional dims
Defaults apply when omitted. Pass a full dims object only when you need to steer output.
| Field | Values | Default |
|---|---|---|
| model | flagship, gemini, doubao, gpt | gemini |
| videoType | general, short, dance, ecommerce, script, drama, anime | general |
| targetModel | allRef, firstFrame, firstFrameRef | allRef |
| beatMode | dialogue, action | dialogue |
| maxSegmentSec | integer seconds 5–120 (web UI: 15 / 30 / custom) | 15 |
Endpoint list
| Method | Path | |
|---|---|---|
| POST | /api/v1/analyze | submit url or upload_key |
| POST | /api/v1/uploads | presigned PUT |
| GET | /api/v1/jobs/{id} | status and result |
| GET | /api/v1/jobs/{id}/video-link | play / download |
| GET | /api/v1/jobs | history (cursor) |
| POST | /api/v1/analyses/batch | batch ≤8 |
| GET | /api/v1/capabilities | limits |
| GET | /api/v1/credits | credits |
| GET | /api/v1/usage | usage ledger |
| POST | /api/v1/jobs/{id}/retry | retry |
| POST | /api/v1/jobs/{id}/cancel | cancel |
| POST / GET / DELETE | /api/v1/webhooks… | async notifications |
| POST | /api/mcp | MCP JSON-RPC |
Credits
curl 'https://www.videoseed.cc/api/v1/credits' \ -H 'Authorization: Bearer YOUR_API_KEY'
{
"balanceSec": 1800,
"isPro": true,
"activePacks": 1
}Async jobs and results
Keep the returned id. Poll every 3 seconds; stop on done, error, or cancelled. Client-side wait of ~30 minutes is a reasonable upper bound.
curl 'https://www.videoseed.cc/api/v1/jobs/JOB_ID' \ -H 'Authorization: Bearer YOUR_API_KEY'
| status | Meaning |
|---|---|
| queued | In queue |
| analyzing | Parsing, ASR, generating result |
| done | Success; result set; charged actual seconds |
| error | Failed; no charge; error_code set |
| cancelled | Cancelled; unused reservation released |
Success body
{
"id": "c9aa82c5-79ac-4cb8-9a91-9223dc83b6b8",
"status": "done",
"source_kind": "link",
"title": "Sample",
"result": { "text": "Full analysis text" },
"error_code": null,
"charged_seconds": 15,
"created_at": "2026-07-29T08:00:00.000Z",
"updated_at": "2026-07-29T08:02:10.000Z"
}Error body
{
"id": "c9aa82c5-79ac-4cb8-9a91-9223dc83b6b8",
"status": "error",
"source_kind": "file",
"title": "video.mp4",
"result": null,
"error_code": "ASR_FAILED",
"charged_seconds": null,
"created_at": "2026-07-29T08:00:00.000Z",
"updated_at": "2026-07-29T08:01:20.000Z"
}On-demand video link
done jobs do not embed a URL in history or the result. Call on demand. play returns a durable R2 custom-domain URL (https://r2.videoseed.cc/…; expires_in is kept for compatibility) and skips S3 HEAD; download is still a short-lived presigned URL with Content-Disposition (10 minutes) after HEAD verifies video/mp4.
curl 'https://www.videoseed.cc/api/v1/jobs/JOB_ID/video-link?mode=play' \ -H 'Authorization: Bearer YOUR_API_KEY' curl 'https://www.videoseed.cc/api/v1/jobs/JOB_ID/video-link?mode=download' \ -H 'Authorization: Bearer YOUR_API_KEY'
{
"url": "https://r2.videoseed.cc/upload/<sha>/video/source.mp4",
"mode": "play",
"expires_in": 1800,
"expires_at": "2026-08-10T00:30:00.000Z",
"content_type": "video/mp4",
"filename": "sample.mp4"
}History, batch, and usage
History uses a stable keyset cursor and returns card fields only. Treat cursor as opaque.
History
GET https://www.videoseed.cc/api/v1/jobs?limit=20&status=done Authorization: Bearer YOUR_API_KEY Response: items / has_more / next_cursor
Batch + idempotency
POST https://www.videoseed.cc/api/v1/analyses/batch
Idempotency-Key: batch-20260805-01
body: { items: [{ client_reference_id, upload_key | url, title, dims }] }
Max 8 items. Same key + same body replays the first response; different body → 409 IDEMPOTENCY_CONFLICT.Capabilities, usage, retry, cancel
GET /api/v1/capabilities (video_links: play 1800s, download 600s)
GET /api/v1/usage?limit=20
POST /api/v1/jobs/{id}/retry
POST /api/v1/jobs/{id}/cancel
usage is an append-only ledger: analysis_charge negative; credit_purchase / reservation_release positive. Only error is retryable; only queued / analyzing are cancellable.Webhook
On completion or failure, events go to a DB outbox first, then an independent dispatcher delivers them. Delivery failures do not change job status.
Requires scope webhooks:manage.
POST /api/v1/webhooks
{ "url": "https://your-server.example/hook", "events": ["analysis.completed", "analysis.failed"] }
→ 201; secret is returned once (whsec_…)
GET /api/v1/webhooks → list (no secret)
DELETE /api/v1/webhooks/{id} → { "ok": true }
URL must be HTTPS and public. localhost, private, link-local, and metadata addresses are rejected (400 WEBHOOK_INVALID_URL).
The dispatcher does not follow redirects.
Headers: X-Videoseed-Event-Id, X-Videoseed-Delivery-Id, X-Videoseed-Timestamp, X-Videoseed-Signature
Signature: sha256=HMAC_SHA256(secret, timestamp + "." + rawBody)
Non-2xx / timeouts retry with exponential backoff, up to 8 attempts then failed.// Receiver: verify before JSON.parse; reject timestamps >5 minutes old; dedupe by event_id const ts = req.headers['x-videoseed-timestamp']; const sig = req.headers['x-videoseed-signature']; const raw = await readRawBody(req); if (!verifyHmac(secret, ts, raw, sig)) return res.status(401).end(); const payload = JSON.parse(raw);
MCP
Remote MCP URL: https://www.videoseed.cc/api/mcp. For clients that support remote HTTP MCP and custom Authorization headers.
Create an API Key
Prefer a dedicated MCP key so you can revoke it alone.
Add to the MCP client
Claude Code can use the command below. Other clients should merge the JSON into existing mcpServers — do not overwrite.
claude mcp add --transport http videoseed 'https://www.videoseed.cc/api/mcp' \ --header 'Authorization: Bearer YOUR_API_KEY'
{
"mcpServers": {
"videoseed": {
"url": "https://www.videoseed.cc/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Reload and verify
Reload the MCP server, then ask for remaining quota. A success path calls get_quota and returns balanceSec.
Connectivity without the client
If the client fails to connect, verify URL and key with this request. A JSON-RPC result means the wire is fine.
curl -X POST 'https://www.videoseed.cc/api/mcp' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tools/call",
"params":{"name":"get_quota","arguments":{}}
}'Tools
| Tool | Args | Purpose |
|---|---|---|
| get_quota | — | credits |
| create_video_upload | filename, size_bytes | presigned PUT |
| analyze_video | url | upload_key; title?, dims? | create job |
| get_analysis | id | job status |
| get_video_link | id, mode=play|download | R2 MP4 URL |
| list_analyses | limit?, cursor?, status? | job history |
| analyze_videos | items ≤8 | batch |
| get_capabilities | — | limits |
| list_usage | limit?, cursor?, type? | usage ledger |
| retry_analysis | id | retry |
| cancel_analysis | id | cancel |
get_video_link only returns a URL when the job is done and the object still exists; it never exposes storage keys. play is a durable r2.videoseed.cc URL (sha256 path) until GC; download stays short-lived and presigned. Local files: create_video_upload → HTTP PUT → analyze_video with upload_key.
Errors
HTTP errors return code, message, and request_id. When contacting support, include request_id, job id, and time — never the full API Key.
{
"code": "QUOTA_INSUFFICIENT",
"message": "额度不足,请充值",
"request_id": "82d64113-09b0-473b-a7b5-1f11bbb08f64"
}| HTTP | code | What to do |
|---|---|---|
| 400 | VALIDATION | Bad fields, or both url and upload_key |
| 400 | LINK_UNSUPPORTED | Unsupported link platform |
| 400 | VIDEO_INVALID | Unrecognized video |
| 400 | WEBHOOK_INVALID_URL | Webhook URL is not HTTPS public |
| 401 | API_KEY_INVALID | Bad/revoked key or credential mismatch |
| 402 | QUOTA_INSUFFICIENT | Out of credits |
| 403 | FORBIDDEN | Foreign upload_key or missing scope |
| 404 | NOT_FOUND | Job missing or not yours |
| 404 | VIDEO_NOT_AVAILABLE | No playable object |
| 409 | IDEMPOTENCY_CONFLICT | Same Idempotency-Key, different body |
| 409 | JOB_NOT_RETRYABLE / JOB_NOT_CANCELLABLE | Wrong status |
| 429 | RATE_LIMITED | Too fast (>60/min) |
| 502 | LINK_FETCH_FAILED / ASR_FAILED / AI_FAILED | Upstream failure |
| 500 / 503 | INTERNAL / PROVIDER_DOWN / WEBHOOK_SECRET_UNAVAILABLE | Temporary outage |
Safe to retry automatically: 429, 500, 502, 503 (2s / 4s / 8s, max 3). Do not blind-retry: 400, 401, 402, 404, 422.