Tra cứu
REST API và webhook
REST API là một giao diện HTTP nhỏ dưới /v1 dành cho script và backend: báo giá một clip, tạo clip, đọc và tải tệp, và chạy các lô clip. API dùng cùng token API như MCP, và cùng các giới hạn áp dụng cho nó.
Cơ bản
- Đường dẫn gốc là
/v1trên máy chủ studio, cùng máy chủ với MCP server. Trong các ví dụ,STUDIO_URLchứa địa chỉ vàMCP_API_TOKENchứa token. - Xác thực chỉ bằng bearer token:
Authorization: Bearer <token>. Cookie đăng nhập web không bao giờ được đọc. Gọi mà không có token thì lỗiUNAUTHENTICATED(HTTP 401). - Tạo token ở trang Nhà phát triển, như trong bắt đầu nhanh. Chế độ, công cụ được phép, phạm vi dự án, số lệnh gọi mỗi phút, trần chi tiêu, số job tính phí cùng lúc và việc tạm khóa đều áp dụng y như qua MCP.
- Mỗi endpoint cần công cụ MCP cùng mục đích nằm trong danh sách công cụ được phép của token (xem bảng bên dưới).
GET /v1/mekhông cần công cụ nào.
export STUDIO_URL="https://<your-studio-host>"
export MCP_API_TOKEN="<your token>"
curl -sS "$STUDIO_URL/v1/me" -H "Authorization: Bearer $MCP_API_TOKEN"GET /v1/me{
"object": "token",
"workspace_id": "<workspace id>",
"project_id": null,
"mode": "execute",
"environment": "<environment>",
"tools": ["quote_media_task", "generate_media_task", "get_media_task"],
"rate_limit_per_minute": 60
}Các endpoint
| Endpoint | Cần công cụ | Làm gì |
|---|---|---|
GET /v1/me | không cần | Không gian làm việc, dự án, chế độ, công cụ được phép và số lệnh gọi mỗi phút của token. |
GET /v1/models | list_video_models | Các mô hình video bạn có thể chọn, và default_video_model. |
POST /v1/media-tasks/quotes | quote_media_task | Báo giá một tác vụ media và trả 201. Báo giá miễn phí. |
POST /v1/media-tasks/{task_id}/generate | generate_media_task | Bắt đầu một tác vụ đã báo giá. Tiêu tiền ngay theo giá đã báo. |
GET /v1/media-tasks | get_media_task | Liệt kê tác vụ. kind mặc định là video_clip. |
GET /v1/media-tasks/{task_id} | get_media_task | Đọc một tác vụ. |
GET /v1/media-tasks/{task_id}/file | get_media_task | Tải tệp. Trả 409 cho đến khi tác vụ ở trạng thái READY. |
POST /v1/media-batches | create_media_batch | Tạo một lô clip và mặc định bắt đầu nó. Trả 201. |
GET /v1/media-batches/{batch_id} | get_media_batch | Đọc một lô: trạng thái, số lượng và tiến độ. |
GET /v1/media-batches/{batch_id}/items | list_media_batch_items | Liệt kê các mục. Mục ready có file_url. |
GET /v1/openapi.json, GET /v1/docs | không cần | Tài liệu OpenAPI và một trang liệt kê các endpoint. Cả hai đều mở: không cần token. |
Nội dung báo giá nhận cùng các trường như công cụ MCP quote_media_task; trường lạ bị từ chối. Các trường cho clip video là kind, prompt, duration_seconds, ratio, resolution, route và reference_asset_id. Các loại khác là voice, music, sfx và image. Không gian làm việc không bao giờ nằm trong nội dung gửi lên: một token thuộc về một không gian làm việc.
Báo giá rồi tạo clip
- Báo giá clip. Phản hồi có
idcủa tác vụ vàstatelàQUOTED. - Cho người dùng xem giá. Chỉ sau khi họ đồng ý mới tạo clip. Phản hồi có
statelàQUEUED. - Đọc tác vụ cho đến khi
statelàREADY, hoặc chờ webhook (bên dưới). - Tải tệp.
curl -sS -X POST "$STUDIO_URL/v1/media-tasks/quotes" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"kind": "video_clip", "prompt": "A ceramic mug on a bright table, slow turn", "duration_seconds": 4, "ratio": "9:16"}'export TASK_ID="<id from the quote>"
curl -sS -X POST "$STUDIO_URL/v1/media-tasks/$TASK_ID/generate" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"curl -sS "$STUDIO_URL/v1/media-tasks/$TASK_ID" -H "Authorization: Bearer $MCP_API_TOKEN"
curl -sS -o clip.mp4 "$STUDIO_URL/v1/media-tasks/$TASK_ID/file" -H "Authorization: Bearer $MCP_API_TOKEN"Lô
Gửi bảng dưới dạng csv (văn bản), xlsx_base64 (tệp .xlsx mã hóa base64) hoặc rows (danh sách đối tượng). Ngoài bảng, nội dung nhận các trường sau; trường lạ bị từ chối.
| Trường | Ý nghĩa |
|---|---|
csv, xlsx_base64, rows | Bảng. csv tối đa 2.000.000 ký tự, xlsx_base64 tối đa 4.000.000. Một lô mặc định có 100 dòng. |
template | Tối đa 3600 ký tự, với các chỗ giữ chỗ {{column}}. Không có thì cột prompt của mỗi dòng là prompt. |
route, duration_seconds, ratio, resolution, reference_asset_id | Các tùy chọn clip cho mọi dòng; một cột trong bảng thay chúng cho một dòng. Xem Clip video và lô clip. |
project_id | Dự án để gắn lô vào, không bắt buộc. |
start | Mặc định là true: lô bắt đầu ngay. false tạo bản nháp miễn phí để người dùng bắt đầu trong ứng dụng web. |
curl -sS -X POST "$STUDIO_URL/v1/media-batches" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"rows": [{"product": "Ceramic mug"}, {"product": "Linen shirt"}], "template": "Slow turntable shot of a {{product}} on a bright table", "ratio": "9:16", "start": false}'Phản hồi có id của lô, status (ở đây là draft) và item_count. Đọc lô bằng GET /v1/media-batches/{batch_id} và các mục bằng GET /v1/media-batches/{batch_id}/items, có thể kèm state=ready.
Idempotency-Key
- Mọi
POSTcần headerIdempotency-Keydài từ 8 đến 200 ký tự ASCII in được. UUID là một khóa tốt. Không có khóa hợp lệ thì lệnh gọi lỗiV1_IDEMPOTENCY_KEY_REQUIRED(HTTP 400). - Khóa có phạm vi theo không gian làm việc và theo route. Lặp lại một khóa trên cùng route trả về kết quả đầu tiên và không tạo thêm báo giá, tác vụ hay lô nào. Cùng khóa trên route khác là một yêu cầu khác.
- Tạo khóa mới cho mỗi yêu cầu mới; chỉ dùng lại khóa để thử lại đúng lệnh gọi đó sau khi hết thời gian chờ.
Giới hạn tốc độ, lỗi và phân trang
- Mọi phản hồi mang
X-RateLimit-Limit,X-RateLimit-RemainingvàX-RateLimit-Reset. Giá trị reset là thời điểm unix tính bằng giây, khi cửa sổ một phút hiện tại kết thúc. Phản hồi 429 còn mangRetry-After. - Lệnh gọi thất bại trả
{code, message, retryable, request_id}.messagetheo headerAccept-Languagecủa yêu cầu. Hãy nêurequest_idkhi nhờ hỗ trợ. - Các mã là những mã trong bảng tra cứu lỗi; riêng REST có
V1_IDEMPOTENCY_KEY_REQUIREDvàV1_CURSOR_INVALID. - Danh sách trả
{object: "list", data, has_more, next_cursor, total}. Gửi lại nguyênnext_cursorlàmcursorđể lấy trang kế.limitmặc định 20 và tối đa 100. Con trỏ không do chúng tôi cấp bị lỗiV1_CURSOR_INVALID(HTTP 400).
{
"code": "V1_IDEMPOTENCY_KEY_REQUIRED",
"message": "<text in the language of Accept-Language>",
"retryable": false,
"request_id": "<request id>"
}Tài liệu OpenAPI
GET /v1/openapi.json trả tài liệu OpenAPI của API công khai và GET /v1/docs hiện các endpoint dưới dạng một trang. Cả hai đều không cần token và có thể được lưu đệm vài phút. Hợp đồng công khai là tài liệu này; các route mà chính ứng dụng web dùng không thuộc về nó.
curl -sS "$STUDIO_URL/v1/openapi.json"Webhook
Webhook gửi một POST HTTPS có chữ ký tới máy chủ của bạn khi có việc hoàn tất, để script không phải hỏi liên tục.
- Quản trị viên của không gian làm việc đăng nhập ứng dụng web, mở Nhà phát triển rồi Webhook. Token API hoặc phiên MCP không quản lý được webhook: chúng nhận
FORBIDDEN. Không có endpoint REST cho việc này. - Thêm URL endpoint, mô tả tùy chọn tối đa 120 ký tự, và các sự kiện cần gửi, hoặc tất cả. Một không gian làm việc có tối đa 10 endpoint (
WEBHOOK_LIMIT_REACHED, HTTP 409). - Sao chép khóa ký, bắt đầu bằng
whsec_. Khóa chỉ hiện một lần, khi bạn tạo endpoint hoặc xoay khóa. Sau đó chỉ hiện 4 ký tự cuối. - Chọn Gửi thử để nhận sự kiện
webhook.test, thường trong khoảng 30 giây. Gửi thử và gửi lại có giới hạn tốc độ: quá nhiều lần đang chờ sẽ trảWEBHOOK_RATE_LIMITED(HTTP 429, thử lại được); đợi vài giây rồi thử lại.
- Xoay khóa: khóa trước vẫn tiếp tục ký thêm 24 giờ, nên bạn đổi khóa mà không mất sự kiện. Trong thời gian đó, header chữ ký mang một giá trị
v1=cho mỗi khóa còn hiệu lực. - Một endpoint có thể bị tắt và xóa. Endpoint đã tắt không nhận gì (
WEBHOOK_ENDPOINT_DISABLED, HTTP 409, khi bạn thử gửi thử tới nó). - Nhật ký gửi cho thấy mọi lần thử. Gửi lại sẽ gửi lại một lần gửi với cùng mã sự kiện, để máy chủ của bạn nhận ra bản trùng.
Sự kiện và header của yêu cầu
| Sự kiện | Gửi khi | Trường trong `data` |
|---|---|---|
media_task.succeeded | Một tác vụ media, ví dụ một clip, đã xong. | task_id, kind, state, billed_micros, currency, error_code, batch_id, row_index, url |
media_task.failed | Một tác vụ media thất bại. | Các trường giống trên. |
batch.completed | Một lô clip đã kết thúc. | batch_id, status, ready, needs_attention, cancelled, url |
delivery.issued | Một bản giao đã được phát hành. | delivery_id, project_id |
webhook.test | Bạn chọn Gửi thử. Không thể đăng ký nhận sự kiện này. |
created là thời điểm unix tính bằng giây){
"api_version": "v1",
"created": 1700000000,
"data": {
"batch_id": "<batch id>",
"cancelled": 0,
"needs_attention": 1,
"ready": 9,
"status": "completed",
"url": "/v1/media-batches/<batch id>"
},
"id": "<event id>",
"object": "event",
"type": "batch.completed",
"workspace_id": "<workspace id>"
}| Header | Giá trị |
|---|---|
Content-Type | application/json |
User-Agent | RPT-Webhooks/1 |
RPT-Signature | t=<unix seconds>,v1=<hex> |
RPT-Event-Id | Mã sự kiện. Lần gửi lại giữ nguyên. |
RPT-Event-Type | Loại sự kiện. |
RPT-Delivery-Id | Lần gửi này. |
RPT-Delivery-Attempt | Số lần thử, bắt đầu từ 1. |
Xác minh chữ ký
Chữ ký là HMAC-SHA256 dạng hex của {t}.{raw body}, với khóa là khóa ký. Luôn xác minh trước khi tin một yêu cầu:
- Dùng nguyên các byte thô của nội dung, đúng như nhận được. Đừng phân tích rồi tuần tự hóa lại.
- So sánh trong thời gian hằng số. Chấp nhận yêu cầu nếu bất kỳ giá trị
v1nào khớp. - Từ chối mốc thời gian lệch quá 300 giây so với đồng hồ của bạn, để yêu cầu cũ không bị phát lại.
- Trả trạng thái 2xx ngay khi đã lưu sự kiện. Việc chậm làm sau.
import hashlib
import hmac
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["WEBHOOK_SECRET"]
def valid(header, body):
timestamp, candidates = None, []
for part in (header or "").split(","):
key, _, value = part.strip().partition("=")
if key == "t":
timestamp = value
elif key == "v1":
candidates.append(value)
if timestamp is None or not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(SECRET.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, candidate) for candidate in candidates)
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
ok = valid(self.headers.get("RPT-Signature"), body)
self.send_response(204 if ok else 400)
self.end_headers()
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()Gửi, thử lại và đích an toàn
- Mỗi lần thử chờ tối đa 8 giây. Mọi trạng thái khác 2xx đều tính là thất bại. Chuyển hướng (3xx) cũng là thất bại: chuyển hướng không bao giờ được đi theo.
- Lần gửi thất bại được thử lại sau 30 giây, 2 phút, 10 phút, 30 phút, 1 giờ, 2 giờ, 4 giờ, 8 giờ và 8 giờ. Đó là chín lần thử lại trong khoảng 24 giờ. Sau đó lần gửi ở trạng thái
failed. Trạng thái của lần gửi làqueued,deliveredhoặcfailed. - URL endpoint phải là
https, không có tên người dùng hay mật khẩu, không có fragment, và dài tối đa 2000 ký tự. - Máy chủ đích phải là một tên công khai. Địa chỉ IP, tên không có dấu chấm, hoặc tên kết thúc bằng
.local,.internal,.localhost,.lanhay.home.arpađều bị từ chối. Tên trỏ tới địa chỉ loopback, riêng tư, link-local hoặc địa chỉ dành riêng khác cũng vậy. Bộ gửi kết nối đúng địa chỉ đã kiểm tra, nên câu trả lời không thể đổi giữa lúc kiểm tra và lúc gọi. - URL bị từ chối cho lỗi
WEBHOOK_URL_REFUSED(HTTP 422);details.reasonlàurl_invalid,https_required,target_not_publichoặcdns_failed. Đích bị từ chối ở một lần gửi sau thì thất bại ngay, không thử lại.