The Developer APIs are still in beta and may change at any time. At this stage, they are better suited to personal testing and experimentation and are not yet recommended for production use.
Call the 63 capabilities enabled for this deployment through REST, or use them directly from MCP-compatible AI tools. An active subscription is required.
Codex MCP, REST, Claude Code, Cursor, and manual MCP clients use dedicated API keys. Codex stores the static Authorization header in user-level configuration; never send a key through chat or shell history.
Codex MCP, REST, and manual clients use an API key tied to your subscription.
Authorization: Bearer YOUR_API_KEYAn expired subscription returns 403, and existing Keys resume after renewal. Only ACCOUNT_SESSION_REFRESH_REQUIRED is fixed by signing in to VidMage once; follow the specific recovery action for other 401 types.
Use a short-lived, size-bound URL to upload local images, videos, or audio directly to storage. File bytes do not pass through the VidMage application server.
curl -X POST "https://vidmage.ai/api/v1/files/upload" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "capability": "video-upscaler", "parameter": "videoURL", "duration": 7.25, "fileName": "input.mp4", "localPath": "./input.mp4", "contentType": "video/mp4", "fileSize": 12345678 }'
# -> { "uploadCommand": "curl ...", "fileUrl": "https://..." }
# Run uploadCommand once, then use fileUrl in the generation request.# ~/.codex/config.toml
# Direct, persistent user-level MCP registration; this is not a plugin install.
# Replace any existing [mcp_servers.vidmage] table; do not append a duplicate.
[mcp_servers.vidmage]
url = "https://vidmage.ai/api/mcp"
http_headers = { Authorization = "Bearer YOUR_API_KEY" }
enabled = true
required = true
startup_timeout_sec = 30
tool_timeout_sec = 120
# Save this file, then confirm persistence with: codex mcp get vidmage
# Restrict the user-level file after saving: chmod 600 ~/.codex/config.toml
# Diagnose this direct server from VidMage startup errors; a remote Plugins catalog 401 is unrelated.
# Fully restart Codex, create a new task, and call list_capabilities.Skip discovery when the capability is known. For local files, call upload_files once and run all uploadCommand values in parallel. Call submit_task exactly once, then query the same task after retryAfterMs. If it returns NEEDS_INPUT, show the corrected native previews, call select_faces once, and continue polling the same task.
list_models → estimate_model_creditsMCP requests require a subscription-backed API key.
upload_files (one batch, parallel PUT)
→ submit_task + fresh stable idempotencyKey (exactly once)
→ get_task_result after retryAfterMs
→ select_faces only on NEEDS_INPUT
→ continue polling the same task
→ native resultAll AI tasks are asynchronous. Every capability provides two endpoints:
| Endpoint | Description |
|---|---|
| GET /api/v1/capabilities | Every request is authenticated with an API key tied to your subscription. |
| GET /api/v1/openapi.json | Every request is authenticated with an API key tied to your subscription. |
| POST /api/v1/files/upload | Creates a restricted temporary direct-upload URL for a local media file. |
| GET /api/v1/tasks/recent | Recovers recent task IDs after a timeout, disconnect, or lost response. |
| POST /api/v1/<capability>/submit | Starts a task and returns its task ID immediately. |
| POST /api/v1/<capability>/query | Checks a task by ID and returns its status and result URL when complete. |
curl -X POST "https://vidmage.ai/api/v1/face-swap/submit" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"targetImageUrl":"https://vidmage.ai/assets/images/samples/blue-eyed-woman-sunlight.webp","referenceFaceImageUrl":"https://vidmage.ai/assets/images/samples/smiling-man-sweater.webp"}'
# -> { "success": true, "taskId": "...", "creditsRequired": ..., "creditsConsumed": 0, "usageDeferred": true }curl -X POST "https://vidmage.ai/api/v1/face-swap/query" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "taskId": "TASK_ID_FROM_SUBMIT" }'
# -> { "success": true, "data": { "status": "...", "imageUrl": "https://..." } }The capability catalog and OpenAPI 3.1 document are generated from the same contract used for request validation. Import the specification into Postman, Insomnia, Bruno, or an API client generator.
curl -H "Authorization: Bearer YOUR_API_KEY" "https://vidmage.ai/api/v1/capabilities"curl -H "Authorization: Bearer YOUR_API_KEY" "https://vidmage.ai/api/v1/openapi.json" --output vidmage-openapi.jsoncurl -H "Authorization: Bearer YOUR_API_KEY" "https://vidmage.ai/api/v1/tasks/recent?limit=10"These examples submit a task once and poll the same task ID until completion. Keep the idempotency key stable when retrying the same submission.
const submitResponse = await fetch('https://vidmage.ai/api/v1/face-swap/submit', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDMAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"targetImageUrl": "https://vidmage.ai/assets/images/samples/blue-eyed-woman-sunlight.webp",
"referenceFaceImageUrl": "https://vidmage.ai/assets/images/samples/smiling-man-sweater.webp"
}),
})
const submitted = await submitResponse.json()
if (!submitResponse.ok || !submitted.success) {
throw new Error(submitted.message ?? submitted.error ?? 'Task submission failed')
}
const taskId = submitted["taskId"]
if (!taskId) throw new Error('Submit succeeded without a task id')
while (true) {
await new Promise(resolve => setTimeout(resolve, 5000))
const queryResponse = await fetch('https://vidmage.ai/api/v1/face-swap/query', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDMAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ taskId: taskId }),
})
const queried = await queryResponse.json()
if (!queryResponse.ok || !queried.success) {
throw new Error(queried.message ?? queried.error ?? 'Task query failed')
}
const data = queried.data ?? queried
const status = String(data.status ?? '').toLowerCase()
if (['success', 'succeeded', 'completed'].includes(status)) {
console.log(queried.result ?? queried["imageUrl"] ?? data["imageUrl"])
break
}
if (['failed', 'error'].includes(status)) {
throw new Error(data.errorDetail ?? data.error ?? 'Task failed')
}
}import os
import time
import requests
submit_response = requests.post(
"https://vidmage.ai/api/v1/face-swap/submit",
headers={
"Authorization": f"Bearer {os.environ['VIDMAGE_API_KEY']}",
},
json={
"targetImageUrl": "https://vidmage.ai/assets/images/samples/blue-eyed-woman-sunlight.webp",
"referenceFaceImageUrl": "https://vidmage.ai/assets/images/samples/smiling-man-sweater.webp",
},
timeout=60,
)
submitted = submit_response.json()
if not submit_response.ok or not submitted.get("success"):
raise RuntimeError(submitted.get("message") or submitted.get("error") or "Task submission failed")
task_id = submitted["taskId"]
if not task_id:
raise RuntimeError("Submit succeeded without a task id")
while True:
time.sleep(5)
query_response = requests.post(
"https://vidmage.ai/api/v1/face-swap/query",
headers={"Authorization": f"Bearer {os.environ['VIDMAGE_API_KEY']}"},
json={"taskId": task_id},
timeout=60,
)
queried = query_response.json()
if not query_response.ok or not queried.get("success"):
raise RuntimeError(queried.get("message") or queried.get("error") or "Task query failed")
data = queried.get("data") or queried
status = str(data.get("status") or "").lower()
if status in {"success", "succeeded", "completed"}:
print(queried.get("result") or queried.get("imageUrl") or data.get("imageUrl"))
break
if status in {"failed", "error"}:
raise RuntimeError(data.get("errorDetail") or data.get("error") or "Task failed")Use the HTTP status for control flow and errorType for specific recovery behavior. Every REST response includes an X-Request-Id header for tracing and support.
| Status | Common errorType | Recommended action |
|---|---|---|
| 400 | INVALID_JSON / VALIDATION_ERROR | Correct the fields listed in details; do not retry the same input unchanged. |
| 401 | AUTHENTICATION_REQUIRED | VidMage received no credential. Add Authorization to the Codex user-level configuration or target client; never request or paste an API Key in chat. |
| 401 | AUTHORIZATION_HEADER_INVALID | Fix the Authorization header to exactly Bearer <API_KEY>; no task was submitted. |
| 401 | API_KEY_INVALID_OR_REVOKED | Create a new API key and replace only Authorization in the existing configuration; restart Codex and verify in a new task. |
| 401 | API_KEY_INVALID_CREDENTIAL | Create a new API key and replace the credential in the target client; the existing credential cannot be decrypted. |
| 401 | ACCOUNT_SESSION_REFRESH_REQUIRED | Sign in to VidMage once. The service refreshes the backend account credential wrapped by existing API keys; client configuration stays unchanged. |
| 401 REST | NEED_API_KEY | REST compatibility only: provide an API key. |
| 402 | NEED_PURCHASE_CREDITS | Add credits or choose a lower-cost operation. |
| 403 | NEED_SUBSCRIBE | Activate or renew the account subscription. |
| 404 | CAPABILITY_NOT_ENABLED | Refresh capability discovery. The capability is disabled or is not available in this deployment. |
| 409 | UPLOAD_NOT_READY / IDEMPOTENCY_CONFLICT / IDEMPOTENCY_IN_PROGRESS | If submission is in progress, wait for the indicated delay and reuse the same idempotencyKey. Create a new key only for a different generation request. |
| 410 | UPLOAD_EXPIRED | Create a new temporary upload; the previous file URL has expired. |
| 429 | RATE_LIMITED | Wait for Retry-After, then retry with capped exponential backoff. |
| 503 | CREDENTIAL_STORAGE_UNAVAILABLE | Honor Retry-After and retry once. If it persists, stop, report requestId, and ask an operator to restore Developers MySQL readiness. Keep the API key and do not resubmit. |
| 503 / MCP | SUBMISSION_OUTCOME_UNKNOWN / BILLING_OUTCOME_UNKNOWN / REFUND_OUTCOME_UNKNOWN | Follow recovery first: GET_RECENT_TASKS means call get_recent_tasks; QUERY_TASK_ID_OR_CONTACT_SUPPORT means query the same taskId or contact support; CONTACT_SUPPORT_WITH_IDEMPOTENCY_KEY_AND_BUSINESS_ID means contact support with idempotencyKey and businessId. Never create a new idempotencyKey, resubmit, or attempt another refund. |
| 503 / MCP | TASK_PERSISTENCE_UNCERTAIN | Keep taskId, do not resubmit, and contact support with taskId if this persists. |
| MCP | TASK_QUERY_INTERRUPTED | Resume polling the same taskId; do not submit another task. |
| MCP | RESULT_MISSING | Keep polling the same taskId; do not resubmit the task. |
| 404 / MCP | TASK_NOT_FOUND | Call get_recent_tasks before any retry; do not resubmit a paid task. |
| 502 / MCP | BILLING_INVARIANT_FAILED | Do not retry, rotate the API key, change the original Idempotency-Key, or resubmit. Contact support with taskId when present, capability, and the original Idempotency-Key; for REST, also include X-Request-Id. |
| 5xx | *_SERVICE_UNAVAILABLE / UPSTREAM_* | For other 5xx errors, retry with capped exponential backoff and retain the task ID. This does not apply to CREDENTIAL_STORAGE_UNAVAILABLE. |
The parameters below form the request body for submit. Use the same task ID field when polling query.