diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index cfe68b5..5b4f3f5 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "beatapi-codex-plugin", "version": "0.2.0", - "description": "Create and manage BeatAPI async and realtime AI video APIs from Codex.", + "description": "Create and manage BeatAPI image, video, Effect, workflow, and realtime APIs from Codex.", "author": { "name": "BeatAPI", "email": "support@beatapi.io", @@ -15,14 +15,17 @@ "ai-video", "music-video", "ecommerce-video", - "realtime-video" + "realtime-video", + "image-generation", + "video-generation", + "ai-effects" ], "skills": "./skills/", "mcpServers": "./.mcp.json", "interface": { "displayName": "BeatAPI", - "shortDescription": "Manage async and realtime AI video APIs", - "longDescription": "Use one existing BeatAPI account and API key from Codex to upload inputs, check credits and concurrency, create asynchronous Music Video and Ecommerce Video tasks, manage short-lived Realtime Video sessions, monitor progress, retrieve hosted results, and configure webhooks.", + "shortDescription": "Manage generation, Effects, workflows, and realtime", + "longDescription": "Use one existing BeatAPI account and API key from Codex to discover public models and Effects, upload inputs, check USD balance and concurrency, create image, video, Effect, Music Video, and Ecommerce Video tasks, manage short-lived Realtime Video sessions, monitor progress, retrieve hosted results, and configure webhooks.", "developerName": "BeatAPI", "category": "Creativity", "capabilities": [ @@ -35,8 +38,11 @@ "defaultPrompt": [ "Use $beatapi-video to create a music video from my images and audio.", "Use $beatapi-video to turn my product images into a vertical ad.", + "Use $beatapi-video to generate an image with Nano Banana Pro.", + "Use $beatapi-video to create a Seedance 2.5 video.", + "Use $beatapi-video to list current Effects for this portrait.", "Use $beatapi-video to create a 60-second realtime session for my web app.", - "Use $beatapi-video to check my credits and task status." + "Use $beatapi-video to check my USD balance and task status." ], "brandColor": "#2563FF", "composerIcon": "./assets/icon.png", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bcff7d..f2d9847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- Added MCP tools for generation-model discovery, image generation, video + generation, and versioned Effect discovery and task creation. +- Synchronized the canonical Skill, typed client runtime, and OpenAPI snapshot + with four image models, seven video models, shared task fields, and + USD-denominated usage semantics. + ## 0.2.0 - 2026-07-31 - Added Realtime Video session create, read, and close MCP tools. diff --git a/README.md b/README.md index 7f52744..03c5117 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # BeatAPI Codex Plugin -Create and manage BeatAPI asynchronous workflows and Realtime Video sessions +Create and manage BeatAPI image, video, Effect, workflow, and Realtime APIs directly from Codex. The plugin combines the canonical `beatapi-video` Skill with a bundled local MCP server and uses the same API key as the BeatAPI CLI. ## What users can do -- inspect workflows, credits, usage, and concurrency; +- discover stable image/video model aliases and published Effects; +- create model-specific image, video, and versioned Effect tasks; +- inspect workflows, USD balance, usage, and concurrency; - upload local images, audio, and SRT files; - create automatic or manual Music Video tasks; - inspect, edit, materialize, and compose storyboard shots; @@ -56,7 +58,10 @@ Restart the desktop app after installation. Useful starter requests include: - “Use `$beatapi-video` to create a music video from my images and audio.” - “Turn these product photos into a 15-second 9:16 ad.” -- “Check my BeatAPI credits and the status of task `task_...`.” +- “Generate an image with Nano Banana Pro.” +- “Make a 10-second Seedance 2.5 video with these references.” +- “List the current Effects and run one on this portrait.” +- “Check my BeatAPI balance and the status of task `task_...`.” - “Create a 60-second Realtime Video session for `https://app.example.com`.” ## Package layout diff --git a/contract/beatapi.openapi.yaml b/contract/beatapi.openapi.yaml index 65e1dc9..36e850d 100644 --- a/contract/beatapi.openapi.yaml +++ b/contract/beatapi.openapi.yaml @@ -6,11 +6,15 @@ info: name: BeatAPI Terms of Service url: https://beatapi.io/terms-of-service description: | - BeatAPI provides async video workflows and short-lived Realtime Video - Sessions behind one BeatAPI-native API. Async integrations create a task, - poll until it finishes, then read the hosted video URL from `output.media`. - Realtime browser integrations create a Session with the same Bearer API key, - then pass only the returned BeatAPI `client_secret` to `@beatapi/realtime`. + BeatAPI provides a unified API for image generation, video generation, Effects, asynchronous video workflows, + and short-lived Realtime Video Sessions. + + For asynchronous operations, create a task, poll the shared task endpoint or receive webhook events, + and read hosted output URLs from `output.media`. For Realtime, create a Session on a trusted server + with your Bearer API key and pass only the returned short-lived `client_secret` to the browser. + + Customer balances and usage are USD-denominated. Compatibility fields such + as `credit_balance` and `credits_reserved` remain in the API; 1 Credit = $1 USD. ## 5 minute Quick Start @@ -18,7 +22,7 @@ info: 2. Create an API key in [Dashboard → API Keys](https://beatapi.io/dashboard/apikeys) and send it as `Authorization: Bearer `. - Credit packs are available from + USD balance top-ups are available from [Dashboard → Billing](https://beatapi.io/dashboard/billing). 3. Use public HTTPS URLs for input media. If your files are local, upload them with `POST /v1/files` first. @@ -27,7 +31,8 @@ info: 6. Add webhooks later if you do not want to poll. ```bash - export BEATAPI_API_KEY="sk_your_key" + read -rsp "BeatAPI API key: " BEATAPI_API_KEY && echo + export BEATAPI_API_KEY curl https://api.beatapi.io/v1/workflows @@ -71,13 +76,13 @@ info: public internet. Localhost, private network URLs, and data URLs are rejected. Use `POST /v1/files` for local images, audio, or subtitles. - Each verified new user account starts with 50 welcome credits valid for 14 days and - 1 active processing concurrency. Lifetime paid credit purchases unlock higher limits: + Each verified new user account starts with a $2 welcome balance that never expires and + 1 active processing concurrency. Lifetime paid purchases unlock higher limits: $10+ = 2, $100+ = 5, $1,000+ = 10, $5,000+ = 15, and $20,000+ = 30. `GET /v1/usage` returns current usage totals, concurrency limit, and active processing task count. Active concurrency measures tasks that are currently using BeatAPI processing resources. storyboard_ready and requires_action - tasks can have settled credits but do not count toward active processing + tasks can have settled USD usage but do not count toward active processing concurrency. ## Webhooks are optional @@ -95,6 +100,10 @@ tags: description: Discover the workflow IDs available for task creation. - name: Music Video description: Create music video tasks from images, audio, and optional creative controls. + - name: Effects + description: Discover versioned effects and create image or video effect tasks. + - name: Generation + description: Discover BeatAPI generation models and create image or video tasks. - name: Ecommerce Video description: Create product ad video tasks from product images and duration. - name: Tasks @@ -107,6 +116,70 @@ tags: description: Upload local assets and use the returned HTTPS URL as workflow input. - name: Webhooks description: Manage optional completion callbacks. +webhooks: + taskCompleted: + post: + operationId: receiveBeatApiTaskEvent + x-fern-ignore: true + tags: [Webhooks] + summary: Receive a BeatAPI task completion event + description: | + BeatAPI sends this request to each active endpoint subscribed to the event. + Verify `x-beatapi-signature` against the exact request body and use polling + as the source of truth if delivery is delayed or fails. + security: [] + parameters: + - in: header + name: x-beatapi-event + required: true + schema: { type: string, enum: [task.succeeded, task.failed] } + - in: header + name: x-beatapi-timestamp + required: true + schema: { type: string } + - in: header + name: x-beatapi-signature + required: true + schema: { type: string } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/WebhookEvent' } + example: + id: evt_123 + event: task.succeeded + created_at: 1782210300 + data: + id: task_8K2qA + object: task + task_kind: video + capability_id: seedance-2.5 + capability_version: null + media_type: video + model: seedance-2.5 + status: succeeded + stage: succeeded + created_at: 1782210000 + updated_at: 1782210300 + completed_at: 1782210300 + output: + media: + - type: video + url: https://media.beatapi.io/outputs/task_8K2qA/0.mp4 + mime_type: video/mp4 + r2_url: https://media.beatapi.io/outputs/task_8K2qA/0.mp4 + usage: + credits_reserved: 1.55 + credits_charged: 1.55 + billable_duration_seconds: 5 + credits_settled: 1.55 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + responses: + '200': { description: Event accepted } components: securitySchemes: BearerAuth: @@ -144,17 +217,22 @@ components: example: shot_xxx index: type: integer + description: Zero-based shot order in the storyboard. example: 0 status: $ref: '#/components/schemas/TaskStatus' + description: Current lifecycle state for this storyboard shot. duration_seconds: type: integer + description: Planned or generated shot duration in seconds. example: 5 prompt: type: string + description: Creative instruction used to generate this shot. example: Opening lyric shot. lyric_text: type: string + description: Lyric segment aligned with this shot when available. example: Intro media: type: object @@ -162,19 +240,24 @@ components: properties: type: type: string + description: Hosted media type for the materialized shot. example: video url: type: string format: uri + description: BeatAPI-hosted HTTPS URL for the materialized shot. example: https://media.beatapi.io/outputs/task_8K2qA/shots/0.mp4 mime_type: type: string + description: MIME type of the hosted shot media. example: video/mp4 created_at: type: integer + description: Unix timestamp when the shot record was created. example: 1782210000 updated_at: type: integer + description: Unix timestamp when the shot record last changed. example: 1782210300 Storyboard: type: object @@ -182,6 +265,7 @@ components: properties: shots: type: array + description: Ordered Music Video storyboard shots. The array may be empty before storyboard generation completes. items: $ref: '#/components/schemas/StoryboardShot' ShotMedia: @@ -230,46 +314,88 @@ components: required: [credits_reserved, credits_settled, credits_refunded, credits_charged] properties: credits_reserved: - type: integer - description: BeatAPI customer credits reserved for this task. + type: number + format: double + multipleOf: 0.01 + description: USD amount reserved for this task. The compatibility field name is retained; 1 Credit equals $1 USD. credits_charged: - type: integer - description: BeatAPI customer credits charged when the task or operation is accepted. + type: number + format: double + multipleOf: 0.01 + description: USD amount charged when the task or operation is accepted. billable_duration_seconds: type: integer - description: Server-detected or request-declared billable duration used for credit calculation. + description: Server-detected or request-declared billable duration used for USD calculation. credits_settled: - type: integer - description: BeatAPI customer credits settled after successful work. + type: number + format: double + multipleOf: 0.01 + description: USD amount settled after successful work. credits_refunded: - type: integer - description: BeatAPI customer credits refunded after failed eligible work. + type: number + format: double + multipleOf: 0.01 + description: USD amount refunded after failed eligible work. Task: type: object - required: [id, object, workflow, status, stage, created_at, updated_at, completed_at, output, usage, request_id, error_code, error_message] + required: [id, object, task_kind, capability_id, capability_version, status, stage, created_at, updated_at, completed_at, output, usage, request_id, error_code, error_message] properties: id: type: string + description: Stable BeatAPI task ID used for polling and support. example: task_8K2qA object: type: string enum: [task] + description: Object discriminator; always `task`. + task_kind: + type: string + enum: [workflow, effect, image, video] + description: Public task family that determines which capability fields are present. + capability_id: + type: string + description: Stable BeatAPI workflow, Effect, or generation model ID selected when the task was accepted. + capability_version: + type: [integer, 'null'] + description: Immutable capability version used by this task. Legacy workflow rows are returned as version 1. workflow: type: string enum: [music-video, ecommerce-video] + description: Present for workflow tasks; identifies the selected BeatAPI workflow. example: music-video + effect_id: + type: string + description: Present for Effect tasks; stable selected Effect ID. + example: video-muscle-max + effect_version: + type: integer + description: Present for Effect tasks; immutable Effect version used for processing. + example: 1 + media_type: + type: string + enum: [image, video] + description: Present when task_kind is image or video. + model: + type: string + description: Stable BeatAPI model alias. It is independent from internal execution routing. status: $ref: '#/components/schemas/TaskStatus' + description: Current task lifecycle status. Stop polling at `succeeded` or `failed`; Music Video can also require manual action. stage: $ref: '#/components/schemas/TaskStatus' + description: Current processing stage, exposed separately so workflow progress can be tracked. storyboard: $ref: '#/components/schemas/Storyboard' + description: Music Video storyboard metadata when available. created_at: type: integer + description: Unix timestamp when BeatAPI accepted the task. updated_at: type: integer + description: Unix timestamp of the latest task update. completed_at: type: [integer, 'null'] + description: Terminal Unix timestamp, or null while work is in progress. output: description: Output is null until the task succeeds. oneOf: @@ -279,54 +405,129 @@ components: properties: media: type: array + description: BeatAPI-hosted result assets. items: type: object required: [type, url, mime_type] properties: type: type: string - enum: [video] + enum: [image, video] + description: Result asset type. url: type: string format: uri + description: BeatAPI-hosted HTTPS result URL. mime_type: type: string - example: video/mp4 + description: Result asset MIME type. + examples: [video/mp4, image/png, image/jpeg, image/webp] r2_url: type: string format: uri + description: Primary BeatAPI-hosted result URL for clients that need one canonical asset. usage: $ref: '#/components/schemas/TaskUsage' + description: USD reservation, settlement, refund, and optional billable duration for this task. request_id: type: string + description: Correlation ID to retain for logs and BeatAPI support. example: req_abc123 error_code: type: [string, 'null'] + description: Machine-readable terminal failure code, or null when no task failure is recorded. example: processing_timeout error_message: type: [string, 'null'] + description: Human-readable terminal failure detail, or null when no task failure is recorded. + Effect: + type: object + required: [id, object, name, description, output_type, category, tags, input, options, preview, version, status] + properties: + id: { type: string, example: video-muscle-max } + object: { type: string, enum: [effect] } + name: { type: string, example: Muscle Transformation } + description: { type: string } + output_type: { type: string, enum: [image, video] } + category: { type: string, example: transformation } + tags: { type: array, items: { type: string } } + input: + type: object + required: [images_min, images_max, accepted_types] + properties: + images_min: { type: integer, minimum: 1 } + images_max: { type: integer, minimum: 1 } + accepted_types: + type: array + items: { type: string, enum: [image/jpeg, image/png, image/webp] } + max_size_mb: + type: integer + minimum: 1 + description: Maximum downloaded bytes per input image. When omitted, BeatAPI enforces 50 MB. + max_dimension_px: + type: integer + minimum: 1 + description: Maximum decoded width or height. BeatAPI inspects the actual image header before charging. + subject_requirements: { type: array, items: { type: string } } + options: + type: object + properties: + aspect_ratios: { type: array, items: { type: string } } + resolutions: { type: array, items: { type: string } } + duration_seconds: { type: array, items: { type: integer } } + bgm: { type: boolean } + seed: { type: boolean } + preview: + type: object + required: [cover_url, media_url] + properties: + cover_url: { type: [string, 'null'], format: uri } + media_url: { type: [string, 'null'], format: uri } + version: { type: integer, minimum: 1 } + status: { type: string, enum: [testing, active, paused] } + EffectResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/Effect' } + EffectListResponse: + type: object + required: [data] + properties: + data: + type: object + required: [object, data] + properties: + object: { type: string, enum: [list] } + data: { type: array, items: { $ref: '#/components/schemas/Effect' } } File: type: object required: [id, object, url, key, mime_type, size_bytes, purpose, created_at] properties: id: type: string + description: Stable uploaded file ID. example: file_3xYz9 object: type: string enum: [file] + description: Object discriminator; always `file`. url: type: string format: uri + description: Long-lived BeatAPI HTTPS URL to use in workflow or model requests. example: https://media.beatapi.io/inputs/file_3xYz9.mp3 key: type: string + description: BeatAPI storage key for support and diagnostics. example: inputs/file_3xYz9.mp3 mime_type: type: string + description: Accepted MIME type detected for the uploaded file. example: audio/mpeg size_bytes: type: integer + description: Uploaded file size in bytes. example: 1048576 audio_duration_seconds: type: number @@ -339,8 +540,10 @@ components: purpose: type: string enum: [input] + description: File purpose; currently always `input`. created_at: type: integer + description: Unix timestamp when the file was stored. example: 1782210000 WebhookEndpoint: type: object @@ -348,33 +551,41 @@ components: properties: id: type: string + description: Stable webhook endpoint ID used for get, update, and delete operations. example: wh_9aBcD object: type: string enum: [webhook_endpoint] + description: Object discriminator; always `webhook_endpoint`. url: type: string format: uri + description: Public HTTPS callback URL receiving subscribed task events. example: https://example.com/beatapi-webhook description: type: string + description: Account-defined label for the endpoint. example: Production webhook events: type: array + description: Task event types delivered to this endpoint. items: type: string enum: [task.succeeded, task.failed] status: type: string enum: [active, disabled] + description: Delivery status. Disabled endpoints do not receive events. secret: type: string description: Returned in full only when the endpoint is created. Later responses return a masked value. example: whsec_example_masked created_at: type: integer + description: Unix timestamp when the endpoint was created. updated_at: type: integer + description: Unix timestamp when the endpoint last changed. WebhookEvent: type: object required: [id, event, created_at, data] @@ -406,28 +617,340 @@ components: properties: data: $ref: '#/components/schemas/WorkflowList' + GenerationModel: + type: object + additionalProperties: false + required: [id, object, name, media_type, input_modes] + properties: + id: + type: string + enum: [nano-banana, nano-banana-pro, gpt-image-2, seedream-5-pro, minimax-h3, seedance-2, seedance-2-fast, seedance-2-mini, veo-3.1, seedance-2.5, kling-3] + object: { type: string, enum: [generation_model] } + name: { type: string } + media_type: { type: string, enum: [image, video] } + input_modes: + type: array + items: { type: string, enum: [text, image, frames, reference] } + GenerationModelList: + type: object + required: [object, data] + properties: + object: { type: string, enum: [list] } + data: + type: array + items: { $ref: '#/components/schemas/GenerationModel' } + GenerationModelListResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/GenerationModelList' } + ImageGenerationTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/NanoBananaImageRequest' + - $ref: '#/components/schemas/NanoBananaProImageRequest' + - $ref: '#/components/schemas/GptImage2Request' + - $ref: '#/components/schemas/Seedream5ProImageRequest' + discriminator: + propertyName: model + mapping: + nano-banana: '#/components/schemas/NanoBananaImageRequest' + nano-banana-pro: '#/components/schemas/NanoBananaProImageRequest' + gpt-image-2: '#/components/schemas/GptImage2Request' + seedream-5-pro: '#/components/schemas/Seedream5ProImageRequest' + NanoBananaImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: nano-banana, description: Must be `nano-banana`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation instructions. } + aspect_ratio: + type: string + enum: ['1:1', '9:16', '16:9', '3:4', '4:3', '3:2', '2:3', '5:4', '4:5', '21:9', auto] + default: '1:1' + description: Output image aspect ratio. + output_format: { type: string, enum: [png, jpeg], default: png, description: Output image file format. } + NanoBananaProImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: nano-banana-pro, description: Must be `nano-banana-pro`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 8 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: ['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9', auto] + default: '1:1' + description: Output image aspect ratio. + resolution: { type: string, enum: [1K, 2K, 4K], default: 1K, description: Output resolution tier. } + output_format: { type: string, enum: [png, jpg], default: png, description: Output image file format. } + GptImage2Request: + type: object + additionalProperties: false + required: [model, prompt] + description: | + `auto` only supports 1K. `1:1` does not support 4K. At 2K/4K, + `5:4`, `4:5`, `3:1`, `1:3`, and `9:21` are unavailable. + properties: + model: { type: string, const: gpt-image-2, description: Must be `gpt-image-2`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 16 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: [auto, '1:1', '3:2', '2:3', '4:3', '3:4', '5:4', '4:5', '16:9', '9:16', '2:1', '1:2', '3:1', '1:3', '21:9', '9:21'] + default: auto + description: Output image aspect ratio. Availability also depends on resolution. + resolution: { type: string, enum: [1K, 2K, 4K], default: 1K, description: Output resolution tier. } + Seedream5ProImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: seedream-5-pro, description: Must be `seedream-5-pro`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 10 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: [auto, '1:1', '4:3', '3:4', '16:9', '9:16', '3:2', '2:3', '21:9'] + default: '1:1' + description: Output image aspect ratio. + resolution: { type: string, enum: [1K, 2K], default: 1K, description: Output resolution tier. } + output_format: { type: string, enum: [png, jpeg], default: png, description: Output image file format. } + VideoGenerationTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/MinimaxH3VideoRequest' + - $ref: '#/components/schemas/Seedance2VideoRequest' + - $ref: '#/components/schemas/Seedance2FastVideoRequest' + - $ref: '#/components/schemas/Seedance2MiniVideoRequest' + - $ref: '#/components/schemas/Veo31VideoRequest' + - $ref: '#/components/schemas/Seedance25VideoRequest' + - $ref: '#/components/schemas/Kling3VideoRequest' + discriminator: + propertyName: model + mapping: + minimax-h3: '#/components/schemas/MinimaxH3VideoRequest' + seedance-2: '#/components/schemas/Seedance2VideoRequest' + seedance-2-fast: '#/components/schemas/Seedance2FastVideoRequest' + seedance-2-mini: '#/components/schemas/Seedance2MiniVideoRequest' + veo-3.1: '#/components/schemas/Veo31VideoRequest' + seedance-2.5: '#/components/schemas/Seedance25VideoRequest' + kling-3: '#/components/schemas/Kling3VideoRequest' + MinimaxH3VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input.' + properties: + model: { type: string, const: minimax-h3, description: Must be `minimax-h3`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [768P, 2K], default: 768P, description: Output resolution tier. } + Seedance2VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2, description: Must be `seedance-2`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p, 1080p, 4k], default: 720p, description: Output resolution tier. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + Seedance2FastVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2-fast, description: Must be `seedance-2-fast`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p], default: 720p, description: Output resolution tier. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + Seedance2MiniVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: 'Low-cost Seedance 2.0 route. `images` cannot be combined with any `reference_*` input. Generated audio is not supported.' + properties: + model: { type: string, const: seedance-2-mini, description: Must be `seedance-2-mini`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p], default: 720p, description: Output resolution tier. } + Veo31VideoRequest: + allOf: + - oneOf: + - $ref: '#/components/schemas/Veo31TextOrFrameVideoRequest' + - $ref: '#/components/schemas/Veo31ReferenceVideoRequest' + Veo31TextOrFrameVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: | + Veo 3.1 text or first/last-frame generation. Output is fixed at 8 seconds + and defaults to the Quality tier. + properties: + model: { type: string, const: veo-3.1, description: Must be `veo-3.1`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + aspect_ratio: { type: string, enum: ['16:9', '9:16', auto], default: '16:9', description: Output video aspect ratio. } + quality: { type: string, enum: [Quality, Fast, Lite], default: Quality, description: Text or frame generation tier. } + watermark: { type: string, description: Optional watermark text forwarded to the selected model. } + enable_translation: { type: boolean, description: Allow prompt translation before generation. } + Veo31ReferenceVideoRequest: + type: object + additionalProperties: false + required: [model, prompt, reference_images] + description: | + Veo 3.1 reference-image generation. Output is fixed at 8 seconds and + supports the Fast or Lite tier, defaulting to Fast. + properties: + model: { type: string, const: veo-3.1, description: Must be `veo-3.1`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + reference_images: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS reference images., items: { type: string, format: uri, pattern: '^https://' } } + aspect_ratio: { type: string, enum: ['16:9', '9:16', auto], default: '16:9', description: Output video aspect ratio. } + quality: { type: string, enum: [Fast, Lite], default: Fast, description: Reference-image generation tier. } + watermark: { type: string, description: Optional watermark text forwarded to the selected model. } + enable_translation: { type: boolean, description: Allow prompt translation before generation. } + Seedance25VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2.5, description: Must be `seedance-2.5`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 30, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 10, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 10, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 30, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, const: 720p, default: 720p, description: Seedance 2.5 currently returns 720p output. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + seed: { type: integer, minimum: -1, maximum: 4294967295, default: -1, description: Reproducibility seed. Use -1 for a random seed. } + KlingShot: + type: object + additionalProperties: false + required: [prompt, duration] + properties: + prompt: { type: string, minLength: 1, maxLength: 500, description: Instructions for this shot. } + duration: { type: integer, minimum: 1, maximum: 12, description: Shot duration in seconds. All shot durations must sum to the task duration. } + KlingElement: + type: object + additionalProperties: false + required: [name, element_input_urls] + description: Use 2-4 image URLs or one video URL. A video element may include one audio URL and a 3-8 second segment in milliseconds. + properties: + name: { type: string, minLength: 1, description: Stable name used to reference this element in the prompt. } + description: { type: string, description: Optional description of the subject or object. } + element_input_urls: + type: array + minItems: 1 + maxItems: 4 + description: Two to four image URLs, or one video URL. + items: { type: string, format: uri, pattern: '^https://' } + element_input_audio_urls: + type: array + minItems: 1 + maxItems: 1 + description: Optional audio URL used with a video element. + items: { type: string, format: uri, pattern: '^https://' } + start_time: { type: integer, minimum: 0, maximum: 30000, description: Video element segment start time in milliseconds. } + end_time: { type: integer, minimum: 0, maximum: 30000, description: Video element segment end time in milliseconds. The segment must be 3-8 seconds. } + Kling3VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: Multi-shot mode accepts one first-frame image, requires `multi_prompt`, and defaults sound to true. Shot durations must sum to `duration`. + properties: + model: { type: string, const: kling-3, description: Must be `kling-3`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs. Multi-shot mode accepts exactly one., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 3, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: + type: string + enum: ['16:9', '9:16', '1:1'] + description: Defaults to 16:9 for text generation. Omit it with frame images to adapt to the input aspect ratio. + resolution: { type: string, enum: [std, pro, 4K], default: pro, description: Output quality tier. } + sound: { type: boolean, description: Generate synchronized sound. Defaults to true in multi-shot mode. } + multi_shots: { type: boolean, default: false, description: Enable storyboard-style multi-shot generation. } + multi_prompt: + type: array + minItems: 1 + maxItems: 5 + description: Shot definitions required when `multi_shots=true`. + items: { $ref: '#/components/schemas/KlingShot' } + elements: + type: array + maxItems: 3 + description: Up to three reusable subject or object references. + items: { $ref: '#/components/schemas/KlingElement' } TaskResponse: type: object required: [data] properties: data: $ref: '#/components/schemas/Task' + description: Accepted or current BeatAPI task state. Usage: type: object - required: [object, credit_balance, total_tasks, credits_settled, credits_refunded, concurrency, by_workflow] + required: [object, credit_balance, total_tasks, credits_settled, credits_refunded, concurrency, by_workflow, by_capability, by_model, by_api_key] properties: object: type: string enum: [usage] credit_balance: - type: integer - description: Current credit balance. It may be negative. + type: number + format: double + multipleOf: 0.01 + description: Current USD balance. The compatibility field name is retained; 1 Credit equals $1 USD. The balance may be negative. total_tasks: type: integer credits_settled: - type: integer + type: number + format: double + multipleOf: 0.01 credits_refunded: - type: integer + type: number + format: double + multipleOf: 0.01 concurrency: type: object required: [limit, active] @@ -437,9 +960,10 @@ components: example: 2 active: type: integer - description: Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled credits without counting toward this value. + description: Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled USD usage without counting toward this value. by_workflow: type: array + description: Compatibility view containing workflow tasks only. Image, video, and Effect tasks are reported under by_capability instead. items: type: object required: [workflow, tasks, credits_settled] @@ -450,7 +974,54 @@ components: tasks: type: integer credits_settled: + type: number + format: double + multipleOf: 0.01 + by_capability: + type: array + items: + type: object + required: [task_kind, capability_id, tasks, credits_settled] + properties: + task_kind: + type: string + enum: [workflow, effect, image, video] + capability_id: + type: string + tasks: + type: integer + credits_settled: + type: number + format: double + multipleOf: 0.01 + by_model: + type: array + items: + type: object + required: [media_type, model, tasks, credits_settled] + properties: + media_type: + type: string + enum: [image, video] + model: + type: string + tasks: type: integer + credits_settled: + type: number + format: double + multipleOf: 0.01 + by_api_key: + type: array + items: + type: object + required: [api_key_id, title, key_prefix, tasks, credits_settled] + properties: + api_key_id: { type: string } + title: { type: string } + key_prefix: { type: string } + tasks: { type: integer } + credits_settled: { type: number, format: double, multipleOf: 0.01 } realtime: type: object required: [sessions, credits, active] @@ -459,8 +1030,10 @@ components: type: integer description: Total BeatAPI realtime sessions for this account. credits: - type: integer - description: Credits settled by connected realtime sessions. + type: number + format: double + multipleOf: 0.01 + description: USD amount settled by connected realtime sessions. active: type: integer description: Realtime sessions in ready, connecting, or active state. @@ -470,6 +1043,213 @@ components: properties: data: $ref: '#/components/schemas/Usage' + MusicVideoTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/StandardMusicVideoTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoTaskCreateRequest' + discriminator: + propertyName: mv_tier + mapping: + standard: '#/components/schemas/StandardMusicVideoTaskCreateRequest' + premium: '#/components/schemas/PremiumMusicVideoTaskCreateRequest' + StandardMusicVideoTaskCreateRequest: + type: object + required: [images, audio_url] + allOf: + - if: + required: [lip_sync] + properties: + lip_sync: { const: true } + then: + required: [lip_ref_url] + properties: + lip_ref_url: {} + not: + anyOf: + - required: [mv_mode] + properties: { mv_mode: {} } + - required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_tier: + type: string + enum: [standard] + default: standard + description: May be omitted to preserve the backwards-compatible Standard contract. + images: + type: array + minItems: 1 + maxItems: 7 + description: Standard scene images. Provide 1-7 public HTTPS PNG, JPEG, or WebP URLs; place the primary subject or opening scene first. Upload local files through `POST /v1/files` and use the returned `data.url`. + items: { type: string, format: uri } + audio_url: + type: string + format: uri + description: Public HTTPS audio URL; Standard audio must be 10-180 seconds. + prompt: { type: string, maxLength: 3000, description: "Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters." } + language: { type: string, enum: [en, zh], description: Dialogue and lyric language used by the Standard workflow. } + quality: { type: string, enum: [standard, high], default: standard, description: Generation quality tier. High quality is unavailable at 540p. } + style: { type: string, maxLength: 200, description: "Optional concise visual style, such as cinematic, anime, documentary, or fashion editorial." } + aspect_ratio: { type: string, enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], description: Target output placement. Set explicitly for the destination player or social feed. } + resolution: { type: string, enum: [540p, 720p, 1080p], default: 720p, description: Output resolution. 540p cannot be combined with high quality or lip sync. } + lip_sync: + type: boolean + default: false + description: Generate lip-synchronized performance. When true, `lip_ref_url` is required. + lip_ref_url: + type: string + format: uri + description: Public HTTPS close-up, front-facing face image used for Standard lip sync. + add_subtitle: { type: boolean, default: false, description: Burn generated or supplied subtitles into the final video. } + subtitle_color: { type: string, pattern: '^#[0-9A-Fa-f]{6}$', example: '#FFFFFF', description: Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. } + srt_url: { type: string, format: uri, description: Optional public HTTPS `.srt` subtitle file. Upload a local subtitle through `POST /v1/files`. } + duration: + type: integer + minimum: 10 + maximum: 180 + description: Billing fallback only; detected audio duration wins. + compose_mode: + type: string + enum: [auto, manual] + default: auto + description: Auto composes the final Music Video; manual pauses at `requires_action` so shots can be reviewed or edited before compose. + PremiumMusicVideoTaskCreateRequest: + allOf: + - oneOf: + - $ref: '#/components/schemas/PremiumMusicVideoSingTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoSingPerformTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoDanceTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoPerformTaskCreateRequest' + discriminator: + propertyName: mv_mode + mapping: + sing: '#/components/schemas/PremiumMusicVideoSingTaskCreateRequest' + sing_perform: '#/components/schemas/PremiumMusicVideoSingPerformTaskCreateRequest' + dance: '#/components/schemas/PremiumMusicVideoDanceTaskCreateRequest' + perform: '#/components/schemas/PremiumMusicVideoPerformTaskCreateRequest' + PremiumMusicVideoTaskRequestBase: + type: object + required: [mv_tier, mv_mode, audio_url] + not: + anyOf: + - required: [quality] + properties: { quality: {} } + - required: [language] + properties: { language: {} } + - required: [lip_sync] + properties: { lip_sync: {} } + - required: [lip_ref_url] + properties: { lip_ref_url: {} } + - required: [srt_url] + properties: { srt_url: {} } + - required: [compose_mode] + properties: { compose_mode: {} } + properties: + mv_tier: { type: string, enum: [premium], description: Selects the Premium Music Video workflow and its mode-specific inputs. } + mv_mode: { type: string, enum: [sing, sing_perform, dance, perform], description: Premium performance mode. Sing modes require `lip_ref_urls`; dance and perform require exactly six `images`. } + audio_url: + type: string + format: uri + description: Public HTTPS audio URL; Premium audio must be 10-300 seconds. + prompt: { type: string, maxLength: 3000, description: "Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters." } + style: { type: string, maxLength: 200 } + aspect_ratio: { type: string, enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], description: Target output placement. Set explicitly for the destination player or social feed. } + resolution: + type: string + enum: [720p] + default: 720p + description: Premium output is fixed to 720p. + add_subtitle: { type: boolean, default: false, description: Burn generated subtitles into the final video. } + subtitle_color: { type: string, pattern: '^#[0-9A-Fa-f]{6}$', example: '#FFFFFF', description: Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. } + duration: + type: integer + minimum: 10 + maximum: 300 + description: Premium billing fallback only; detected audio duration wins. + PremiumMusicVideoSingTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [lip_ref_urls] + properties: + mv_mode: { type: string, enum: [sing] } + images: + type: array + minItems: 0 + maxItems: 6 + description: Optional Premium scene images for sing mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. + items: { type: string, format: uri } + lip_ref_urls: + type: array + minItems: 1 + maxItems: 2 + description: Required for sing mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. + items: { type: string, format: uri } + PremiumMusicVideoSingPerformTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [lip_ref_urls] + properties: + mv_mode: { type: string, enum: [sing_perform] } + images: + type: array + minItems: 0 + maxItems: 6 + description: Optional Premium scene images for sing and perform mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. + items: { type: string, format: uri } + lip_ref_urls: + type: array + minItems: 1 + maxItems: 2 + description: Required for sing and perform mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. + items: { type: string, format: uri } + PremiumMusicVideoDanceTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [images] + not: + required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_mode: { type: string, enum: [dance] } + images: + type: array + minItems: 6 + maxItems: 6 + description: Required for dance mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. + items: { type: string, format: uri } + PremiumMusicVideoPerformTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [images] + not: + required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_mode: { type: string, enum: [perform] } + images: + type: array + minItems: 6 + maxItems: 6 + description: Required for perform mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. + items: { type: string, format: uri } + EditMusicVideoShotRequest: + type: object + additionalProperties: false + required: [prompt] + properties: + prompt: + type: string + maxLength: 3000 + images: + type: array + minItems: 0 + maxItems: 6 + description: Premium tasks only. Optional replacement scene images; an empty array is treated as omitted. Standard tasks reject this field. + items: { type: string, format: uri } RealtimeSession: type: object required: [id, object, status, expires_at, max_duration_seconds, allowed_origins, credits, request_id, created_at, connected_at, closed_at] @@ -477,41 +1257,66 @@ components: id: type: string pattern: '^rts_' + description: Stable Realtime Session ID used to inspect or close the session. object: type: string enum: [realtime.session] + description: Object discriminator; always `realtime.session`. status: type: string enum: [ready, connecting, active, closed, failed, expired] description: Active means BeatAPI accepted the first billing heartbeat after remote output began. - client_secret: - type: string - description: Returned only by POST. Give this short-lived BeatAPI secret to the browser SDK; never give the browser an sk_ API key. - pattern: '^brt_live_' expires_at: type: string format: date-time + description: Time when the unconnected short-lived session credential expires. max_duration_seconds: type: integer enum: [15, 60, 300] + description: Maximum selected live duration and billing tier in seconds. allowed_origins: type: array - items: { type: string, format: uri } + description: Exact browser origins authorized to use this Session. + items: + type: string + format: uri + pattern: '^(https://[A-Za-z0-9.-]+(?::[0-9]+)?|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]+)?)$' + description: Exact browser origin. Use HTTPS in production; HTTP is accepted only for localhost development. + example: https://app.example.com credits: type: object required: [reserved, settled, refunded] + description: USD reservation, settlement, and refund lifecycle for this Realtime Session. Compatibility field names are retained. properties: - reserved: { type: integer } - settled: { type: integer } - refunded: { type: integer } + reserved: { type: number, format: double, multipleOf: 0.01, description: USD amount reserved when the Session is created. } + settled: { type: number, format: double, multipleOf: 0.01, description: USD amount settled after the first accepted billing heartbeat. } + refunded: { type: number, format: double, multipleOf: 0.01, description: USD amount refunded if the Session ends without billing activation. } request_id: type: string - created_at: { type: string, format: date-time } + description: Correlation ID to retain for logs and BeatAPI support. + created_at: { type: string, format: date-time, description: Time when the Session was created. } connected_at: type: [string, 'null'] format: date-time description: Time of the first accepted BeatAPI billing heartbeat; null before billing activation. - closed_at: { type: [string, 'null'], format: date-time } + closed_at: { type: [string, 'null'], format: date-time, description: "Time when the Session closed, or null while it remains open." } + RealtimeSessionCreated: + allOf: + - $ref: '#/components/schemas/RealtimeSession' + - type: object + required: [client_secret] + properties: + client_secret: + type: string + description: Short-lived BeatAPI browser credential returned only by POST. Never expose an sk_ API key to the browser. + pattern: '^brt_live_' + RealtimeSessionCreateResponse: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/RealtimeSessionCreated' + description: Created Realtime Session including the one-time short-lived browser credential. RealtimeSessionResponse: type: object required: [data] @@ -524,6 +1329,7 @@ components: properties: data: $ref: '#/components/schemas/File' + description: Uploaded file metadata and the public HTTPS URL to use in later requests. WebhookEndpointList: type: object required: [object, data] @@ -547,6 +1353,7 @@ components: properties: data: $ref: '#/components/schemas/WebhookEndpoint' + description: Created or retrieved webhook endpoint. Public API responses return the full signing secret at creation and mask it afterward; authenticated dashboard owners can explicitly reveal it again. DeleteResponse: type: object required: [data] @@ -565,10 +1372,12 @@ components: properties: error: type: object + description: Structured BeatAPI error. Use `code` for program logic and retain `request_id` for support. required: [code, message, request_id] properties: code: type: string + description: Stable machine-readable error code. enum: - bad_request - unauthorized @@ -578,6 +1387,7 @@ components: - idempotency_conflict - user_concurrency_exceeded - rate_limit_exceeded + - content_policy_violation - processing_unavailable - processing_failed - processing_timeout @@ -592,8 +1402,10 @@ components: - internal_error message: type: string + description: Human-readable detail intended for logs and debugging. request_id: type: string + description: Correlation ID to retain for BeatAPI support. retry_after_seconds: type: integer description: Present on retryable rate-limit or capacity responses when the client should wait before retrying. @@ -637,6 +1449,28 @@ components: message: Too many polling requests. Poll every 5-10 seconds. request_id: req_xxx retry_after_seconds: 12 + InternalError: + description: BeatAPI could not complete the request because of an internal or storage failure. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: internal_error + message: Internal error. Contact support with the request_id if the problem continues. + request_id: req_xxx + ProcessingUnavailable: + description: BeatAPI processing is temporarily unavailable or did not complete within the processing window. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: processing_unavailable + message: Task processing is temporarily unavailable. + request_id: req_xxx paths: /v1/workflows: get: @@ -667,32 +1501,439 @@ paths: '429': $ref: '#/components/responses/RateLimited' + /v1/media/models: + get: + operationId: listGenerationModels + tags: [Generation] + summary: List BeatAPI image and video generation models + description: Returns stable BeatAPI model aliases and public input modes. Internal execution routing is not part of this contract. + security: [] + parameters: + - in: query + name: media_type + schema: { type: string, enum: [image, video] } + responses: + '200': + description: Generation model list + content: + application/json: + schema: { $ref: '#/components/schemas/GenerationModelListResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/images/tasks: + post: + operationId: createImageGenerationTask + tags: [Generation] + summary: Create an image generation task + description: | + Creates one asynchronous image task. Select the model-specific request + contract with `model`, save the returned `data.id`, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. + security: [{ BearerAuth: [] }] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/ImageGenerationTaskCreateRequest' } + examples: + Nano Banana: + summary: Nano Banana + value: + model: nano-banana + prompt: Editorial product photograph on a warm stone pedestal. + aspect_ratio: '1:1' + output_format: png + Nano Banana Pro: + summary: Nano Banana Pro + value: + model: nano-banana-pro + prompt: Place the supplied product in a premium editorial studio scene. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '4:5' + resolution: 2K + output_format: png + GPT Image 2: + summary: GPT Image 2 + value: + model: gpt-image-2 + prompt: Create a clean campaign image from the supplied product reference. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '1:1' + resolution: 2K + Seedream 5 Pro: + summary: Seedream 5 Pro + value: + model: seedream-5-pro + prompt: Recompose the product as a cinematic storefront campaign image. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '16:9' + resolution: 2K + output_format: png + responses: + '201': + description: Image generation task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + examples: + Nano Banana Pro: + summary: Nano Banana Pro + value: + data: + id: task_8K2qA + object: task + task_kind: image + capability_id: nano-banana-pro + capability_version: null + media_type: image + model: nano-banana-pro + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 0.09 + credits_charged: 0.09 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': { description: Insufficient USD balance, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '409': { description: Idempotency key conflicts with another request body, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/videos/tasks: + post: + operationId: createVideoGenerationTask + tags: [Generation] + summary: Create a video generation task + description: | + Creates one asynchronous video task. Select the model-specific request + contract with `model`, save the returned `data.id`, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. + security: [{ BearerAuth: [] }] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/VideoGenerationTaskCreateRequest' } + examples: + MiniMax H3: + summary: MiniMax H3 + value: + model: minimax-h3 + prompt: A slow cinematic push through a misty mountain village at dawn. + duration: 5 + aspect_ratio: '16:9' + resolution: 768P + Seedance 2: + summary: Seedance 2 + value: + model: seedance-2 + prompt: A handheld tracking shot through a crowded neon night market. + duration: 8 + aspect_ratio: '16:9' + resolution: 1080p + generate_audio: true + Seedance 2 Fast: + summary: Seedance 2 Fast + value: + model: seedance-2-fast + prompt: A fast cinematic production draft through a neon night market. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + generate_audio: true + Seedance 2 Mini: + summary: Seedance 2 Mini + value: + model: seedance-2-mini + prompt: A low-cost storyboard draft for a product reveal. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + Veo 3.1 Quality: + summary: Veo 3.1 Quality + value: + model: veo-3.1 + prompt: A cinematic aerial reveal of a quiet coastal village at sunrise. + aspect_ratio: '16:9' + quality: Quality + Veo 3.1 Fast: + summary: Veo 3.1 Fast + value: + model: veo-3.1 + prompt: A fast cinematic product reveal with natural camera motion. + aspect_ratio: '16:9' + quality: Fast + Veo 3.1 Lite: + summary: Veo 3.1 Lite + value: + model: veo-3.1 + prompt: A concise storyboard-ready product reveal. + aspect_ratio: '16:9' + quality: Lite + Veo 3.1 Reference Fast: + summary: Veo 3.1 Reference Fast + value: + model: veo-3.1 + prompt: Create a cohesive cinematic scene using the supplied visual references. + reference_images: + - https://media.beatapi.io/samples/neon-singer.png + - https://media.beatapi.io/samples/smart-bottle.png + aspect_ratio: '16:9' + quality: Fast + Seedance 2.5: + summary: Seedance 2.5 + value: + model: seedance-2.5 + prompt: A cinematic tracking shot through a rain-lit night market. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + generate_audio: true + seed: -1 + Kling 3: + summary: Kling 3 + value: + model: kling-3 + prompt: A dramatic product reveal with a slow orbiting camera move. + duration: 5 + aspect_ratio: '16:9' + resolution: pro + sound: true + responses: + '201': + description: Video generation task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + examples: + MiniMax H3: + summary: MiniMax H3 + value: + data: + id: task_8K2qA + object: task + task_kind: video + capability_id: minimax-h3 + capability_version: null + media_type: video + model: minimax-h3 + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 0.2 + credits_charged: 0.2 + billable_duration_seconds: 5 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': { description: Insufficient USD balance, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '409': { description: Idempotency key conflicts with another request body, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/effects: + get: + operationId: listEffects + tags: [Effects] + summary: List active Effects + security: [] + description: Returns only versioned Effects that have passed BeatAPI publication gates. Internal integration names, template ids, costs, and execution context are never exposed. + parameters: + - in: query + name: output_type + schema: { type: string, enum: [image, video] } + - in: query + name: category + schema: { type: string } + responses: + '200': + description: Active Effect catalog + content: + application/json: + schema: { $ref: '#/components/schemas/EffectListResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/effects/{effect_id}: + get: + operationId: getEffect + tags: [Effects] + summary: Get an active Effect + security: [] + parameters: + - in: path + name: effect_id + required: true + schema: { type: string } + responses: + '200': + description: Effect definition and immutable current version contract + content: + application/json: + schema: { $ref: '#/components/schemas/EffectResponse' } + '404': + description: Effect is unknown or not currently published. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + + /v1/effects/tasks: + post: + operationId: createEffectTask + tags: [Effects] + summary: Create an Effect task + security: [{ BearerAuth: [] }] + description: | + Creates an asynchronous image or video Effect task. Read the catalog + first: image count, accepted input types, output resolution/duration, + and execution contract are fixed by the selected Effect version. Send + an `Idempotency-Key`; an exact replay returns the + accepted task before remote input URLs are revalidated, while a changed + body returns `idempotency_conflict`. + + The USD amount is reserved atomically when accepted, settled on success, and + fully refunded after a definite processing failure. An uncertain create + result is not blindly retried and never switches integrations automatically. + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: false + required: [effect_id, images] + properties: + effect_id: { type: string, example: video-muscle-max, description: Stable published Effect ID from `GET /v1/effects`. } + effect_version: + type: integer + minimum: 1 + description: Optional immutable version. Omit to use the current published version. + images: + type: array + minItems: 1 + maxItems: 7 + description: Public HTTPS input images in the order required by the selected Effect version. Read `GET /v1/effects/{effect_id}` for the exact count and accepted media rules; upload local files with `POST /v1/files`. + items: { type: string, format: uri } + options: + type: object + additionalProperties: false + description: Optional controls supported by the selected Effect version. Omit unsupported controls; the catalog is the source of truth. + properties: + aspect_ratio: { type: string, description: Requested output aspect ratio when the selected Effect exposes this option. } + resolution: { type: string, description: Requested output resolution when the selected Effect exposes this option. } + duration: { type: integer, description: Requested video duration in seconds when the selected Effect exposes this option. } + bgm: { type: boolean, description: Include background music when supported by the selected Effect. } + seed: { type: integer, description: Optional deterministic seed when supported by the selected Effect. } + example: + effect_id: video-muscle-max + images: ['https://media.beatapi.io/samples/portrait.png'] + options: { resolution: 720p, duration: 12 } + responses: + '201': + description: Effect task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + example: + data: + id: task_effect123 + object: task + task_kind: effect + capability_id: video-muscle-max + capability_version: 1 + effect_id: video-muscle-max + effect_version: 1 + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: { credits_reserved: 1.2, credits_charged: 1.2, credits_settled: 0, credits_refunded: 0 } + request_id: req_effect123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': + description: Insufficient USD balance. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '404': + description: Effect or requested version is unavailable. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '409': + description: Idempotency key conflicts with another request body. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '429': { $ref: '#/components/responses/RateLimited' } + /v1/music-video/tasks: post: operationId: createMusicVideoTask tags: [Music Video] x-apidog-folder: Music Video API/Create Video - summary: Create Music Video + summary: Create a Music Video workflow task security: - BearerAuth: [] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + example: mv-create-cus_123-01 description: | - Music Video requires public HTTPS image URLs and a public HTTPS audio URL. - Prompt, language, quality, style, lip reference, subtitle, and format - controls are optional. BeatAPI detects the audio duration before task - creation and charges the detected billable duration at the selected - per-second customer-credit rate. If audio duration cannot be detected, - `duration` is used as the billing fallback. + Music Video requires public HTTPS media URLs. Requests that omit + `mv_tier` use `standard` and preserve the existing API behavior. + Premium retains the configured integration host but uses a distinct + execution path selected internally by BeatAPI. + Shared controls include prompt, aspect ratio, subtitles, and the tier's + billing fallback. Language, quality, `lip_sync`, `lip_ref_url`, + `srt_url`, and `compose_mode` are Standard-only. Premium uses `mv_mode` + plus `style` and mode-specific images or `lip_ref_urls`. BeatAPI detects audio duration before task + creation and records the billable duration in Task usage. If audio + duration cannot be detected, `duration` is used as the billing fallback. Input limits: - - Images must contain 1-7 public HTTPS URLs. + - Standard images must contain 1-7 public HTTPS URLs. + - Premium `sing` and `sing_perform` accept 0-6 scene images and require 1-2 `lip_ref_urls`. + - Premium `dance` and `perform` require exactly 6 scene images. - Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller. - Image aspect ratio should be between 1:4 and 4:1. - - Audio must be a public HTTPS mp3, wav, aac, or m4a URL between 10 and 180 seconds. + - Standard audio must be 10-180 seconds; Premium audio must be 10-300 seconds and contain vocals or lyrics rather than instrumental-only audio. - The audio file should be 50 MB or smaller. - `prompt` is optional and must be at most 3000 characters. - - `lip_ref_url`, when provided, must be a public HTTPS image URL. Use a clear, front-facing close-up face reference for best lip-sync results. - - `srt_url`, when provided, must point to an `.srt` subtitle file. - - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; it must be 10-180 seconds and cannot override a detected audio duration. + - Standard `lip_sync=true` requires `lip_ref_url`. It must be a public HTTPS image URL showing a clear, front-facing close-up face. + - Standard `srt_url`, when provided, must point to an `.srt` subtitle file. + - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; Standard accepts 10-180 seconds and Premium accepts 10-300 seconds. It cannot override a detected audio duration. BeatAPI validates URL shape, text limits, enum values, combination limits, and audio duration at task creation. Files uploaded through @@ -700,15 +1941,6 @@ paths: Third-party media URLs must follow the same media requirements and may be rejected during processing if invalid. - Customer pricing: - - MV 540p standard: 4 credits/s - - MV 720p standard: 5 credits/s - - MV 1080p standard: 6 credits/s - - lip_sync add-on: +2 credits/s - - MV 720p high: 16 credits/s - - MV 1080p high: 18 credits/s - - Ecommerce Video 1080p: 15 credits/s - Combination limits: - `quality=high` is not supported with `resolution=540p`. - `lip_sync=true` is not supported with `resolution=540p`. @@ -724,76 +1956,23 @@ paths: content: application/json: schema: - type: object - required: [images, audio_url] - properties: - images: - type: array - minItems: 1 - maxItems: 7 - description: 1-7 public HTTPS image URLs. Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller, with aspect ratio from 1:4 to 4:1. /v1/files uploads are checked before use; third-party URLs may be rejected during processing if invalid. - items: - type: string - format: uri - audio_url: - type: string - format: uri - description: Public HTTPS audio URL. Use mp3, wav, aac, or m4a; file size should be 50 MB or smaller and duration must be 10-180 seconds. - prompt: - type: string - maxLength: 3000 - description: Optional creative prompt, at most 3000 characters. - language: - type: string - enum: [en, zh] - lip_sync: - type: boolean - lip_ref_url: - type: string - format: uri - description: Public HTTPS image URL for lip-sync face reference. Use a clear, front-facing close-up face reference. - style: - type: string - maxLength: 200 - description: Optional style phrase, at most 200 characters. - quality: - type: string - enum: [standard, high] - default: standard - aspect_ratio: - type: string - enum: ['1:1', '16:9', '9:16', '4:3', '3:4'] - resolution: - type: string - enum: [540p, 720p, 1080p] - default: 720p - add_subtitle: - type: boolean - subtitle_color: - type: string - pattern: '^#[0-9A-Fa-f]{6}$' - example: '#FFFFFF' - srt_url: - type: string - format: uri - duration: - type: integer - minimum: 10 - maximum: 180 - description: Billing fallback when audio duration cannot be detected. It must be 10-180 seconds and cannot override a detected audio duration. - compose_mode: - type: string - enum: [auto, manual] - default: auto - example: - images: - - https://media.beatapi.io/samples/neon-singer.png - audio_url: https://media.beatapi.io/samples/neon-singer-preview.mp3 - prompt: Neon rooftop performance with metro cutaways and cinematic light trails. - language: en - quality: standard - resolution: 720p - compose_mode: auto + $ref: '#/components/schemas/MusicVideoTaskCreateRequest' + examples: + standard_backwards_compatible: + summary: Standard music video + value: + mv_tier: standard + images: ['https://media.beatapi.io/samples/neon-singer.png'] + audio_url: https://media.beatapi.io/samples/neon-singer-preview.mp3 + resolution: 720p + premium_sing: + value: { mv_tier: premium, mv_mode: sing, images: [], lip_ref_urls: ['https://media.beatapi.io/samples/singer.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_sing_perform: + value: { mv_tier: premium, mv_mode: sing_perform, images: ['https://media.beatapi.io/samples/stage.png'], lip_ref_urls: ['https://media.beatapi.io/samples/singer.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_dance: + value: { mv_tier: premium, mv_mode: dance, images: ['https://media.beatapi.io/1.png', 'https://media.beatapi.io/2.png', 'https://media.beatapi.io/3.png', 'https://media.beatapi.io/4.png', 'https://media.beatapi.io/5.png', 'https://media.beatapi.io/6.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_perform: + value: { mv_tier: premium, mv_mode: perform, images: ['https://media.beatapi.io/1.png', 'https://media.beatapi.io/2.png', 'https://media.beatapi.io/3.png', 'https://media.beatapi.io/4.png', 'https://media.beatapi.io/5.png', 'https://media.beatapi.io/6.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } responses: '201': description: Task accepted @@ -801,28 +1980,34 @@ paths: application/json: schema: $ref: '#/components/schemas/TaskResponse' - example: - data: - id: task_8K2qA - object: task - workflow: music-video - status: queued - stage: queued - storyboard: - shots: [] - created_at: 1782210000 - updated_at: 1782210000 - completed_at: null - output: null - usage: - credits_reserved: 75 - credits_charged: 75 - billable_duration_seconds: 15 - credits_settled: 0 - credits_refunded: 0 - request_id: req_abc123 - error_code: null - error_message: null + examples: + standard_backwards_compatible: + summary: Standard music video + value: + data: + id: task_8K2qA + object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 + workflow: music-video + status: queued + stage: queued + storyboard: + shots: [] + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 1.5 + credits_charged: 1.5 + billable_duration_seconds: 15 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null '400': $ref: '#/components/responses/BadRequest' '401': @@ -838,6 +2023,17 @@ paths: code: insufficient_credits message: Account balance is not sufficient for this task. request_id: req_xxx + '409': + description: The Idempotency-Key was reused with a different body or while another request with that key is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: idempotency_conflict + message: This Idempotency-Key was already used with a different request body. + request_id: req_xxx '429': description: User concurrency exceeded. content: @@ -855,13 +2051,16 @@ paths: operationId: editMusicVideoShot tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Edit Shot + summary: Edit a Music Video storyboard shot security: - BearerAuth: [] description: | Edit one storyboard shot using its BeatAPI `shot_id`. This operation - charges BeatAPI customer credits using the selected quality/resolution - rate and the shot duration. Default shot duration is 5 seconds. + charges the customer USD balance using the applicable task tier and shot + duration. Standard edits accept only `prompt`. Premium edits accept + `prompt` plus up to 6 optional replacement `images`. Generation quality, + resolution, and shot duration are inherited from the original task and + are not editable request fields. When the edit finishes, BeatAPI stores the edited shot media and exposes it on that shot. The existing final Music Video is not replaced until you call compose with the selected shot ids. @@ -878,35 +2077,21 @@ paths: schema: type: string example: shot_xxx + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key for this task, shot, and request body returns the accepted task without charging the USD amount again; changing any of them returns `409 idempotency_conflict`. + example: music-edit-task_8K2qA-shot_xxx-01 requestBody: required: true content: application/json: schema: - type: object - required: [prompt] - properties: - prompt: - type: string - maxLength: 3000 - duration: - type: integer - minimum: 1 - maximum: 180 - default: 5 - quality: - type: string - enum: [standard, high] - default: standard - resolution: - type: string - enum: [540p, 720p, 1080p] - default: 720p + $ref: '#/components/schemas/EditMusicVideoShotRequest' example: prompt: Night city chorus with brighter face lighting. - duration: 5 - quality: standard - resolution: 720p + images: ['https://media.beatapi.io/samples/stage.png'] responses: '202': description: Shot edit accepted @@ -918,19 +2103,37 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '402': + description: Account balance is not sufficient for this shot edit. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: The Idempotency-Key was reused for a different task, shot, or request body, or the same request is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '404': description: Task or shot not found. content: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/music-video/tasks/{task_id}/shots/{shot_id}/media: post: operationId: getMusicVideoShotMedia tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Get Shot Media + summary: Retrieve a Music Video storyboard shot media URL security: - BearerAuth: [] description: | @@ -989,18 +2192,24 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/music-video/tasks/{task_id}/compose: post: operationId: composeMusicVideoTask tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Compose Video + summary: Compose a Music Video task from selected shots security: - BearerAuth: [] description: | Compose selected BeatAPI storyboard shots into the final Music Video. - This operation charges a fixed 1 BeatAPI customer credit. + This operation charges a fixed $1 USD. parameters: - in: path name: task_id @@ -1008,6 +2217,12 @@ paths: schema: type: string example: task_8K2qA + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key for this task and request body returns the accepted task without charging the $1 compose amount again; changing either returns `409 idempotency_conflict`. + example: music-compose-task_8K2qA-01 requestBody: required: true content: @@ -1034,22 +2249,51 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '402': + description: Account balance is not sufficient for this compose operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: The Idempotency-Key was reused for a different task or request body, or the same request is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '404': description: Task or shot not found. content: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/ecommerce-video/tasks: post: operationId: createEcommerceVideoTask tags: [Ecommerce Video] x-apidog-folder: Ecommerce Video API - summary: Create Ecommerce Video + summary: Create an Ecommerce Video workflow task security: - BearerAuth: [] - description: Ecommerce Video requires product images and an explicit output duration. + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + example: ecommerce-create-cus_123-01 + description: | + Ecommerce Video creates a complete product video from public HTTPS product or + lifestyle images, an explicit duration, and optional creative direction. Upload + local images with `POST /v1/files`, save the returned Task ID, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. requestBody: required: true content: @@ -1062,6 +2306,7 @@ paths: type: array minItems: 1 maxItems: 7 + description: Primary product or scene image first, followed by up to six additional public HTTPS PNG, JPEG, or WebP product or lifestyle images. Upload local files with `POST /v1/files` and use the returned `data.url`. items: type: string format: uri @@ -1069,21 +2314,25 @@ paths: type: integer minimum: 10 maximum: 60 + description: Required target output duration in seconds and the basis for USD calculation. Allowed range is 10-60 seconds. prompt: type: string maxLength: 2000 + description: Optional creative direction, audience, product benefit, offer, tone, scenes, or call to action. Maximum 2000 characters. aspect_ratio: type: string enum: ['16:9', '9:16', '1:1'] + description: Target output placement. Use 16:9 for landscape, 9:16 for vertical social, or 1:1 for square placements; set explicitly for stable layout. language: type: string enum: [en, zh] + description: Dialogue and narration language. Use `en` for English or `zh` for Chinese; set explicitly when the prompt contains mixed languages. example: images: - https://media.beatapi.io/samples/smart-bottle.png duration: 15 prompt: Fast product launch ad for paid social. - aspect_ratio: 9:16 + aspect_ratio: '9:16' responses: '201': description: Task accepted @@ -1095,6 +2344,9 @@ paths: data: id: task_p9Lm2 object: task + task_kind: workflow + capability_id: ecommerce-video + capability_version: 1 workflow: ecommerce-video status: queued stage: queued @@ -1103,8 +2355,8 @@ paths: completed_at: null output: null usage: - credits_reserved: 225 - credits_charged: 225 + credits_reserved: 4.5 + credits_charged: 4.5 billable_duration_seconds: 15 credits_settled: 0 credits_refunded: 0 @@ -1126,6 +2378,17 @@ paths: code: insufficient_credits message: Account balance is not sufficient for this task. request_id: req_xxx + '409': + description: The Idempotency-Key was reused with a different body or while another request with that key is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: idempotency_conflict + message: This Idempotency-Key was already used with a different request body. + request_id: req_xxx '429': description: User concurrency exceeded. content: @@ -1168,6 +2431,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: queued stage: queued @@ -1178,8 +2444,8 @@ paths: completed_at: null output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 credits_settled: 0 credits_refunded: 0 @@ -1192,6 +2458,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: storyboard_ready stage: storyboard_ready @@ -1210,10 +2479,10 @@ paths: updated_at: 1782210300 output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 - credits_settled: 75 + credits_settled: 1.5 credits_refunded: 0 request_id: req_abc123 error_code: null @@ -1224,6 +2493,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: failed stage: failed @@ -1234,11 +2506,11 @@ paths: completed_at: 1782210600 output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 credits_settled: 0 - credits_refunded: 75 + credits_refunded: 1.5 request_id: req_abc123 error_code: processing_timeout error_message: Task waited too long for platform capacity. @@ -1262,9 +2534,9 @@ paths: security: - BearerAuth: [] description: | - Reserve credits and allocate a short-lived BeatAPI realtime session. Send a unique + Reserve the selected USD amount and allocate a short-lived BeatAPI realtime session. Send a unique `Idempotency-Key`; retries with the same user, key, and body return the same session - and deterministic short-lived `client_secret` without reserving credits or capacity + and deterministic short-lived `client_secret` without reserving funds or capacity twice. The browser receives only that BeatAPI secret and connects with `@beatapi/realtime`. @@ -1280,7 +2552,8 @@ paths: - in: header name: Idempotency-Key required: true - schema: { type: string, maxLength: 128 } + schema: { type: string, minLength: 1, maxLength: 128 } + example: rts-create-cus_123-01 requestBody: required: true content: @@ -1292,14 +2565,22 @@ paths: max_duration_seconds: type: integer enum: [15, 60, 300] + description: Required maximum live session duration in seconds. The USD amount is reserved for the selected 15, 60, or 300 second tier. allowed_origins: type: array minItems: 1 maxItems: 10 - items: { type: string, format: uri } + description: Exact browser origins allowed to use the short-lived session secret. + items: + type: string + format: uri + pattern: '^(https://[A-Za-z0-9.-]+(?::[0-9]+)?|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]+)?)$' + description: Exact browser origin. Use HTTPS in production; HTTP is accepted only for localhost development. + example: https://app.example.com metadata: type: object maxProperties: 20 + description: Optional server-defined string metadata for your own correlation. Up to 20 keys; keys are at most 64 characters and values at most 256 characters. propertyNames: { maxLength: 64 } additionalProperties: { type: string, maxLength: 256 } example: @@ -1311,11 +2592,25 @@ paths: description: Realtime session created content: application/json: - schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } + schema: { $ref: '#/components/schemas/RealtimeSessionCreateResponse' } + example: + data: + id: rts_8K2qA + object: realtime.session + status: ready + client_secret: brt_live_example_short_lived_secret + expires_at: '2026-08-12T10:01:00.000Z' + max_duration_seconds: 60 + allowed_origins: ['https://app.example.com'] + credits: { reserved: 1.2, settled: 0, refunded: 0 } + request_id: req_abc123 + created_at: '2026-08-12T10:00:00.000Z' + connected_at: null + closed_at: null '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '402': - description: Insufficient credits + description: Insufficient USD balance content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } '409': description: Idempotency conflict @@ -1379,24 +2674,44 @@ paths: example: data: object: usage - credit_balance: 1080 + credit_balance: 21.6 total_tasks: 12 - credits_settled: 720 - credits_refunded: 450 + credits_settled: 14.4 + credits_refunded: 9 concurrency: limit: 2 active: 1 realtime: sessions: 3 - credits: 90 + credits: 1.8 active: 1 by_workflow: - workflow: music-video tasks: 8 - credits_settled: 480 + credits_settled: 9.6 - workflow: ecommerce-video tasks: 4 - credits_settled: 240 + credits_settled: 4.8 + by_capability: + - task_kind: image + capability_id: seedream-5-pro + tasks: 3 + credits_settled: 0.42 + - task_kind: video + capability_id: veo-3.1 + tasks: 2 + credits_settled: 14 + by_model: + - media_type: image + model: seedream-5-pro + tasks: 3 + credits_settled: 0.42 + by_api_key: + - api_key_id: key_abc123 + title: Production + key_prefix: sk_live_abcd + tasks: 12 + credits_settled: 14.4 '401': $ref: '#/components/responses/Unauthorized' @@ -1419,7 +2734,7 @@ paths: (`image/png`, `image/jpeg`, `image/webp`). - Audio: `mp3`, `wav`, `aac`, `m4a` (`audio/mpeg`, `audio/wav`, `audio/aac`, `audio/mp4`). - - Audio uploads must be 10-180 seconds. + - Audio uploads must be 10-300 seconds. The selected Music Video tier applies its own task limit: Standard 10-180 seconds; Premium 10-300 seconds. - Subtitles: `srt` (`application/x-subrip`; multipart uploads may use `text/plain` only when the filename ends in `.srt`). - PDF, generic text files, octet-stream uploads, videos, and zip files @@ -1445,6 +2760,22 @@ paths: purpose: type: string enum: [input] + image/png: + schema: { type: string, format: binary } + image/jpeg: + schema: { type: string, format: binary } + image/webp: + schema: { type: string, format: binary } + audio/mpeg: + schema: { type: string, format: binary } + audio/wav: + schema: { type: string, format: binary } + audio/aac: + schema: { type: string, format: binary } + audio/mp4: + schema: { type: string, format: binary } + application/x-subrip: + schema: { type: string, format: binary } responses: '201': description: File uploaded @@ -1468,6 +2799,10 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' /v1/webhooks: get: @@ -1499,6 +2834,10 @@ paths: updated_at: 1782210000 '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' post: operationId: createWebhookEndpoint tags: [Webhooks] @@ -1507,8 +2846,9 @@ paths: security: - BearerAuth: [] description: | - The signing secret is returned only once at creation time. Store it - securely. Later responses return a masked secret. + The public API returns the signing secret in full at creation time. Store + it securely; later public API responses return a masked secret. An + authenticated dashboard owner can explicitly reveal the secret again. BeatAPI sends these headers with each delivery: - `x-beatapi-event`: `task.succeeded` or `task.failed` @@ -1572,8 +2912,9 @@ paths: ``` Reject old timestamps to prevent replay attacks. A 5 minute window is - recommended. Failed deliveries are retried at most 3 times with fixed - backoff windows of 1 minute, 5 minutes, and 15 minutes. Polling + recommended. A delivery is attempted at most 3 times total: the initial + request plus up to 2 retries, with fixed backoff windows of 1 minute and + 5 minutes. Polling `GET /v1/tasks/{task_id}` remains the source of truth. requestBody: required: true @@ -1586,10 +2927,14 @@ paths: url: type: string format: uri + pattern: '^https://' + description: Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. description: type: string + description: Optional internal label for identifying the endpoint in your account. events: type: array + description: Task events to deliver. Omit to subscribe to both `task.succeeded` and `task.failed`. items: type: string enum: [task.succeeded, task.failed] @@ -1618,6 +2963,10 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' /v1/webhooks/{id}: get: @@ -1660,6 +3009,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' patch: operationId: updateWebhookEndpoint tags: [Webhooks] @@ -1684,6 +3037,8 @@ paths: url: type: string format: uri + pattern: '^https://' + description: Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. description: type: string status: @@ -1707,6 +3062,16 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '404': + description: Webhook endpoint not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' delete: operationId: deleteWebhookEndpoint tags: [Webhooks] @@ -1734,3 +3099,13 @@ paths: deleted: true '401': $ref: '#/components/responses/Unauthorized' + '404': + description: Webhook endpoint not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' diff --git a/contract/contract.lock.json b/contract/contract.lock.json index 47af603..d42fda4 100644 --- a/contract/contract.lock.json +++ b/contract/contract.lock.json @@ -1,6 +1,6 @@ { "source": "https://github.com/BeatAPI/beatapi-examples", - "ref": "8f7d3cff33445ded4d3c94f0fb8ac5060d790148", + "ref": "5fbea4d15956bb280b5eada46542bd6521155b91", "openapiVersion": "1.0.0-launch", - "sha256": "290100dba10bb14b040f5a826657ad7d4a01f179fc28ef69ea0bdcaa66f7dad3" + "sha256": "faca257de1d82acd4448ae966420fc2aa9ea6e73c14a03db77b3d31f5add9afa" } diff --git a/generated/runtime.lock.json b/generated/runtime.lock.json index 5bb650c..f514b43 100644 --- a/generated/runtime.lock.json +++ b/generated/runtime.lock.json @@ -1,5 +1,5 @@ { "source": "https://github.com/BeatAPI/beatapi-cli/tree/main/packages/client", - "ref": "ec973b34894551ee2fafb27b7c8f3881ecef0b8b", - "sha256": "bbcc96e34af27bdf2a719e60c60ee075f0526b416e9a82b8ee349fadc5a9eb73" + "ref": "4b4fb1000c714d6897edbfbd8a2812212a21e2d0", + "sha256": "5d0e6df94b0feaf664bceb80bd2501def05fb70f840431c1f14a365952e555e3" } diff --git a/generated/skill.lock.json b/generated/skill.lock.json index e5dd8e4..0355949 100644 --- a/generated/skill.lock.json +++ b/generated/skill.lock.json @@ -1,5 +1,5 @@ { "source": "https://github.com/BeatAPI/beatapi-skill/tree/main/skills/beatapi-video", - "ref": "62ceeaeb2ec461f70159899385920f21aa804e19", - "sha256": "f50bf504fcb43224dec22ab1f94a00c407a4d459c33babb21153d466263b41e6" + "ref": "f81cd2eef15fa764fbbef3eb1e147538cc725692", + "sha256": "678e7091a149d33a8b6fce69d49c20f13f33507288816e83070ad8a789242800" } diff --git a/mcp/server.mjs b/mcp/server.mjs index 313ad10..a7639fd 100644 --- a/mcp/server.mjs +++ b/mcp/server.mjs @@ -31187,6 +31187,44 @@ var BeatAPIClient = class { { authenticated: false } ).then((result) => result.data); } + listGenerationModels() { + return this.request( + "/v1/media/models", + { authenticated: false } + ).then((result) => result.data); + } + createImageTask(input) { + return this.request("/v1/images/tasks", { method: "POST", body: input }); + } + createVideoTask(input) { + return this.request("/v1/videos/tasks", { method: "POST", body: input }); + } + listEffects(filters = {}) { + const query = new URLSearchParams(); + if (filters.outputType) query.set("output_type", filters.outputType); + if (filters.category) query.set("category", filters.category); + const suffix = query.size > 0 ? `?${query.toString()}` : ""; + return this.request( + `/v1/effects${suffix}`, + { authenticated: false } + ).then((result) => result.data); + } + getEffect(effectId) { + return this.request(`/v1/effects/${encodePathSegment(effectId)}`, { + authenticated: false + }); + } + createEffectTask(input, options) { + const idempotencyKey = options.idempotencyKey.trim(); + if (!idempotencyKey) { + throw new TypeError("idempotencyKey must not be empty."); + } + return this.request("/v1/effects/tasks", { + method: "POST", + body: input, + headers: { "idempotency-key": idempotencyKey } + }); + } getUsage() { return this.request("/v1/usage"); } @@ -31468,6 +31506,18 @@ var BeatAPIExecutor = class { if (name === "beatapi_list_workflows") { return sanitize(await this.direct.listWorkflows()); } + if (name === "beatapi_list_generation_models") { + return sanitize(await this.direct.listGenerationModels()); + } + if (name === "beatapi_list_effects") { + return sanitize(await this.direct.listEffects({ + ...typeof input.output_type === "string" ? { outputType: input.output_type } : {}, + ...typeof input.category === "string" ? { category: input.category } : {} + })); + } + if (name === "beatapi_get_effect") { + return sanitize(await this.direct.getEffect(stringValue(input, "effect_id"))); + } if (!this.usesDirectClient) return this.executeViaCli(name, input); return this.executeDirect(name, input); } @@ -31513,6 +31563,21 @@ var BeatAPIExecutor = class { switch (name) { case "beatapi_get_usage": return sanitize(await this.direct.getUsage()); + case "beatapi_create_image": + return sanitize( + await this.direct.createImageTask(input) + ); + case "beatapi_create_video": + return sanitize( + await this.direct.createVideoTask(input) + ); + case "beatapi_create_effect": + return sanitize( + await this.direct.createEffectTask( + without(input, ["idempotency_key"]), + { idempotencyKey: stringValue(input, "idempotency_key") } + ) + ); case "beatapi_upload_file": { const path = resolve(stringValue(input, "path")); const info = await stat(path); @@ -31643,6 +31708,31 @@ var BeatAPIExecutor = class { case "beatapi_get_usage": result = await runCli(["usage"]); break; + case "beatapi_create_image": + result = await withJsonFile( + input, + (path) => runCli(["images", "create", "--file", path]) + ); + break; + case "beatapi_create_video": + result = await withJsonFile( + input, + (path) => runCli(["videos", "create", "--file", path]) + ); + break; + case "beatapi_create_effect": + result = await withJsonFile( + without(input, ["idempotency_key"]), + (path) => runCli([ + "effects", + "create", + "--file", + path, + "--idempotency-key", + stringValue(input, "idempotency_key") + ]) + ); + break; case "beatapi_upload_file": result = await runCli(["files", "upload", resolve(stringValue(input, "path"))]); break; @@ -31838,6 +31928,120 @@ var httpsOrigin = external_exports.string().url().superRefine((value, context) = }); } }); +var generationPrompt = external_exports.string().trim().min(1).max(5e3); +var generationImages = (max) => external_exports.array(httpsUrl).min(1).max(max); +var imageGenerationInput = external_exports.discriminatedUnion("model", [ + external_exports.object({ + model: external_exports.literal("nano-banana"), + prompt: generationPrompt, + aspect_ratio: external_exports.enum(["1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5", "21:9", "auto"]).optional(), + output_format: external_exports.enum(["png", "jpeg"]).optional() + }).strict(), + external_exports.object({ + model: external_exports.literal("nano-banana-pro"), + prompt: generationPrompt, + images: generationImages(8).optional(), + aspect_ratio: external_exports.enum(["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "auto"]).optional(), + resolution: external_exports.enum(["1K", "2K", "4K"]).optional(), + output_format: external_exports.enum(["png", "jpg"]).optional() + }).strict(), + external_exports.object({ + model: external_exports.literal("gpt-image-2"), + prompt: generationPrompt, + images: generationImages(16).optional(), + aspect_ratio: external_exports.enum(["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]).optional(), + resolution: external_exports.enum(["1K", "2K", "4K"]).optional() + }).strict(), + external_exports.object({ + model: external_exports.literal("seedream-5-pro"), + prompt: generationPrompt, + images: generationImages(10).optional(), + aspect_ratio: external_exports.enum(["auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9"]).optional(), + resolution: external_exports.enum(["1K", "2K"]).optional(), + output_format: external_exports.enum(["png", "jpeg"]).optional() + }).strict() +]); +var videoAspectRatio = external_exports.enum(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"]); +function seedanceInput(model, resolutions) { + return external_exports.object({ + model: external_exports.literal(model), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(9).optional(), + reference_videos: generationImages(3).optional(), + reference_audios: generationImages(3).optional(), + duration: external_exports.number().int().min(4).max(15).optional(), + aspect_ratio: videoAspectRatio.optional(), + resolution: external_exports.enum(resolutions).optional(), + ...model === "seedance-2" || model === "seedance-2-fast" ? { generate_audio: external_exports.boolean().optional() } : {} + }).strict(); +} +var klingShot = external_exports.object({ + prompt: external_exports.string().trim().min(1).max(500), + duration: external_exports.number().int().min(1).max(12) +}).strict(); +var klingElement = external_exports.object({ + name: external_exports.string().trim().min(1), + description: external_exports.string().optional(), + element_input_urls: generationImages(4), + element_input_audio_urls: generationImages(1).optional(), + start_time: external_exports.number().int().min(0).max(3e4).optional(), + end_time: external_exports.number().int().min(0).max(3e4).optional() +}).strict(); +var videoGenerationInput = external_exports.discriminatedUnion("model", [ + seedanceInput("minimax-h3", ["768P", "2K"]), + seedanceInput("seedance-2", ["480p", "720p", "1080p", "4k"]), + seedanceInput("seedance-2-fast", ["480p", "720p"]), + seedanceInput("seedance-2-mini", ["480p", "720p"]), + external_exports.object({ + model: external_exports.literal("veo-3.1"), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(3).optional(), + aspect_ratio: external_exports.enum(["16:9", "9:16", "auto"]).optional(), + quality: external_exports.enum(["Quality", "Fast", "Lite"]).optional(), + watermark: external_exports.string().optional(), + enable_translation: external_exports.boolean().optional() + }).strict(), + external_exports.object({ + model: external_exports.literal("seedance-2.5"), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(30).optional(), + reference_videos: generationImages(10).optional(), + reference_audios: generationImages(10).optional(), + duration: external_exports.number().int().min(4).max(30).optional(), + aspect_ratio: videoAspectRatio.optional(), + resolution: external_exports.literal("720p").optional(), + generate_audio: external_exports.boolean().optional(), + seed: external_exports.number().int().min(-1).max(4294967295).optional() + }).strict(), + external_exports.object({ + model: external_exports.literal("kling-3"), + prompt: generationPrompt, + images: generationImages(2).optional(), + duration: external_exports.number().int().min(3).max(15).optional(), + aspect_ratio: external_exports.enum(["16:9", "9:16", "1:1"]).optional(), + resolution: external_exports.enum(["std", "pro", "4K"]).optional(), + sound: external_exports.boolean().optional(), + multi_shots: external_exports.boolean().optional(), + multi_prompt: external_exports.array(klingShot).min(1).max(5).optional(), + elements: external_exports.array(klingElement).max(3).optional() + }).strict() +]); +var effectTaskInput = external_exports.object({ + effect_id: id, + effect_version: external_exports.number().int().min(1).optional(), + images: generationImages(7), + options: external_exports.object({ + aspect_ratio: external_exports.string().optional(), + resolution: external_exports.string().optional(), + duration: external_exports.number().int().optional(), + bgm: external_exports.boolean().optional(), + seed: external_exports.number().int().optional() + }).strict().optional(), + idempotency_key: external_exports.string().trim().min(1).max(255) +}).strict(); var musicVideoInput = external_exports.object({ images: imageUrls, audio_url: httpsUrl, @@ -31901,6 +32105,51 @@ var toolDefinitions = [ inputSchema: external_exports.object({}).strict(), annotations: readOnly }, + { + name: "beatapi_list_generation_models", + title: "List BeatAPI generation models", + description: "List stable public BeatAPI image and video model aliases and input modes. Authentication is not required.", + inputSchema: external_exports.object({}).strict(), + annotations: readOnly + }, + { + name: "beatapi_create_image", + title: "Create BeatAPI image", + description: "Paid mutation: create one asynchronous image task with a stable BeatAPI model alias.", + inputSchema: imageGenerationInput, + annotations: write + }, + { + name: "beatapi_create_video", + title: "Create BeatAPI video", + description: "Paid mutation: create one asynchronous model-specific video task.", + inputSchema: videoGenerationInput, + annotations: write + }, + { + name: "beatapi_list_effects", + title: "List BeatAPI Effects", + description: "List active published Effects. Authentication is not required.", + inputSchema: external_exports.object({ + output_type: external_exports.enum(["image", "video"]).optional(), + category: external_exports.string().trim().min(1).optional() + }).strict(), + annotations: readOnly + }, + { + name: "beatapi_get_effect", + title: "Get BeatAPI Effect", + description: "Read one published Effect and its current immutable input contract. Authentication is not required.", + inputSchema: external_exports.object({ effect_id: id }).strict(), + annotations: readOnly + }, + { + name: "beatapi_create_effect", + title: "Create BeatAPI Effect task", + description: "Paid mutation: create one versioned Effect task after validating inputs against the published Effect contract.", + inputSchema: effectTaskInput, + annotations: write + }, { name: "beatapi_get_usage", title: "Get BeatAPI usage", diff --git a/mcp/src/executor.ts b/mcp/src/executor.ts index 99e55ec..4354099 100644 --- a/mcp/src/executor.ts +++ b/mcp/src/executor.ts @@ -16,11 +16,14 @@ import { promisify } from "node:util"; import { BeatAPIClient, BeatAPIError, + type CreateEffectTaskInput, type CreateRealtimeSessionInput, type CreateWebhookInput, type EcommerceVideoTaskInput, + type ImageGenerationTaskInput, type MusicVideoShotEditInput, type MusicVideoTaskInput, + type VideoGenerationTaskInput, type UpdateWebhookInput, } from "../vendor/client/index.js"; @@ -247,6 +250,20 @@ export class BeatAPIExecutor { if (name === "beatapi_list_workflows") { return sanitize(await this.direct.listWorkflows()); } + if (name === "beatapi_list_generation_models") { + return sanitize(await this.direct.listGenerationModels()); + } + if (name === "beatapi_list_effects") { + return sanitize(await this.direct.listEffects({ + ...(typeof input.output_type === "string" + ? { outputType: input.output_type as "image" | "video" } + : {}), + ...(typeof input.category === "string" ? { category: input.category } : {}), + })); + } + if (name === "beatapi_get_effect") { + return sanitize(await this.direct.getEffect(stringValue(input, "effect_id"))); + } if (!this.usesDirectClient) return this.executeViaCli(name, input); return this.executeDirect(name, input); } @@ -296,6 +313,21 @@ export class BeatAPIExecutor { switch (name) { case "beatapi_get_usage": return sanitize(await this.direct.getUsage()); + case "beatapi_create_image": + return sanitize( + await this.direct.createImageTask(input as ImageGenerationTaskInput), + ); + case "beatapi_create_video": + return sanitize( + await this.direct.createVideoTask(input as VideoGenerationTaskInput), + ); + case "beatapi_create_effect": + return sanitize( + await this.direct.createEffectTask( + without(input, ["idempotency_key"]) as CreateEffectTaskInput, + { idempotencyKey: stringValue(input, "idempotency_key") }, + ), + ); case "beatapi_upload_file": { const path = resolve(stringValue(input, "path")); const info = await stat(path); @@ -423,6 +455,28 @@ export class BeatAPIExecutor { case "beatapi_get_usage": result = await runCli(["usage"]); break; + case "beatapi_create_image": + result = await withJsonFile(input, (path) => + runCli(["images", "create", "--file", path]), + ); + break; + case "beatapi_create_video": + result = await withJsonFile(input, (path) => + runCli(["videos", "create", "--file", path]), + ); + break; + case "beatapi_create_effect": + result = await withJsonFile(without(input, ["idempotency_key"]), (path) => + runCli([ + "effects", + "create", + "--file", + path, + "--idempotency-key", + stringValue(input, "idempotency_key"), + ]), + ); + break; case "beatapi_upload_file": result = await runCli(["files", "upload", resolve(stringValue(input, "path"))]); break; diff --git a/mcp/src/tools.ts b/mcp/src/tools.ts index b139d59..fe275c6 100644 --- a/mcp/src/tools.ts +++ b/mcp/src/tools.ts @@ -62,6 +62,128 @@ const httpsOrigin = z.string().url().superRefine((value, context) => { }); } }); +const generationPrompt = z.string().trim().min(1).max(5000); +const generationImages = (max: number) => z.array(httpsUrl).min(1).max(max); +const imageGenerationInput = z.discriminatedUnion("model", [ + z.object({ + model: z.literal("nano-banana"), + prompt: generationPrompt, + aspect_ratio: z.enum(["1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5", "21:9", "auto"]).optional(), + output_format: z.enum(["png", "jpeg"]).optional(), + }).strict(), + z.object({ + model: z.literal("nano-banana-pro"), + prompt: generationPrompt, + images: generationImages(8).optional(), + aspect_ratio: z.enum(["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "auto"]).optional(), + resolution: z.enum(["1K", "2K", "4K"]).optional(), + output_format: z.enum(["png", "jpg"]).optional(), + }).strict(), + z.object({ + model: z.literal("gpt-image-2"), + prompt: generationPrompt, + images: generationImages(16).optional(), + aspect_ratio: z.enum(["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]).optional(), + resolution: z.enum(["1K", "2K", "4K"]).optional(), + }).strict(), + z.object({ + model: z.literal("seedream-5-pro"), + prompt: generationPrompt, + images: generationImages(10).optional(), + aspect_ratio: z.enum(["auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9"]).optional(), + resolution: z.enum(["1K", "2K"]).optional(), + output_format: z.enum(["png", "jpeg"]).optional(), + }).strict(), +]); + +const videoAspectRatio = z.enum(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"]); +function seedanceInput( + model: "seedance-2" | "seedance-2-fast" | "seedance-2-mini" | "minimax-h3", + resolutions: readonly [string, ...string[]], +) { + return z.object({ + model: z.literal(model), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(9).optional(), + reference_videos: generationImages(3).optional(), + reference_audios: generationImages(3).optional(), + duration: z.number().int().min(4).max(15).optional(), + aspect_ratio: videoAspectRatio.optional(), + resolution: z.enum(resolutions).optional(), + ...(model === "seedance-2" || model === "seedance-2-fast" + ? { generate_audio: z.boolean().optional() } + : {}), + }).strict(); +} + +const klingShot = z.object({ + prompt: z.string().trim().min(1).max(500), + duration: z.number().int().min(1).max(12), +}).strict(); +const klingElement = z.object({ + name: z.string().trim().min(1), + description: z.string().optional(), + element_input_urls: generationImages(4), + element_input_audio_urls: generationImages(1).optional(), + start_time: z.number().int().min(0).max(30000).optional(), + end_time: z.number().int().min(0).max(30000).optional(), +}).strict(); +const videoGenerationInput = z.discriminatedUnion("model", [ + seedanceInput("minimax-h3", ["768P", "2K"]), + seedanceInput("seedance-2", ["480p", "720p", "1080p", "4k"]), + seedanceInput("seedance-2-fast", ["480p", "720p"]), + seedanceInput("seedance-2-mini", ["480p", "720p"]), + z.object({ + model: z.literal("veo-3.1"), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(3).optional(), + aspect_ratio: z.enum(["16:9", "9:16", "auto"]).optional(), + quality: z.enum(["Quality", "Fast", "Lite"]).optional(), + watermark: z.string().optional(), + enable_translation: z.boolean().optional(), + }).strict(), + z.object({ + model: z.literal("seedance-2.5"), + prompt: generationPrompt, + images: generationImages(2).optional(), + reference_images: generationImages(30).optional(), + reference_videos: generationImages(10).optional(), + reference_audios: generationImages(10).optional(), + duration: z.number().int().min(4).max(30).optional(), + aspect_ratio: videoAspectRatio.optional(), + resolution: z.literal("720p").optional(), + generate_audio: z.boolean().optional(), + seed: z.number().int().min(-1).max(4294967295).optional(), + }).strict(), + z.object({ + model: z.literal("kling-3"), + prompt: generationPrompt, + images: generationImages(2).optional(), + duration: z.number().int().min(3).max(15).optional(), + aspect_ratio: z.enum(["16:9", "9:16", "1:1"]).optional(), + resolution: z.enum(["std", "pro", "4K"]).optional(), + sound: z.boolean().optional(), + multi_shots: z.boolean().optional(), + multi_prompt: z.array(klingShot).min(1).max(5).optional(), + elements: z.array(klingElement).max(3).optional(), + }).strict(), +]); + +const effectTaskInput = z.object({ + effect_id: id, + effect_version: z.number().int().min(1).optional(), + images: generationImages(7), + options: z.object({ + aspect_ratio: z.string().optional(), + resolution: z.string().optional(), + duration: z.number().int().optional(), + bgm: z.boolean().optional(), + seed: z.number().int().optional(), + }).strict().optional(), + idempotency_key: z.string().trim().min(1).max(255), +}).strict(); const musicVideoInput = z .object({ @@ -135,6 +257,51 @@ export const toolDefinitions: readonly ToolDefinition[] = [ inputSchema: z.object({}).strict(), annotations: readOnly, }, + { + name: "beatapi_list_generation_models", + title: "List BeatAPI generation models", + description: "List stable public BeatAPI image and video model aliases and input modes. Authentication is not required.", + inputSchema: z.object({}).strict(), + annotations: readOnly, + }, + { + name: "beatapi_create_image", + title: "Create BeatAPI image", + description: "Paid mutation: create one asynchronous image task with a stable BeatAPI model alias.", + inputSchema: imageGenerationInput, + annotations: write, + }, + { + name: "beatapi_create_video", + title: "Create BeatAPI video", + description: "Paid mutation: create one asynchronous model-specific video task.", + inputSchema: videoGenerationInput, + annotations: write, + }, + { + name: "beatapi_list_effects", + title: "List BeatAPI Effects", + description: "List active published Effects. Authentication is not required.", + inputSchema: z.object({ + output_type: z.enum(["image", "video"]).optional(), + category: z.string().trim().min(1).optional(), + }).strict(), + annotations: readOnly, + }, + { + name: "beatapi_get_effect", + title: "Get BeatAPI Effect", + description: "Read one published Effect and its current immutable input contract. Authentication is not required.", + inputSchema: z.object({ effect_id: id }).strict(), + annotations: readOnly, + }, + { + name: "beatapi_create_effect", + title: "Create BeatAPI Effect task", + description: "Paid mutation: create one versioned Effect task after validating inputs against the published Effect contract.", + inputSchema: effectTaskInput, + annotations: write, + }, { name: "beatapi_get_usage", title: "Get BeatAPI usage", diff --git a/mcp/vendor/client/client.ts b/mcp/vendor/client/client.ts index 30d3f2f..38639e4 100644 --- a/mcp/vendor/client/client.ts +++ b/mcp/vendor/client/client.ts @@ -9,6 +9,8 @@ export type BeatAPIFile = components["schemas"]["File"]; export type BeatAPIShotMedia = components["schemas"]["ShotMedia"]; export type BeatAPIWebhook = components["schemas"]["WebhookEndpoint"]; export type BeatAPIRealtimeSession = components["schemas"]["RealtimeSession"]; +export type BeatAPIGenerationModel = components["schemas"]["GenerationModel"]; +export type BeatAPIEffect = components["schemas"]["Effect"]; export type BeatAPIDeleteResult = components["schemas"]["DeleteResponse"]["data"]; export type MusicVideoTaskInput = @@ -25,6 +27,12 @@ export type UpdateWebhookInput = operations["updateWebhookEndpoint"]["requestBody"]["content"]["application/json"]; export type CreateRealtimeSessionInput = operations["createRealtimeSession"]["requestBody"]["content"]["application/json"]; +export type ImageGenerationTaskInput = + operations["createImageGenerationTask"]["requestBody"]["content"]["application/json"]; +export type VideoGenerationTaskInput = + operations["createVideoGenerationTask"]["requestBody"]["content"]["application/json"]; +export type CreateEffectTaskInput = + operations["createEffectTask"]["requestBody"]["content"]["application/json"]; type FetchLike = ( input: string | URL | Request, @@ -276,6 +284,55 @@ export class BeatAPIClient { ).then((result) => result.data); } + listGenerationModels(): Promise { + return this.request<{ object: "list"; data: BeatAPIGenerationModel[] }>( + "/v1/media/models", + { authenticated: false }, + ).then((result) => result.data); + } + + createImageTask(input: ImageGenerationTaskInput): Promise { + return this.request("/v1/images/tasks", { method: "POST", body: input }); + } + + createVideoTask(input: VideoGenerationTaskInput): Promise { + return this.request("/v1/videos/tasks", { method: "POST", body: input }); + } + + listEffects( + filters: { outputType?: "image" | "video"; category?: string } = {}, + ): Promise { + const query = new URLSearchParams(); + if (filters.outputType) query.set("output_type", filters.outputType); + if (filters.category) query.set("category", filters.category); + const suffix = query.size > 0 ? `?${query.toString()}` : ""; + return this.request<{ object: "list"; data: BeatAPIEffect[] }>( + `/v1/effects${suffix}`, + { authenticated: false }, + ).then((result) => result.data); + } + + getEffect(effectId: string): Promise { + return this.request(`/v1/effects/${encodePathSegment(effectId)}`, { + authenticated: false, + }); + } + + createEffectTask( + input: CreateEffectTaskInput, + options: { idempotencyKey: string }, + ): Promise { + const idempotencyKey = options.idempotencyKey.trim(); + if (!idempotencyKey) { + throw new TypeError("idempotencyKey must not be empty."); + } + return this.request("/v1/effects/tasks", { + method: "POST", + body: input, + headers: { "idempotency-key": idempotencyKey }, + }); + } + getUsage(): Promise { return this.request("/v1/usage"); } diff --git a/mcp/vendor/client/index.ts b/mcp/vendor/client/index.ts index 9aaa491..9983a49 100644 --- a/mcp/vendor/client/index.ts +++ b/mcp/vendor/client/index.ts @@ -3,6 +3,8 @@ export { type BeatAPIClientOptions, type BeatAPIDeleteResult, type BeatAPIFile, + type BeatAPIEffect, + type BeatAPIGenerationModel, type BeatAPIRealtimeSession, type BeatAPIShotMedia, type BeatAPITask, @@ -11,13 +13,16 @@ export { type BeatAPIWebhook, type BeatAPIWorkflow, type CreateWebhookInput, + type CreateEffectTaskInput, type CreateRealtimeSessionInput, type EcommerceVideoTaskInput, + type ImageGenerationTaskInput, type MusicVideoComposeInput, type MusicVideoShotEditInput, type MusicVideoTaskInput, type RetryOptions, type UpdateWebhookInput, + type VideoGenerationTaskInput, type UploadFileOptions, type WaitForTaskOptions, } from "./client.js"; diff --git a/mcp/vendor/client/types.generated.ts b/mcp/vendor/client/types.generated.ts index d030b36..94f68af 100644 --- a/mcp/vendor/client/types.generated.ts +++ b/mcp/vendor/client/types.generated.ts @@ -21,6 +21,136 @@ export interface paths { patch?: never; trace?: never; }; + "/v1/media/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List BeatAPI image and video generation models + * @description Returns stable BeatAPI model aliases and public input modes. Internal execution routing is not part of this contract. + */ + get: operations["listGenerationModels"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/images/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create an image generation task + * @description Creates one asynchronous image task. Select the model-specific request + * contract with `model`, save the returned `data.id`, and poll + * `GET /v1/tasks/{task_id}` until the task succeeds or fails. + */ + post: operations["createImageGenerationTask"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/videos/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a video generation task + * @description Creates one asynchronous video task. Select the model-specific request + * contract with `model`, save the returned `data.id`, and poll + * `GET /v1/tasks/{task_id}` until the task succeeds or fails. + */ + post: operations["createVideoGenerationTask"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/effects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List active Effects + * @description Returns only versioned Effects that have passed BeatAPI publication gates. Internal integration names, template ids, costs, and execution context are never exposed. + */ + get: operations["listEffects"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/effects/{effect_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get an active Effect */ + get: operations["getEffect"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/effects/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create an Effect task + * @description Creates an asynchronous image or video Effect task. Read the catalog + * first: image count, accepted input types, output resolution/duration, + * and execution contract are fixed by the selected Effect version. Send + * an `Idempotency-Key`; an exact replay returns the + * accepted task before remote input URLs are revalidated, while a changed + * body returns `idempotency_conflict`. + * + * The USD amount is reserved atomically when accepted, settled on success, and + * fully refunded after a definite processing failure. An uncertain create + * result is not blindly retried and never switches integrations automatically. + */ + post: operations["createEffectTask"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/v1/music-video/tasks": { parameters: { query?: never; @@ -31,24 +161,30 @@ export interface paths { get?: never; put?: never; /** - * Create Music Video - * @description Music Video requires public HTTPS image URLs and a public HTTPS audio URL. - * Prompt, language, quality, style, lip reference, subtitle, and format - * controls are optional. BeatAPI detects the audio duration before task - * creation and charges the detected billable duration at the selected - * per-second customer-credit rate. If audio duration cannot be detected, - * `duration` is used as the billing fallback. + * Create a Music Video workflow task + * @description Music Video requires public HTTPS media URLs. Requests that omit + * `mv_tier` use `standard` and preserve the existing API behavior. + * Premium retains the configured integration host but uses a distinct + * execution path selected internally by BeatAPI. + * Shared controls include prompt, aspect ratio, subtitles, and the tier's + * billing fallback. Language, quality, `lip_sync`, `lip_ref_url`, + * `srt_url`, and `compose_mode` are Standard-only. Premium uses `mv_mode` + * plus `style` and mode-specific images or `lip_ref_urls`. BeatAPI detects audio duration before task + * creation and records the billable duration in Task usage. If audio + * duration cannot be detected, `duration` is used as the billing fallback. * * Input limits: - * - Images must contain 1-7 public HTTPS URLs. + * - Standard images must contain 1-7 public HTTPS URLs. + * - Premium `sing` and `sing_perform` accept 0-6 scene images and require 1-2 `lip_ref_urls`. + * - Premium `dance` and `perform` require exactly 6 scene images. * - Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller. * - Image aspect ratio should be between 1:4 and 4:1. - * - Audio must be a public HTTPS mp3, wav, aac, or m4a URL between 10 and 180 seconds. + * - Standard audio must be 10-180 seconds; Premium audio must be 10-300 seconds and contain vocals or lyrics rather than instrumental-only audio. * - The audio file should be 50 MB or smaller. * - `prompt` is optional and must be at most 3000 characters. - * - `lip_ref_url`, when provided, must be a public HTTPS image URL. Use a clear, front-facing close-up face reference for best lip-sync results. - * - `srt_url`, when provided, must point to an `.srt` subtitle file. - * - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; it must be 10-180 seconds and cannot override a detected audio duration. + * - Standard `lip_sync=true` requires `lip_ref_url`. It must be a public HTTPS image URL showing a clear, front-facing close-up face. + * - Standard `srt_url`, when provided, must point to an `.srt` subtitle file. + * - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; Standard accepts 10-180 seconds and Premium accepts 10-300 seconds. It cannot override a detected audio duration. * * BeatAPI validates URL shape, text limits, enum values, combination * limits, and audio duration at task creation. Files uploaded through @@ -56,15 +192,6 @@ export interface paths { * Third-party media URLs must follow the same media requirements and may * be rejected during processing if invalid. * - * Customer pricing: - * - MV 540p standard: 4 credits/s - * - MV 720p standard: 5 credits/s - * - MV 1080p standard: 6 credits/s - * - lip_sync add-on: +2 credits/s - * - MV 720p high: 16 credits/s - * - MV 1080p high: 18 credits/s - * - Ecommerce Video 1080p: 15 credits/s - * * Combination limits: * - `quality=high` is not supported with `resolution=540p`. * - `lip_sync=true` is not supported with `resolution=540p`. @@ -93,10 +220,13 @@ export interface paths { get?: never; put?: never; /** - * Edit Shot + * Edit a Music Video storyboard shot * @description Edit one storyboard shot using its BeatAPI `shot_id`. This operation - * charges BeatAPI customer credits using the selected quality/resolution - * rate and the shot duration. Default shot duration is 5 seconds. + * charges the customer USD balance using the applicable task tier and shot + * duration. Standard edits accept only `prompt`. Premium edits accept + * `prompt` plus up to 6 optional replacement `images`. Generation quality, + * resolution, and shot duration are inherited from the original task and + * are not editable request fields. * When the edit finishes, BeatAPI stores the edited shot media and exposes * it on that shot. The existing final Music Video is not replaced until * you call compose with the selected shot ids. @@ -118,7 +248,7 @@ export interface paths { get?: never; put?: never; /** - * Get Shot Media + * Retrieve a Music Video storyboard shot media URL * @description Materialize one storyboard shot video using its BeatAPI `shot_id`. * If the shot has not been stored yet, BeatAPI retrieves the current shot * video, stores it under BeatAPI media storage, and returns a BeatAPI media @@ -144,9 +274,9 @@ export interface paths { get?: never; put?: never; /** - * Compose Video + * Compose a Music Video task from selected shots * @description Compose selected BeatAPI storyboard shots into the final Music Video. - * This operation charges a fixed 1 BeatAPI customer credit. + * This operation charges a fixed $1 USD. */ post: operations["composeMusicVideoTask"]; delete?: never; @@ -165,8 +295,11 @@ export interface paths { get?: never; put?: never; /** - * Create Ecommerce Video - * @description Ecommerce Video requires product images and an explicit output duration. + * Create an Ecommerce Video workflow task + * @description Ecommerce Video creates a complete product video from public HTTPS product or + * lifestyle images, an explicit duration, and optional creative direction. Upload + * local images with `POST /v1/files`, save the returned Task ID, and poll + * `GET /v1/tasks/{task_id}` until the task succeeds or fails. */ post: operations["createEcommerceVideoTask"]; delete?: never; @@ -206,9 +339,9 @@ export interface paths { put?: never; /** * Create a realtime browser session - * @description Reserve credits and allocate a short-lived BeatAPI realtime session. Send a unique + * @description Reserve the selected USD amount and allocate a short-lived BeatAPI realtime session. Send a unique * `Idempotency-Key`; retries with the same user, key, and body return the same session - * and deterministic short-lived `client_secret` without reserving credits or capacity + * and deterministic short-lived `client_secret` without reserving funds or capacity * twice. The browser receives only that BeatAPI secret and connects with * `@beatapi/realtime`. * @@ -289,7 +422,7 @@ export interface paths { * (`image/png`, `image/jpeg`, `image/webp`). * - Audio: `mp3`, `wav`, `aac`, `m4a` * (`audio/mpeg`, `audio/wav`, `audio/aac`, `audio/mp4`). - * - Audio uploads must be 10-180 seconds. + * - Audio uploads must be 10-300 seconds. The selected Music Video tier applies its own task limit: Standard 10-180 seconds; Premium 10-300 seconds. * - Subtitles: `srt` (`application/x-subrip`; multipart uploads may use * `text/plain` only when the filename ends in `.srt`). * - PDF, generic text files, octet-stream uploads, videos, and zip files @@ -321,8 +454,9 @@ export interface paths { put?: never; /** * Create a webhook endpoint - * @description The signing secret is returned only once at creation time. Store it - * securely. Later responses return a masked secret. + * @description The public API returns the signing secret in full at creation time. Store + * it securely; later public API responses return a masked secret. An + * authenticated dashboard owner can explicitly reveal the secret again. * * BeatAPI sends these headers with each delivery: * - `x-beatapi-event`: `task.succeeded` or `task.failed` @@ -386,8 +520,9 @@ export interface paths { * ``` * * Reject old timestamps to prevent replay attacks. A 5 minute window is - * recommended. Failed deliveries are retried at most 3 times with fixed - * backoff windows of 1 minute, 5 minutes, and 15 minutes. Polling + * recommended. A delivery is attempted at most 3 times total: the initial + * request plus up to 2 retries, with fixed backoff windows of 1 minute and + * 5 minutes. Polling * `GET /v1/tasks/{task_id}` remains the source of truth. */ post: operations["createWebhookEndpoint"]; @@ -417,7 +552,30 @@ export interface paths { trace?: never; }; } -export type webhooks = Record; +export interface webhooks { + taskCompleted: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Receive a BeatAPI task completion event + * @description BeatAPI sends this request to each active endpoint subscribed to the event. + * Verify `x-beatapi-signature` against the exact request body and use polling + * as the source of truth if delivery is delayed or fails. + */ + post: operations["receiveBeatApiTaskEvent"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} export interface components { schemas: { Workflow: { @@ -440,33 +598,60 @@ export interface components { * @example shot_xxx */ id: string; - /** @example 0 */ + /** + * @description Zero-based shot order in the storyboard. + * @example 0 + */ index: number; + /** @description Current lifecycle state for this storyboard shot. */ status: components["schemas"]["TaskStatus"]; - /** @example 5 */ + /** + * @description Planned or generated shot duration in seconds. + * @example 5 + */ duration_seconds?: number; - /** @example Opening lyric shot. */ + /** + * @description Creative instruction used to generate this shot. + * @example Opening lyric shot. + */ prompt?: string; - /** @example Intro */ + /** + * @description Lyric segment aligned with this shot when available. + * @example Intro + */ lyric_text?: string; /** @description Present only after the shot media has been materialized or after a shot edit finishes. */ media?: { - /** @example video */ + /** + * @description Hosted media type for the materialized shot. + * @example video + */ type?: string; /** * Format: uri + * @description BeatAPI-hosted HTTPS URL for the materialized shot. * @example https://media.beatapi.io/outputs/task_8K2qA/shots/0.mp4 */ url?: string; - /** @example video/mp4 */ + /** + * @description MIME type of the hosted shot media. + * @example video/mp4 + */ mime_type?: string; }; - /** @example 1782210000 */ + /** + * @description Unix timestamp when the shot record was created. + * @example 1782210000 + */ created_at: number; - /** @example 1782210300 */ + /** + * @description Unix timestamp when the shot record last changed. + * @example 1782210300 + */ updated_at: number; }; Storyboard: { + /** @description Ordered Music Video storyboard shots. The array may be empty before storyboard generation completes. */ shots: components["schemas"]["StoryboardShot"][]; }; ShotMedia: { @@ -498,68 +683,209 @@ export interface components { request_id: string; }; TaskUsage: { - /** @description BeatAPI customer credits reserved for this task. */ + /** + * Format: double + * @description USD amount reserved for this task. The compatibility field name is retained; 1 Credit equals $1 USD. + */ credits_reserved: number; - /** @description BeatAPI customer credits charged when the task or operation is accepted. */ + /** + * Format: double + * @description USD amount charged when the task or operation is accepted. + */ credits_charged: number; - /** @description Server-detected or request-declared billable duration used for credit calculation. */ + /** @description Server-detected or request-declared billable duration used for USD calculation. */ billable_duration_seconds?: number; - /** @description BeatAPI customer credits settled after successful work. */ + /** + * Format: double + * @description USD amount settled after successful work. + */ credits_settled: number; - /** @description BeatAPI customer credits refunded after failed eligible work. */ + /** + * Format: double + * @description USD amount refunded after failed eligible work. + */ credits_refunded: number; }; Task: { - /** @example task_8K2qA */ + /** + * @description Stable BeatAPI task ID used for polling and support. + * @example task_8K2qA + */ id: string; - /** @enum {string} */ + /** + * @description Object discriminator; always `task`. + * @enum {string} + */ object: "task"; /** + * @description Public task family that determines which capability fields are present. + * @enum {string} + */ + task_kind: "workflow" | "effect" | "image" | "video"; + /** @description Stable BeatAPI workflow, Effect, or generation model ID selected when the task was accepted. */ + capability_id: string; + /** @description Immutable capability version used by this task. Legacy workflow rows are returned as version 1. */ + capability_version: number | null; + /** + * @description Present for workflow tasks; identifies the selected BeatAPI workflow. * @example music-video * @enum {string} */ - workflow: "music-video" | "ecommerce-video"; + workflow?: "music-video" | "ecommerce-video"; + /** + * @description Present for Effect tasks; stable selected Effect ID. + * @example video-muscle-max + */ + effect_id?: string; + /** + * @description Present for Effect tasks; immutable Effect version used for processing. + * @example 1 + */ + effect_version?: number; + /** + * @description Present when task_kind is image or video. + * @enum {string} + */ + media_type?: "image" | "video"; + /** @description Stable BeatAPI model alias. It is independent from internal execution routing. */ + model?: string; + /** @description Current task lifecycle status. Stop polling at `succeeded` or `failed`; Music Video can also require manual action. */ status: components["schemas"]["TaskStatus"]; + /** @description Current processing stage, exposed separately so workflow progress can be tracked. */ stage: components["schemas"]["TaskStatus"]; + /** @description Music Video storyboard metadata when available. */ storyboard?: components["schemas"]["Storyboard"]; + /** @description Unix timestamp when BeatAPI accepted the task. */ created_at: number; + /** @description Unix timestamp of the latest task update. */ updated_at: number; + /** @description Terminal Unix timestamp, or null while work is in progress. */ completed_at: number | null; /** @description Output is null until the task succeeds. */ output: null | { + /** @description BeatAPI-hosted result assets. */ media: { - /** @enum {string} */ - type: "video"; - /** Format: uri */ + /** + * @description Result asset type. + * @enum {string} + */ + type: "image" | "video"; + /** + * Format: uri + * @description BeatAPI-hosted HTTPS result URL. + */ url: string; - /** @example video/mp4 */ + /** + * @description Result asset MIME type. + * @example video/mp4 + * @example image/png + * @example image/jpeg + * @example image/webp + */ mime_type: string; }[]; - /** Format: uri */ + /** + * Format: uri + * @description Primary BeatAPI-hosted result URL for clients that need one canonical asset. + */ r2_url: string; }; + /** @description USD reservation, settlement, refund, and optional billable duration for this task. */ usage: components["schemas"]["TaskUsage"]; - /** @example req_abc123 */ + /** + * @description Correlation ID to retain for logs and BeatAPI support. + * @example req_abc123 + */ request_id: string; - /** @example processing_timeout */ + /** + * @description Machine-readable terminal failure code, or null when no task failure is recorded. + * @example processing_timeout + */ error_code: string | null; + /** @description Human-readable terminal failure detail, or null when no task failure is recorded. */ error_message: string | null; }; - File: { - /** @example file_3xYz9 */ + Effect: { + /** @example video-muscle-max */ id: string; /** @enum {string} */ + object: "effect"; + /** @example Muscle Transformation */ + name: string; + description: string; + /** @enum {string} */ + output_type: "image" | "video"; + /** @example transformation */ + category: string; + tags: string[]; + input: { + images_min: number; + images_max: number; + accepted_types: ("image/jpeg" | "image/png" | "image/webp")[]; + /** @description Maximum downloaded bytes per input image. When omitted, BeatAPI enforces 50 MB. */ + max_size_mb?: number; + /** @description Maximum decoded width or height. BeatAPI inspects the actual image header before charging. */ + max_dimension_px?: number; + subject_requirements?: string[]; + }; + options: { + aspect_ratios?: string[]; + resolutions?: string[]; + duration_seconds?: number[]; + bgm?: boolean; + seed?: boolean; + }; + preview: { + /** Format: uri */ + cover_url: string | null; + /** Format: uri */ + media_url: string | null; + }; + version: number; + /** @enum {string} */ + status: "testing" | "active" | "paused"; + }; + EffectResponse: { + data: components["schemas"]["Effect"]; + }; + EffectListResponse: { + data: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Effect"][]; + }; + }; + File: { + /** + * @description Stable uploaded file ID. + * @example file_3xYz9 + */ + id: string; + /** + * @description Object discriminator; always `file`. + * @enum {string} + */ object: "file"; /** * Format: uri + * @description Long-lived BeatAPI HTTPS URL to use in workflow or model requests. * @example https://media.beatapi.io/inputs/file_3xYz9.mp3 */ url: string; - /** @example inputs/file_3xYz9.mp3 */ + /** + * @description BeatAPI storage key for support and diagnostics. + * @example inputs/file_3xYz9.mp3 + */ key: string; - /** @example audio/mpeg */ + /** + * @description Accepted MIME type detected for the uploaded file. + * @example audio/mpeg + */ mime_type: string; - /** @example 1048576 */ + /** + * @description Uploaded file size in bytes. + * @example 1048576 + */ size_bytes: number; /** * @description Present for uploaded audio files after server-side duration detection. @@ -571,32 +897,54 @@ export interface components { * @example mp3_frame_scan */ audio_duration_source?: string; - /** @enum {string} */ + /** + * @description File purpose; currently always `input`. + * @enum {string} + */ purpose: "input"; - /** @example 1782210000 */ + /** + * @description Unix timestamp when the file was stored. + * @example 1782210000 + */ created_at: number; }; WebhookEndpoint: { - /** @example wh_9aBcD */ + /** + * @description Stable webhook endpoint ID used for get, update, and delete operations. + * @example wh_9aBcD + */ id: string; - /** @enum {string} */ + /** + * @description Object discriminator; always `webhook_endpoint`. + * @enum {string} + */ object: "webhook_endpoint"; /** * Format: uri + * @description Public HTTPS callback URL receiving subscribed task events. * @example https://example.com/beatapi-webhook */ url: string; - /** @example Production webhook */ + /** + * @description Account-defined label for the endpoint. + * @example Production webhook + */ description: string; + /** @description Task event types delivered to this endpoint. */ events: ("task.succeeded" | "task.failed")[]; - /** @enum {string} */ + /** + * @description Delivery status. Disabled endpoints do not receive events. + * @enum {string} + */ status: "active" | "disabled"; /** * @description Returned in full only when the endpoint is created. Later responses return a masked value. * @example whsec_example_masked */ secret: string; + /** @description Unix timestamp when the endpoint was created. */ created_at: number; + /** @description Unix timestamp when the endpoint last changed. */ updated_at: number; }; WebhookEvent: { @@ -615,77 +963,764 @@ export interface components { WorkflowListResponse: { data: components["schemas"]["WorkflowList"]; }; - TaskResponse: { - data: components["schemas"]["Task"]; + GenerationModel: { + /** @enum {string} */ + id: "nano-banana" | "nano-banana-pro" | "gpt-image-2" | "seedream-5-pro" | "minimax-h3" | "seedance-2" | "seedance-2-fast" | "seedance-2-mini" | "veo-3.1" | "seedance-2.5" | "kling-3"; + /** @enum {string} */ + object: "generation_model"; + name: string; + /** @enum {string} */ + media_type: "image" | "video"; + input_modes: ("text" | "image" | "frames" | "reference")[]; }; - Usage: { + GenerationModelList: { /** @enum {string} */ - object: "usage"; - /** @description Current credit balance. It may be negative. */ - credit_balance: number; - total_tasks: number; - credits_settled: number; - credits_refunded: number; - concurrency: { - /** @example 2 */ - limit: number; - /** @description Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled credits without counting toward this value. */ - active: number; - }; - by_workflow: { - /** @enum {string} */ - workflow: "music-video" | "ecommerce-video"; - tasks: number; - credits_settled: number; - }[]; - realtime?: { - /** @description Total BeatAPI realtime sessions for this account. */ - sessions: number; - /** @description Credits settled by connected realtime sessions. */ - credits: number; - /** @description Realtime sessions in ready, connecting, or active state. */ - active: number; - }; + object: "list"; + data: components["schemas"]["GenerationModel"][]; }; - UsageResponse: { - data: components["schemas"]["Usage"]; + GenerationModelListResponse: { + data: components["schemas"]["GenerationModelList"]; }; - RealtimeSession: { - id: string; + ImageGenerationTaskCreateRequest: components["schemas"]["NanoBananaImageRequest"] | components["schemas"]["NanoBananaProImageRequest"] | components["schemas"]["GptImage2Request"] | components["schemas"]["Seedream5ProImageRequest"]; + NanoBananaImageRequest: { + /** + * @description Must be `nano-banana`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "nano-banana"; + /** @description Generation instructions. */ + prompt: string; + /** + * @description Output image aspect ratio. + * @default 1:1 + * @enum {string} + */ + aspect_ratio: "1:1" | "9:16" | "16:9" | "3:4" | "4:3" | "3:2" | "2:3" | "5:4" | "4:5" | "21:9" | "auto"; + /** + * @description Output image file format. + * @default png + * @enum {string} + */ + output_format: "png" | "jpeg"; + }; + NanoBananaProImageRequest: { + /** + * @description Must be `nano-banana-pro`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "nano-banana-pro"; + /** @description Generation or image-editing instructions. */ + prompt: string; + /** @description Public HTTPS reference-image URLs. Omit for text-to-image. */ + images?: string[]; + /** + * @description Output image aspect ratio. + * @default 1:1 + * @enum {string} + */ + aspect_ratio: "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "16:9" | "21:9" | "auto"; + /** + * @description Output resolution tier. + * @default 1K + * @enum {string} + */ + resolution: "1K" | "2K" | "4K"; + /** + * @description Output image file format. + * @default png + * @enum {string} + */ + output_format: "png" | "jpg"; + }; + /** + * @description `auto` only supports 1K. `1:1` does not support 4K. At 2K/4K, + * `5:4`, `4:5`, `3:1`, `1:3`, and `9:21` are unavailable. + */ + GptImage2Request: { + /** + * @description Must be `gpt-image-2`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "gpt-image-2"; + /** @description Generation or image-editing instructions. */ + prompt: string; + /** @description Public HTTPS reference-image URLs. Omit for text-to-image. */ + images?: string[]; + /** + * @description Output image aspect ratio. Availability also depends on resolution. + * @default auto + * @enum {string} + */ + aspect_ratio: "auto" | "1:1" | "3:2" | "2:3" | "4:3" | "3:4" | "5:4" | "4:5" | "16:9" | "9:16" | "2:1" | "1:2" | "3:1" | "1:3" | "21:9" | "9:21"; + /** + * @description Output resolution tier. + * @default 1K + * @enum {string} + */ + resolution: "1K" | "2K" | "4K"; + }; + Seedream5ProImageRequest: { + /** + * @description Must be `seedream-5-pro`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "seedream-5-pro"; + /** @description Generation or image-editing instructions. */ + prompt: string; + /** @description Public HTTPS reference-image URLs. Omit for text-to-image. */ + images?: string[]; + /** + * @description Output image aspect ratio. + * @default 1:1 + * @enum {string} + */ + aspect_ratio: "auto" | "1:1" | "4:3" | "3:4" | "16:9" | "9:16" | "3:2" | "2:3" | "21:9"; + /** + * @description Output resolution tier. + * @default 1K + * @enum {string} + */ + resolution: "1K" | "2K"; + /** + * @description Output image file format. + * @default png + * @enum {string} + */ + output_format: "png" | "jpeg"; + }; + VideoGenerationTaskCreateRequest: components["schemas"]["MinimaxH3VideoRequest"] | components["schemas"]["Seedance2VideoRequest"] | components["schemas"]["Seedance2FastVideoRequest"] | components["schemas"]["Seedance2MiniVideoRequest"] | components["schemas"]["Veo31VideoRequest"] | components["schemas"]["Seedance25VideoRequest"] | components["schemas"]["Kling3VideoRequest"]; + /** @description `images` cannot be combined with any `reference_*` input. */ + MinimaxH3VideoRequest: { + /** + * @description Must be `minimax-h3`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "minimax-h3"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** @description Public HTTPS image references for multimodal reference generation. */ + reference_images?: string[]; + /** @description Public HTTPS video references for multimodal reference generation. */ + reference_videos?: string[]; + /** @description Public HTTPS audio references for multimodal reference generation. */ + reference_audios?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Output video aspect ratio. + * @default adaptive + * @enum {string} + */ + aspect_ratio: "adaptive" | "21:9" | "16:9" | "4:3" | "1:1" | "3:4" | "9:16"; + /** + * @description Output resolution tier. + * @default 768P + * @enum {string} + */ + resolution: "768P" | "2K"; + }; + /** @description `images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video. */ + Seedance2VideoRequest: { + /** + * @description Must be `seedance-2`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "seedance-2"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** @description Public HTTPS image references for multimodal reference generation. */ + reference_images?: string[]; + /** @description Public HTTPS video references for multimodal reference generation. */ + reference_videos?: string[]; + /** @description Public HTTPS audio references. Audio also requires at least one reference image or video. */ + reference_audios?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Output video aspect ratio. + * @default adaptive + * @enum {string} + */ + aspect_ratio: "adaptive" | "21:9" | "16:9" | "4:3" | "1:1" | "3:4" | "9:16"; + /** + * @description Output resolution tier. + * @default 720p + * @enum {string} + */ + resolution: "480p" | "720p" | "1080p" | "4k"; + /** + * @description Generate synchronized audio with the video. + * @default true + */ + generate_audio: boolean; + }; + /** @description `images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video. */ + Seedance2FastVideoRequest: { + /** + * @description Must be `seedance-2-fast`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "seedance-2-fast"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** @description Public HTTPS image references for multimodal reference generation. */ + reference_images?: string[]; + /** @description Public HTTPS video references for multimodal reference generation. */ + reference_videos?: string[]; + /** @description Public HTTPS audio references. Audio also requires at least one reference image or video. */ + reference_audios?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Output video aspect ratio. + * @default adaptive + * @enum {string} + */ + aspect_ratio: "adaptive" | "21:9" | "16:9" | "4:3" | "1:1" | "3:4" | "9:16"; + /** + * @description Output resolution tier. + * @default 720p + * @enum {string} + */ + resolution: "480p" | "720p"; + /** + * @description Generate synchronized audio with the video. + * @default true + */ + generate_audio: boolean; + }; + /** @description Low-cost Seedance 2.0 route. `images` cannot be combined with any `reference_*` input. Generated audio is not supported. */ + Seedance2MiniVideoRequest: { + /** + * @description Must be `seedance-2-mini`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "seedance-2-mini"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** @description Public HTTPS image references for multimodal reference generation. */ + reference_images?: string[]; + /** @description Public HTTPS video references for multimodal reference generation. */ + reference_videos?: string[]; + /** @description Public HTTPS audio references. Audio also requires at least one reference image or video. */ + reference_audios?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Output video aspect ratio. + * @default adaptive + * @enum {string} + */ + aspect_ratio: "adaptive" | "21:9" | "16:9" | "4:3" | "1:1" | "3:4" | "9:16"; + /** + * @description Output resolution tier. + * @default 720p + * @enum {string} + */ + resolution: "480p" | "720p"; + }; + Veo31VideoRequest: (components["schemas"]["Veo31TextOrFrameVideoRequest"] | components["schemas"]["Veo31ReferenceVideoRequest"]) & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + model: "veo-3.1"; + }; + /** + * @description Veo 3.1 text or first/last-frame generation. Output is fixed at 8 seconds + * and defaults to the Quality tier. + */ + Veo31TextOrFrameVideoRequest: { + /** + * @description Must be `veo-3.1`. + * @constant + */ + model: "veo-3.1"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** + * @description Output video aspect ratio. + * @default 16:9 + * @enum {string} + */ + aspect_ratio: "16:9" | "9:16" | "auto"; + /** + * @description Text or frame generation tier. + * @default Quality + * @enum {string} + */ + quality: "Quality" | "Fast" | "Lite"; + /** @description Optional watermark text forwarded to the selected model. */ + watermark?: string; + /** @description Allow prompt translation before generation. */ + enable_translation?: boolean; + }; + /** + * @description Veo 3.1 reference-image generation. Output is fixed at 8 seconds and + * supports the Fast or Lite tier, defaulting to Fast. + */ + Veo31ReferenceVideoRequest: { + /** + * @description Must be `veo-3.1`. + * @constant + */ + model: "veo-3.1"; + /** @description Video generation instructions. */ + prompt: string; + /** @description Public HTTPS reference images. */ + reference_images: string[]; + /** + * @description Output video aspect ratio. + * @default 16:9 + * @enum {string} + */ + aspect_ratio: "16:9" | "9:16" | "auto"; + /** + * @description Reference-image generation tier. + * @default Fast + * @enum {string} + */ + quality: "Fast" | "Lite"; + /** @description Optional watermark text forwarded to the selected model. */ + watermark?: string; + /** @description Allow prompt translation before generation. */ + enable_translation?: boolean; + }; + /** @description `images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video. */ + Seedance25VideoRequest: { + /** + * @description Must be `seedance-2.5`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "seedance-2.5"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. */ + images?: string[]; + /** @description Public HTTPS image references for multimodal reference generation. */ + reference_images?: string[]; + /** @description Public HTTPS video references for multimodal reference generation. */ + reference_videos?: string[]; + /** @description Public HTTPS audio references. Audio also requires at least one reference image or video. */ + reference_audios?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Output video aspect ratio. + * @default adaptive + * @enum {string} + */ + aspect_ratio: "adaptive" | "21:9" | "16:9" | "4:3" | "1:1" | "3:4" | "9:16"; + /** + * @description Seedance 2.5 currently returns 720p output. + * @default 720p + * @constant + */ + resolution: "720p"; + /** + * @description Generate synchronized audio with the video. + * @default true + */ + generate_audio: boolean; + /** + * @description Reproducibility seed. Use -1 for a random seed. + * @default -1 + */ + seed: number; + }; + KlingShot: { + /** @description Instructions for this shot. */ + prompt: string; + /** @description Shot duration in seconds. All shot durations must sum to the task duration. */ + duration: number; + }; + /** @description Use 2-4 image URLs or one video URL. A video element may include one audio URL and a 3-8 second segment in milliseconds. */ + KlingElement: { + /** @description Stable name used to reference this element in the prompt. */ + name: string; + /** @description Optional description of the subject or object. */ + description?: string; + /** @description Two to four image URLs, or one video URL. */ + element_input_urls: string[]; + /** @description Optional audio URL used with a video element. */ + element_input_audio_urls?: string[]; + /** @description Video element segment start time in milliseconds. */ + start_time?: number; + /** @description Video element segment end time in milliseconds. The segment must be 3-8 seconds. */ + end_time?: number; + }; + /** @description Multi-shot mode accepts one first-frame image, requires `multi_prompt`, and defaults sound to true. Shot durations must sum to `duration`. */ + Kling3VideoRequest: { + /** + * @description Must be `kling-3`. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "kling-3"; + /** @description Video generation instructions. */ + prompt: string; + /** @description One first-frame image or first- and last-frame images as public HTTPS URLs. Multi-shot mode accepts exactly one. */ + images?: string[]; + /** + * @description Requested output duration in seconds. + * @default 5 + */ + duration: number; + /** + * @description Defaults to 16:9 for text generation. Omit it with frame images to adapt to the input aspect ratio. + * @enum {string} + */ + aspect_ratio?: "16:9" | "9:16" | "1:1"; + /** + * @description Output quality tier. + * @default pro + * @enum {string} + */ + resolution: "std" | "pro" | "4K"; + /** @description Generate synchronized sound. Defaults to true in multi-shot mode. */ + sound?: boolean; + /** + * @description Enable storyboard-style multi-shot generation. + * @default false + */ + multi_shots: boolean; + /** @description Shot definitions required when `multi_shots=true`. */ + multi_prompt?: components["schemas"]["KlingShot"][]; + /** @description Up to three reusable subject or object references. */ + elements?: components["schemas"]["KlingElement"][]; + }; + TaskResponse: { + /** @description Accepted or current BeatAPI task state. */ + data: components["schemas"]["Task"]; + }; + Usage: { /** @enum {string} */ + object: "usage"; + /** + * Format: double + * @description Current USD balance. The compatibility field name is retained; 1 Credit equals $1 USD. The balance may be negative. + */ + credit_balance: number; + total_tasks: number; + /** Format: double */ + credits_settled: number; + /** Format: double */ + credits_refunded: number; + concurrency: { + /** @example 2 */ + limit: number; + /** @description Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled USD usage without counting toward this value. */ + active: number; + }; + /** @description Compatibility view containing workflow tasks only. Image, video, and Effect tasks are reported under by_capability instead. */ + by_workflow: { + /** @enum {string} */ + workflow: "music-video" | "ecommerce-video"; + tasks: number; + /** Format: double */ + credits_settled: number; + }[]; + by_capability: { + /** @enum {string} */ + task_kind: "workflow" | "effect" | "image" | "video"; + capability_id: string; + tasks: number; + /** Format: double */ + credits_settled: number; + }[]; + by_model: { + /** @enum {string} */ + media_type: "image" | "video"; + model: string; + tasks: number; + /** Format: double */ + credits_settled: number; + }[]; + by_api_key: { + api_key_id: string; + title: string; + key_prefix: string; + tasks: number; + /** Format: double */ + credits_settled: number; + }[]; + realtime?: { + /** @description Total BeatAPI realtime sessions for this account. */ + sessions: number; + /** + * Format: double + * @description USD amount settled by connected realtime sessions. + */ + credits: number; + /** @description Realtime sessions in ready, connecting, or active state. */ + active: number; + }; + }; + UsageResponse: { + data: components["schemas"]["Usage"]; + }; + MusicVideoTaskCreateRequest: components["schemas"]["StandardMusicVideoTaskCreateRequest"] | components["schemas"]["PremiumMusicVideoTaskCreateRequest"]; + StandardMusicVideoTaskCreateRequest: { + /** + * @description May be omitted to preserve the backwards-compatible Standard contract. + * @default standard + * @enum {string} + */ + mv_tier: "standard"; + /** @description Standard scene images. Provide 1-7 public HTTPS PNG, JPEG, or WebP URLs; place the primary subject or opening scene first. Upload local files through `POST /v1/files` and use the returned `data.url`. */ + images: string[]; + /** + * Format: uri + * @description Public HTTPS audio URL; Standard audio must be 10-180 seconds. + */ + audio_url: string; + /** @description Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters. */ + prompt?: string; + /** + * @description Dialogue and lyric language used by the Standard workflow. + * @enum {string} + */ + language?: "en" | "zh"; + /** + * @description Generation quality tier. High quality is unavailable at 540p. + * @default standard + * @enum {string} + */ + quality: "standard" | "high"; + /** @description Optional concise visual style, such as cinematic, anime, documentary, or fashion editorial. */ + style?: string; + /** + * @description Target output placement. Set explicitly for the destination player or social feed. + * @enum {string} + */ + aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4"; + /** + * @description Output resolution. 540p cannot be combined with high quality or lip sync. + * @default 720p + * @enum {string} + */ + resolution: "540p" | "720p" | "1080p"; + /** + * @description Generate lip-synchronized performance. When true, `lip_ref_url` is required. + * @default false + */ + lip_sync: boolean; + /** + * Format: uri + * @description Public HTTPS close-up, front-facing face image used for Standard lip sync. + */ + lip_ref_url?: string; + /** + * @description Burn generated or supplied subtitles into the final video. + * @default false + */ + add_subtitle: boolean; + /** + * @description Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. + * @example #FFFFFF + */ + subtitle_color?: string; + /** + * Format: uri + * @description Optional public HTTPS `.srt` subtitle file. Upload a local subtitle through `POST /v1/files`. + */ + srt_url?: string; + /** @description Billing fallback only; detected audio duration wins. */ + duration?: number; + /** + * @description Auto composes the final Music Video; manual pauses at `requires_action` so shots can be reviewed or edited before compose. + * @default auto + * @enum {string} + */ + compose_mode: "auto" | "manual"; + } & (unknown & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + mv_tier: "standard"; + }); + PremiumMusicVideoTaskCreateRequest: (components["schemas"]["PremiumMusicVideoSingTaskCreateRequest"] | components["schemas"]["PremiumMusicVideoSingPerformTaskCreateRequest"] | components["schemas"]["PremiumMusicVideoDanceTaskCreateRequest"] | components["schemas"]["PremiumMusicVideoPerformTaskCreateRequest"]) & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + mv_tier: "premium"; + }; + PremiumMusicVideoTaskRequestBase: { + /** + * @description Selects the Premium Music Video workflow and its mode-specific inputs. + * @enum {string} + */ + mv_tier: "premium"; + /** + * @description Premium performance mode. Sing modes require `lip_ref_urls`; dance and perform require exactly six `images`. + * @enum {string} + */ + mv_mode: "sing" | "sing_perform" | "dance" | "perform"; + /** + * Format: uri + * @description Public HTTPS audio URL; Premium audio must be 10-300 seconds. + */ + audio_url: string; + /** @description Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters. */ + prompt?: string; + style?: string; + /** + * @description Target output placement. Set explicitly for the destination player or social feed. + * @enum {string} + */ + aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4"; + /** + * @description Premium output is fixed to 720p. + * @default 720p + * @enum {string} + */ + resolution: "720p"; + /** + * @description Burn generated subtitles into the final video. + * @default false + */ + add_subtitle: boolean; + /** + * @description Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. + * @example #FFFFFF + */ + subtitle_color?: string; + /** @description Premium billing fallback only; detected audio duration wins. */ + duration?: number; + }; + PremiumMusicVideoSingTaskCreateRequest: components["schemas"]["PremiumMusicVideoTaskRequestBase"] & { + /** @enum {string} */ + mv_mode?: "sing"; + /** @description Optional Premium scene images for sing mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. */ + images?: string[]; + /** @description Required for sing mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. */ + lip_ref_urls: string[]; + }; + PremiumMusicVideoSingPerformTaskCreateRequest: components["schemas"]["PremiumMusicVideoTaskRequestBase"] & { + /** @enum {string} */ + mv_mode?: "sing_perform"; + /** @description Optional Premium scene images for sing and perform mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. */ + images?: string[]; + /** @description Required for sing and perform mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. */ + lip_ref_urls: string[]; + }; + PremiumMusicVideoDanceTaskCreateRequest: components["schemas"]["PremiumMusicVideoTaskRequestBase"] & { + /** @enum {string} */ + mv_mode?: "dance"; + /** @description Required for dance mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. */ + images: string[]; + }; + PremiumMusicVideoPerformTaskCreateRequest: components["schemas"]["PremiumMusicVideoTaskRequestBase"] & { + /** @enum {string} */ + mv_mode?: "perform"; + /** @description Required for perform mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. */ + images: string[]; + }; + EditMusicVideoShotRequest: { + prompt: string; + /** @description Premium tasks only. Optional replacement scene images; an empty array is treated as omitted. Standard tasks reject this field. */ + images?: string[]; + }; + RealtimeSession: { + /** @description Stable Realtime Session ID used to inspect or close the session. */ + id: string; + /** + * @description Object discriminator; always `realtime.session`. + * @enum {string} + */ object: "realtime.session"; /** * @description Active means BeatAPI accepted the first billing heartbeat after remote output began. * @enum {string} */ status: "ready" | "connecting" | "active" | "closed" | "failed" | "expired"; - /** @description Returned only by POST. Give this short-lived BeatAPI secret to the browser SDK; never give the browser an sk_ API key. */ - client_secret?: string; - /** Format: date-time */ + /** + * Format: date-time + * @description Time when the unconnected short-lived session credential expires. + */ expires_at: string; - /** @enum {integer} */ + /** + * @description Maximum selected live duration and billing tier in seconds. + * @enum {integer} + */ max_duration_seconds: 15 | 60 | 300; + /** @description Exact browser origins authorized to use this Session. */ allowed_origins: string[]; + /** @description USD reservation, settlement, and refund lifecycle for this Realtime Session. Compatibility field names are retained. */ credits: { + /** + * Format: double + * @description USD amount reserved when the Session is created. + */ reserved: number; + /** + * Format: double + * @description USD amount settled after the first accepted billing heartbeat. + */ settled: number; + /** + * Format: double + * @description USD amount refunded if the Session ends without billing activation. + */ refunded: number; }; + /** @description Correlation ID to retain for logs and BeatAPI support. */ request_id: string; - /** Format: date-time */ + /** + * Format: date-time + * @description Time when the Session was created. + */ created_at: string; /** * Format: date-time * @description Time of the first accepted BeatAPI billing heartbeat; null before billing activation. */ connected_at: string | null; - /** Format: date-time */ + /** + * Format: date-time + * @description Time when the Session closed, or null while it remains open. + */ closed_at: string | null; }; + RealtimeSessionCreated: components["schemas"]["RealtimeSession"] & { + /** @description Short-lived BeatAPI browser credential returned only by POST. Never expose an sk_ API key to the browser. */ + client_secret: string; + }; + RealtimeSessionCreateResponse: { + /** @description Created Realtime Session including the one-time short-lived browser credential. */ + data: components["schemas"]["RealtimeSessionCreated"]; + }; RealtimeSessionResponse: { data: components["schemas"]["RealtimeSession"]; }; FileResponse: { + /** @description Uploaded file metadata and the public HTTPS URL to use in later requests. */ data: components["schemas"]["File"]; }; WebhookEndpointList: { @@ -697,6 +1732,7 @@ export interface components { data: components["schemas"]["WebhookEndpointList"]; }; WebhookEndpointResponse: { + /** @description Created or retrieved webhook endpoint. Public API responses return the full signing secret at creation and mask it afterward; authenticated dashboard owners can explicitly reveal it again. */ data: components["schemas"]["WebhookEndpoint"]; }; DeleteResponse: { @@ -706,10 +1742,16 @@ export interface components { }; }; Error: { + /** @description Structured BeatAPI error. Use `code` for program logic and retain `request_id` for support. */ error: { - /** @enum {string} */ - code: "bad_request" | "unauthorized" | "forbidden" | "not_found" | "insufficient_credits" | "idempotency_conflict" | "user_concurrency_exceeded" | "rate_limit_exceeded" | "processing_unavailable" | "processing_failed" | "processing_timeout" | "result_transfer_failed" | "invalid_signature" | "realtime_disabled" | "realtime_capacity_unavailable" | "realtime_session_expired" | "origin_not_allowed" | "invalid_client_secret" | "transport_not_allowed" | "internal_error"; + /** + * @description Stable machine-readable error code. + * @enum {string} + */ + code: "bad_request" | "unauthorized" | "forbidden" | "not_found" | "insufficient_credits" | "idempotency_conflict" | "user_concurrency_exceeded" | "rate_limit_exceeded" | "content_policy_violation" | "processing_unavailable" | "processing_failed" | "processing_timeout" | "result_transfer_failed" | "invalid_signature" | "realtime_disabled" | "realtime_capacity_unavailable" | "realtime_session_expired" | "origin_not_allowed" | "invalid_client_secret" | "transport_not_allowed" | "internal_error"; + /** @description Human-readable detail intended for logs and debugging. */ message: string; + /** @description Correlation ID to retain for BeatAPI support. */ request_id: string; /** @description Present on retryable rate-limit or capacity responses when the client should wait before retrying. */ retry_after_seconds?: number; @@ -774,6 +1816,42 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; + /** @description BeatAPI could not complete the request because of an internal or storage failure. */ + InternalError: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": { + * "code": "internal_error", + * "message": "Internal error. Contact support with the request_id if the problem continues.", + * "request_id": "req_xxx" + * } + * } + */ + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description BeatAPI processing is temporarily unavailable or did not complete within the processing window. */ + ProcessingUnavailable: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": { + * "code": "processing_unavailable", + * "message": "Task processing is temporarily unavailable.", + * "request_id": "req_xxx" + * } + * } + */ + "application/json": components["schemas"]["Error"]; + }; + }; }; parameters: never; requestBodies: never; @@ -821,13 +1899,189 @@ export interface operations { "application/json": components["schemas"]["WorkflowListResponse"]; }; }; - 429: components["responses"]["RateLimited"]; + 429: components["responses"]["RateLimited"]; + }; + }; + listGenerationModels: { + parameters: { + query?: { + media_type?: "image" | "video"; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Generation model list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GenerationModelListResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 429: components["responses"]["RateLimited"]; + }; + }; + createImageGenerationTask: { + parameters: { + query?: never; + header?: { + "Idempotency-Key"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ImageGenerationTaskCreateRequest"]; + }; + }; + responses: { + /** @description Image generation task accepted */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + /** @description Insufficient USD balance */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency key conflicts with another request body */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + }; + }; + createVideoGenerationTask: { + parameters: { + query?: never; + header?: { + "Idempotency-Key"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VideoGenerationTaskCreateRequest"]; + }; + }; + responses: { + /** @description Video generation task accepted */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + /** @description Insufficient USD balance */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency key conflicts with another request body */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + }; + }; + listEffects: { + parameters: { + query?: { + output_type?: "image" | "video"; + category?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Active Effect catalog */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EffectListResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 429: components["responses"]["RateLimited"]; + }; + }; + getEffect: { + parameters: { + query?: never; + header?: never; + path: { + effect_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Effect definition and immutable current version contract */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EffectResponse"]; + }; + }; + /** @description Effect is unknown or not currently published. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; }; }; - createMusicVideoTask: { + createEffectTask: { parameters: { query?: never; - header?: never; + header?: { + "Idempotency-Key"?: string; + }; path?: never; cookie?: never; }; @@ -835,66 +2089,44 @@ export interface operations { content: { /** * @example { + * "effect_id": "video-muscle-max", * "images": [ - * "https://media.beatapi.io/samples/neon-singer.png" + * "https://media.beatapi.io/samples/portrait.png" * ], - * "audio_url": "https://media.beatapi.io/samples/neon-singer-preview.mp3", - * "prompt": "Neon rooftop performance with metro cutaways and cinematic light trails.", - * "language": "en", - * "quality": "standard", - * "resolution": "720p", - * "compose_mode": "auto" + * "options": { + * "resolution": "720p", + * "duration": 12 + * } * } */ "application/json": { - /** @description 1-7 public HTTPS image URLs. Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller, with aspect ratio from 1:4 to 4:1. /v1/files uploads are checked before use; third-party URLs may be rejected during processing if invalid. */ - images: string[]; - /** - * Format: uri - * @description Public HTTPS audio URL. Use mp3, wav, aac, or m4a; file size should be 50 MB or smaller and duration must be 10-180 seconds. - */ - audio_url: string; - /** @description Optional creative prompt, at most 3000 characters. */ - prompt?: string; - /** @enum {string} */ - language?: "en" | "zh"; - lip_sync?: boolean; - /** - * Format: uri - * @description Public HTTPS image URL for lip-sync face reference. Use a clear, front-facing close-up face reference. - */ - lip_ref_url?: string; - /** @description Optional style phrase, at most 200 characters. */ - style?: string; - /** - * @default standard - * @enum {string} - */ - quality?: "standard" | "high"; - /** @enum {string} */ - aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4"; - /** - * @default 720p - * @enum {string} - */ - resolution?: "540p" | "720p" | "1080p"; - add_subtitle?: boolean; - /** @example #FFFFFF */ - subtitle_color?: string; - /** Format: uri */ - srt_url?: string; - /** @description Billing fallback when audio duration cannot be detected. It must be 10-180 seconds and cannot override a detected audio duration. */ - duration?: number; /** - * @default auto - * @enum {string} + * @description Stable published Effect ID from `GET /v1/effects`. + * @example video-muscle-max */ - compose_mode?: "auto" | "manual"; + effect_id: string; + /** @description Optional immutable version. Omit to use the current published version. */ + effect_version?: number; + /** @description Public HTTPS input images in the order required by the selected Effect version. Read `GET /v1/effects/{effect_id}` for the exact count and accepted media rules; upload local files with `POST /v1/files`. */ + images: string[]; + /** @description Optional controls supported by the selected Effect version. Omit unsupported controls; the catalog is the source of truth. */ + options?: { + /** @description Requested output aspect ratio when the selected Effect exposes this option. */ + aspect_ratio?: string; + /** @description Requested output resolution when the selected Effect exposes this option. */ + resolution?: string; + /** @description Requested video duration in seconds when the selected Effect exposes this option. */ + duration?: number; + /** @description Include background music when supported by the selected Effect. */ + bgm?: boolean; + /** @description Optional deterministic seed when supported by the selected Effect. */ + seed?: number; + }; }; }; }; responses: { - /** @description Task accepted */ + /** @description Effect task accepted */ 201: { headers: { [name: string]: unknown; @@ -903,26 +2135,26 @@ export interface operations { /** * @example { * "data": { - * "id": "task_8K2qA", + * "id": "task_effect123", * "object": "task", - * "workflow": "music-video", + * "task_kind": "effect", + * "capability_id": "video-muscle-max", + * "capability_version": 1, + * "effect_id": "video-muscle-max", + * "effect_version": 1, * "status": "queued", * "stage": "queued", - * "storyboard": { - * "shots": [] - * }, * "created_at": 1782210000, * "updated_at": 1782210000, * "completed_at": null, * "output": null, * "usage": { - * "credits_reserved": 75, - * "credits_charged": 75, - * "billable_duration_seconds": 15, + * "credits_reserved": 1.2, + * "credits_charged": 1.2, * "credits_settled": 0, * "credits_refunded": 0 * }, - * "request_id": "req_abc123", + * "request_id": "req_effect123", * "error_code": null, * "error_message": null * } @@ -933,6 +2165,66 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + /** @description Insufficient USD balance. */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Effect or requested version is unavailable. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency key conflicts with another request body. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + }; + }; + createMusicVideoTask: { + parameters: { + query?: never; + header?: { + /** + * @description Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + * @example mv-create-cus_123-01 + */ + "Idempotency-Key"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MusicVideoTaskCreateRequest"]; + }; + }; + responses: { + /** @description Task accepted */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; /** @description Account balance is not sufficient for this task. */ 402: { headers: { @@ -951,6 +2243,24 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + /** @description The Idempotency-Key was reused with a different body or while another request with that key is still being processed. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": { + * "code": "idempotency_conflict", + * "message": "This Idempotency-Key was already used with a different request body.", + * "request_id": "req_xxx" + * } + * } + */ + "application/json": components["schemas"]["Error"]; + }; + }; /** @description User concurrency exceeded. */ 429: { headers: { @@ -974,7 +2284,13 @@ export interface operations { editMusicVideoShot: { parameters: { query?: never; - header?: never; + header?: { + /** + * @description Optional retry key. Reusing the same key for this task, shot, and request body returns the accepted task without charging the USD amount again; changing any of them returns `409 idempotency_conflict`. + * @example music-edit-task_8K2qA-shot_xxx-01 + */ + "Idempotency-Key"?: string; + }; path: { /** @example task_8K2qA */ task_id: string; @@ -988,26 +2304,12 @@ export interface operations { /** * @example { * "prompt": "Night city chorus with brighter face lighting.", - * "duration": 5, - * "quality": "standard", - * "resolution": "720p" + * "images": [ + * "https://media.beatapi.io/samples/stage.png" + * ] * } */ - "application/json": { - prompt: string; - /** @default 5 */ - duration?: number; - /** - * @default standard - * @enum {string} - */ - quality?: "standard" | "high"; - /** - * @default 720p - * @enum {string} - */ - resolution?: "540p" | "720p" | "1080p"; - }; + "application/json": components["schemas"]["EditMusicVideoShotRequest"]; }; }; responses: { @@ -1022,6 +2324,15 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + /** @description Account balance is not sufficient for this shot edit. */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; /** @description Task or shot not found. */ 404: { headers: { @@ -1031,6 +2342,18 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + /** @description The Idempotency-Key was reused for a different task, shot, or request body, or the same request is still being processed. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 502: components["responses"]["ProcessingUnavailable"]; }; }; getMusicVideoShotMedia: { @@ -1088,12 +2411,21 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 502: components["responses"]["ProcessingUnavailable"]; }; }; composeMusicVideoTask: { parameters: { query?: never; - header?: never; + header?: { + /** + * @description Optional retry key. Reusing the same key for this task and request body returns the accepted task without charging the $1 compose amount again; changing either returns `409 idempotency_conflict`. + * @example music-compose-task_8K2qA-01 + */ + "Idempotency-Key"?: string; + }; path: { /** @example task_8K2qA */ task_id: string; @@ -1127,6 +2459,15 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + /** @description Account balance is not sufficient for this compose operation. */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; /** @description Task or shot not found. */ 404: { headers: { @@ -1136,12 +2477,30 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + /** @description The Idempotency-Key was reused for a different task or request body, or the same request is still being processed. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 502: components["responses"]["ProcessingUnavailable"]; }; }; createEcommerceVideoTask: { parameters: { query?: never; - header?: never; + header?: { + /** + * @description Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + * @example ecommerce-create-cus_123-01 + */ + "Idempotency-Key"?: string; + }; path?: never; cookie?: never; }; @@ -1158,12 +2517,21 @@ export interface operations { * } */ "application/json": { + /** @description Primary product or scene image first, followed by up to six additional public HTTPS PNG, JPEG, or WebP product or lifestyle images. Upload local files with `POST /v1/files` and use the returned `data.url`. */ images: string[]; + /** @description Required target output duration in seconds and the basis for USD calculation. Allowed range is 10-60 seconds. */ duration: number; + /** @description Optional creative direction, audience, product benefit, offer, tone, scenes, or call to action. Maximum 2000 characters. */ prompt?: string; - /** @enum {string} */ + /** + * @description Target output placement. Use 16:9 for landscape, 9:16 for vertical social, or 1:1 for square placements; set explicitly for stable layout. + * @enum {string} + */ aspect_ratio?: "16:9" | "9:16" | "1:1"; - /** @enum {string} */ + /** + * @description Dialogue and narration language. Use `en` for English or `zh` for Chinese; set explicitly when the prompt contains mixed languages. + * @enum {string} + */ language?: "en" | "zh"; }; }; @@ -1180,6 +2548,9 @@ export interface operations { * "data": { * "id": "task_p9Lm2", * "object": "task", + * "task_kind": "workflow", + * "capability_id": "ecommerce-video", + * "capability_version": 1, * "workflow": "ecommerce-video", * "status": "queued", * "stage": "queued", @@ -1188,8 +2559,8 @@ export interface operations { * "completed_at": null, * "output": null, * "usage": { - * "credits_reserved": 225, - * "credits_charged": 225, + * "credits_reserved": 4.5, + * "credits_charged": 4.5, * "billable_duration_seconds": 15, * "credits_settled": 0, * "credits_refunded": 0 @@ -1223,6 +2594,24 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + /** @description The Idempotency-Key was reused with a different body or while another request with that key is still being processed. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": { + * "code": "idempotency_conflict", + * "message": "This Idempotency-Key was already used with a different request body.", + * "request_id": "req_xxx" + * } + * } + */ + "application/json": components["schemas"]["Error"]; + }; + }; /** @description User concurrency exceeded. */ 429: { headers: { @@ -1281,6 +2670,7 @@ export interface operations { parameters: { query?: never; header: { + /** @example rts-create-cus_123-01 */ "Idempotency-Key": string; }; path?: never; @@ -1300,9 +2690,14 @@ export interface operations { * } */ "application/json": { - /** @enum {integer} */ + /** + * @description Required maximum live session duration in seconds. The USD amount is reserved for the selected 15, 60, or 300 second tier. + * @enum {integer} + */ max_duration_seconds: 15 | 60 | 300; + /** @description Exact browser origins allowed to use the short-lived session secret. */ allowed_origins: string[]; + /** @description Optional server-defined string metadata for your own correlation. Up to 20 keys; keys are at most 64 characters and values at most 256 characters. */ metadata?: { [key: string]: string; }; @@ -1316,12 +2711,36 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["RealtimeSessionResponse"]; + /** + * @example { + * "data": { + * "id": "rts_8K2qA", + * "object": "realtime.session", + * "status": "ready", + * "client_secret": "brt_live_example_short_lived_secret", + * "expires_at": "2026-08-12T10:01:00.000Z", + * "max_duration_seconds": 60, + * "allowed_origins": [ + * "https://app.example.com" + * ], + * "credits": { + * "reserved": 1.2, + * "settled": 0, + * "refunded": 0 + * }, + * "request_id": "req_abc123", + * "created_at": "2026-08-12T10:00:00.000Z", + * "connected_at": null, + * "closed_at": null + * } + * } + */ + "application/json": components["schemas"]["RealtimeSessionCreateResponse"]; }; }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; - /** @description Insufficient credits */ + /** @description Insufficient USD balance */ 402: { headers: { [name: string]: unknown; @@ -1434,29 +2853,60 @@ export interface operations { * @example { * "data": { * "object": "usage", - * "credit_balance": 1080, + * "credit_balance": 21.6, * "total_tasks": 12, - * "credits_settled": 720, - * "credits_refunded": 450, + * "credits_settled": 14.4, + * "credits_refunded": 9, * "concurrency": { * "limit": 2, * "active": 1 * }, * "realtime": { * "sessions": 3, - * "credits": 90, + * "credits": 1.8, * "active": 1 * }, * "by_workflow": [ * { * "workflow": "music-video", * "tasks": 8, - * "credits_settled": 480 + * "credits_settled": 9.6 * }, * { * "workflow": "ecommerce-video", * "tasks": 4, - * "credits_settled": 240 + * "credits_settled": 4.8 + * } + * ], + * "by_capability": [ + * { + * "task_kind": "image", + * "capability_id": "seedream-5-pro", + * "tasks": 3, + * "credits_settled": 0.42 + * }, + * { + * "task_kind": "video", + * "capability_id": "veo-3.1", + * "tasks": 2, + * "credits_settled": 14 + * } + * ], + * "by_model": [ + * { + * "media_type": "image", + * "model": "seedream-5-pro", + * "tasks": 3, + * "credits_settled": 0.42 + * } + * ], + * "by_api_key": [ + * { + * "api_key_id": "key_abc123", + * "title": "Production", + * "key_prefix": "sk_live_abcd", + * "tasks": 12, + * "credits_settled": 14.4 * } * ] * } @@ -1483,6 +2933,14 @@ export interface operations { /** @enum {string} */ purpose?: "input"; }; + "image/png": string; + "image/jpeg": string; + "image/webp": string; + "audio/mpeg": string; + "audio/wav": string; + "audio/aac": string; + "audio/mp4": string; + "application/x-subrip": string; }; }; responses: { @@ -1513,6 +2971,8 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; }; }; listWebhookEndpoints: { @@ -1557,6 +3017,8 @@ export interface operations { }; }; 401: components["responses"]["Unauthorized"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; }; }; createWebhookEndpoint: { @@ -1578,9 +3040,14 @@ export interface operations { * } */ "application/json": { - /** Format: uri */ + /** + * Format: uri + * @description Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. + */ url: string; + /** @description Optional internal label for identifying the endpoint in your account. */ description?: string; + /** @description Task events to deliver. Omit to subscribe to both `task.succeeded` and `task.failed`. */ events?: ("task.succeeded" | "task.failed")[]; }; }; @@ -1615,6 +3082,8 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; }; }; getWebhookEndpoint: { @@ -1666,6 +3135,8 @@ export interface operations { "application/json": components["schemas"]["Error"]; }; }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; }; }; deleteWebhookEndpoint: { @@ -1698,6 +3169,17 @@ export interface operations { }; }; 401: components["responses"]["Unauthorized"]; + /** @description Webhook endpoint not found. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; }; }; updateWebhookEndpoint: { @@ -1718,7 +3200,10 @@ export interface operations { * } */ "application/json": { - /** Format: uri */ + /** + * Format: uri + * @description Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. + */ url?: string; description?: string; /** @enum {string} */ @@ -1739,6 +3224,84 @@ export interface operations { }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; + /** @description Webhook endpoint not found. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + }; + }; + receiveBeatApiTaskEvent: { + parameters: { + query?: never; + header: { + "x-beatapi-event": "task.succeeded" | "task.failed"; + "x-beatapi-timestamp": string; + "x-beatapi-signature": string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "id": "evt_123", + * "event": "task.succeeded", + * "created_at": 1782210300, + * "data": { + * "id": "task_8K2qA", + * "object": "task", + * "task_kind": "video", + * "capability_id": "seedance-2.5", + * "capability_version": null, + * "media_type": "video", + * "model": "seedance-2.5", + * "status": "succeeded", + * "stage": "succeeded", + * "created_at": 1782210000, + * "updated_at": 1782210300, + * "completed_at": 1782210300, + * "output": { + * "media": [ + * { + * "type": "video", + * "url": "https://media.beatapi.io/outputs/task_8K2qA/0.mp4", + * "mime_type": "video/mp4" + * } + * ], + * "r2_url": "https://media.beatapi.io/outputs/task_8K2qA/0.mp4" + * }, + * "usage": { + * "credits_reserved": 1.55, + * "credits_charged": 1.55, + * "billable_duration_seconds": 5, + * "credits_settled": 1.55, + * "credits_refunded": 0 + * }, + * "request_id": "req_abc123", + * "error_code": null, + * "error_message": null + * } + * } + */ + "application/json": components["schemas"]["WebhookEvent"]; + }; + }; + responses: { + /** @description Event accepted */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; } diff --git a/package-lock.json b/package-lock.json index 9f398af..309725d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1153,9 +1153,9 @@ } }, "node_modules/hono": { - "version": "4.12.30", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz", - "integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==", + "version": "4.13.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.3.tgz", + "integrity": "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==", "dev": true, "license": "MIT", "engines": { @@ -1208,9 +1208,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", + "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index a2cc04d..f1fb1f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "beatapi-codex-plugin", "version": "0.2.0", "private": true, - "description": "Codex plugin for BeatAPI async and realtime AI video APIs.", + "description": "Codex plugin for BeatAPI image, video, Effect, workflow, and Realtime APIs.", "type": "module", "scripts": { "build": "node scripts/build-mcp.mjs", diff --git a/scripts/build-mcp.mjs b/scripts/build-mcp.mjs index 50ae371..cec00bc 100644 --- a/scripts/build-mcp.mjs +++ b/scripts/build-mcp.mjs @@ -1,4 +1,5 @@ import { build } from "esbuild"; +import { createHash } from "node:crypto"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -20,9 +21,33 @@ const result = await build({ const output = result.outputFiles?.[0]?.contents; if (!output) throw new Error("esbuild did not produce the MCP server bundle."); +function bundleMismatchDetails(current, generated) { + generated = Buffer.from(generated); + const limit = Math.min(current.length, generated.length); + let offset = 0; + while (offset < limit && current[offset] === generated[offset]) offset += 1; + const line = current.subarray(0, offset).toString("utf8").split("\n").length; + const start = Math.max(0, offset - 120); + const end = offset + 240; + const digest = (value) => createHash("sha256").update(value).digest("hex"); + return [ + `first difference: byte ${offset}, line ${line}`, + `committed: ${current.length} bytes, sha256 ${digest(current)}`, + `generated: ${generated.length} bytes, sha256 ${digest(generated)}`, + `committed snippet: ${JSON.stringify(current.subarray(start, end).toString("utf8"))}`, + `generated snippet: ${JSON.stringify(generated.subarray(start, end).toString("utf8"))}`, + ].join("\n"); +} + if (check) { - if (!existsSync(outfile) || !readFileSync(outfile).equals(output)) { - throw new Error("mcp/server.mjs is stale. Run npm run build."); + if (!existsSync(outfile)) { + throw new Error("mcp/server.mjs is missing. Run npm run build."); + } + const current = readFileSync(outfile); + if (!current.equals(output)) { + throw new Error( + `mcp/server.mjs is stale. Run npm run build.\n${bundleMismatchDetails(current, output)}`, + ); } console.log("MCP server bundle is current."); } else { diff --git a/skills/beatapi-video/SKILL.md b/skills/beatapi-video/SKILL.md index 232de6d..9922b2d 100644 --- a/skills/beatapi-video/SKILL.md +++ b/skills/beatapi-video/SKILL.md @@ -1,6 +1,6 @@ --- name: beatapi-video -description: Create, monitor, and troubleshoot BeatAPI Music Video, Ecommerce Video, and Realtime Video sessions through bundled BeatAPI MCP tools when available or the official BeatAPI CLI as a fallback. Use when a user wants to generate an AI video, create or inspect a realtime browser session, upload workflow media, check credits and concurrency, manage storyboard shots, retrieve hosted output, configure webhooks, or diagnose a BeatAPI API error. +description: Create, monitor, and troubleshoot BeatAPI image, video, Effect, Music Video, Ecommerce Video, and Realtime tasks through bundled BeatAPI MCP tools when available or the official BeatAPI CLI as a fallback. Use when a user wants to generate media with a BeatAPI model, run a published Effect, create or inspect a realtime browser session, upload media, check balance and concurrency, manage storyboard shots, retrieve hosted output, configure webhooks, or diagnose a BeatAPI API error. --- # BeatAPI Video @@ -24,7 +24,8 @@ The Skills-only distribution requires Node.js 20.19+ or 22.12+ and `BEATAPI_API_KEY`. - Never request a key in chat, pass it as a command argument, print it, or place it in JSON, source files, logs, screenshots, or issue text. -- Treat task creation, shot editing, and composition as paid mutations. +- Treat image, video, Effect, workflow, Realtime, shot-editing, and composition + creation as paid mutations. - Consider an explicit request to generate or edit authorization for that operation. Ask before spending credits only when the request is ambiguous, material settings are missing, or the operation expands beyond the request. @@ -41,13 +42,19 @@ The Skills-only distribution requires Node.js 20.19+ or 22.12+ and terminal or set `BEATAPI_API_KEY`. Do not ask them to paste the key into the conversation. 5. Before a paid operation, call `beatapi_get_usage` or run `beatapi usage`. - Check both credit balance and active concurrency. + Check both USD balance and active concurrency. -Skip credential checks for anonymous `beatapi_list_workflows` or -`beatapi workflows list`. +Skip credential checks for anonymous workflow, model, and Effect discovery. ## Choose the workflow +- Choose Image generation for one hosted still image. Choose Video generation + for one hosted model-specific video. Read + [generation-and-effects.md](references/generation-and-effects.md) before + selecting a model or request shape. +- Choose an Effect only after listing and reading its current published input + contract. Effects can return an image or video. + - Choose Music Video when the user supplies audio plus 1-7 visual references. - Choose automatic Music Video composition unless the user wants to inspect, select, reorder, or edit storyboard shots. @@ -59,8 +66,8 @@ Skip credential checks for anonymous `beatapi_list_workflows` or session. Read [realtime-video.md](references/realtime-video.md) first. The agent may manage the server-side session but does not own camera permission, WebRTC negotiation, or browser rendering. -- Do not force unrelated video editing, transcription, generic image - generation, or non-BeatAPI API design tasks into this Skill. +- Do not force unrelated editing, transcription, or non-BeatAPI API design + tasks into this Skill. Read [credits-and-limits.md](references/credits-and-limits.md) when estimating cost or validating media and generation settings. @@ -84,6 +91,25 @@ cost or validating media and generation settings. Reject unsupported media, private-network URLs, localhost URLs, data URLs, and unknown fields instead of guessing. +## Execute image, video, or Effect generation + +1. Read [generation-and-effects.md](references/generation-and-effects.md). +2. Discover the current model or Effect before selecting it. +3. Copy the matching image, video, or Effect template to a temporary file. +4. Validate the exact model-specific or Effect-version-specific fields against + the bundled OpenAPI contract. +5. With MCP, call `beatapi_create_image`, `beatapi_create_video`, or + `beatapi_create_effect`. With the CLI fallback: + + ```bash + beatapi images create --file /tmp/beatapi-image.json + beatapi videos create --file /tmp/beatapi-video.json + beatapi effects create --file /tmp/beatapi-effect.json \ + --idempotency-key effect_request_123 + ``` + +6. Preserve the task ID and wait through the shared task endpoint. + ## Execute automatic Music Video 1. Copy `assets/music-video.auto.json` to a temporary working file. @@ -120,7 +146,11 @@ unknown fields instead of guessing. - Inspect one task with `beatapi_get_task` or `beatapi tasks get TASK_ID`. - Discover workflows with `beatapi_list_workflows` or `beatapi workflows list`. -- Inspect balance and concurrency with `beatapi_get_usage` or `beatapi usage`. +- Discover model aliases with `beatapi_list_generation_models` or + `beatapi models list`; discover Effects with `beatapi_list_effects` or + `beatapi effects list`. +- Inspect USD balance and concurrency with `beatapi_get_usage` or + `beatapi usage`. - Manage webhook endpoints with the `beatapi_*_webhook` tools or `beatapi webhooks list|create|get|update|delete`. - Read [api-workflows.md](references/api-workflows.md) for the exact MCP, CLI, @@ -156,7 +186,8 @@ Return: - workflow and task ID; - final or actionable status; - hosted output URL(s) only when present; -- credits charged, settled, or refunded when useful; +- USD amount charged, settled, or refunded when useful; compatibility response + fields still use `credits_*` names; - `request_id`, `error_code`, and `error_message` for failures; - the next required action for `storyboard_ready` or `requires_action`. diff --git a/skills/beatapi-video/agents/openai.yaml b/skills/beatapi-video/agents/openai.yaml index ba49804..e1d1350 100644 --- a/skills/beatapi-video/agents/openai.yaml +++ b/skills/beatapi-video/agents/openai.yaml @@ -1,7 +1,7 @@ interface: display_name: "BeatAPI Video" - short_description: "Manage BeatAPI async and realtime video APIs" - default_prompt: "Use $beatapi-video to create an AI video and return the verified hosted result." + short_description: "Manage BeatAPI image, video, Effect, workflow, and realtime APIs" + default_prompt: "Use $beatapi-video to choose the current BeatAPI capability, create the requested media, and return only a verified hosted result." policy: allow_implicit_invocation: true diff --git a/skills/beatapi-video/assets/effect-task.json b/skills/beatapi-video/assets/effect-task.json new file mode 100644 index 0000000..d471857 --- /dev/null +++ b/skills/beatapi-video/assets/effect-task.json @@ -0,0 +1,6 @@ +{ + "effect_id": "replace-with-effect-id", + "images": [ + "https://replace-with-public-input.example/image.png" + ] +} diff --git a/skills/beatapi-video/assets/image-generation.json b/skills/beatapi-video/assets/image-generation.json new file mode 100644 index 0000000..0aeff56 --- /dev/null +++ b/skills/beatapi-video/assets/image-generation.json @@ -0,0 +1,6 @@ +{ + "model": "nano-banana", + "prompt": "Replace with the requested image generation instructions.", + "aspect_ratio": "1:1", + "output_format": "png" +} diff --git a/skills/beatapi-video/assets/video-generation.json b/skills/beatapi-video/assets/video-generation.json new file mode 100644 index 0000000..5c54974 --- /dev/null +++ b/skills/beatapi-video/assets/video-generation.json @@ -0,0 +1,7 @@ +{ + "model": "seedance-2-mini", + "prompt": "Replace with the requested video generation instructions.", + "duration": 5, + "aspect_ratio": "16:9", + "resolution": "720p" +} diff --git a/skills/beatapi-video/evals/evals.json b/skills/beatapi-video/evals/evals.json index 0958668..4b79afe 100644 --- a/skills/beatapi-video/evals/evals.json +++ b/skills/beatapi-video/evals/evals.json @@ -122,6 +122,45 @@ "Does not claim browser media success from ready status" ], "files": [] + }, + { + "id": 10, + "prompt": "Use Nano Banana Pro and these two reference images to generate a 16:9 campaign still. Go ahead and create it.", + "expected_output": "Should discover the current generation catalog, select the stable nano-banana-pro alias, validate the two HTTPS or uploaded image references against that model's request schema, check USD balance and concurrency, create one image task, poll it, and report only hosted image output.", + "assertions": [ + "Discovers generation models", + "Uses the stable nano-banana-pro alias", + "Validates model-specific image inputs", + "Treats generation as a paid mutation", + "Requires hosted image output before success" + ], + "files": ["reference-one.png", "reference-two.png"] + }, + { + "id": 11, + "prompt": "Make a 10-second Seedance 2.5 video using these image and audio references.", + "expected_output": "Should select seedance-2.5 only after catalog discovery, inspect and upload local references, avoid mixing frame images with reference fields, ensure audio has an accompanying reference image or video, validate the 10-second 720p contract, create one video task, and wait for hosted output.", + "assertions": [ + "Uses the stable seedance-2.5 alias", + "Validates reference input combinations", + "Keeps duration within the model contract", + "Does not expose internal provider routing", + "Waits for hosted video output" + ], + "files": ["character.png", "motion.mp4", "voice.mp3"] + }, + { + "id": 12, + "prompt": "Show me the current portrait Effects, then run the one I choose on this image.", + "expected_output": "Should list active Effects without authentication, present publication-safe choices, wait for the user's Effect selection, read that Effect's current version contract, upload and validate the image, use a stable idempotency key for the paid create call, and poll the shared task endpoint.", + "assertions": [ + "Lists active Effects before creation", + "Waits for the user to choose an Effect", + "Reads the selected Effect contract", + "Uses a stable idempotency key", + "Does not infer internal templates or providers" + ], + "files": ["portrait.png"] } ] } diff --git a/skills/beatapi-video/references/api-workflows.md b/skills/beatapi-video/references/api-workflows.md index 213fe66..3401501 100644 --- a/skills/beatapi-video/references/api-workflows.md +++ b/skills/beatapi-video/references/api-workflows.md @@ -2,7 +2,8 @@ Base URL: `https://api.beatapi.io` -Authentication: Bearer API key for every endpoint except `GET /v1/workflows`. +Authentication: Bearer API key for mutations and account data. Workflow, +generation-model, and Effect discovery endpoints are anonymous. Prefer bundled MCP tools when available. Otherwise use the CLI so credential handling and output separation stay consistent. @@ -10,6 +11,12 @@ handling and output separation stay consistent. | --- | --- | --- | --- | | Check setup | `beatapi_check_setup` | `beatapi auth status` | `GET /v1/usage` | | Discover workflows | `beatapi_list_workflows` | `beatapi workflows list` | `GET /v1/workflows` | +| Discover generation models | `beatapi_list_generation_models` | `beatapi models list` | `GET /v1/media/models` | +| Create image task | `beatapi_create_image` | `beatapi images create --file INPUT` | `POST /v1/images/tasks` | +| Create video task | `beatapi_create_video` | `beatapi videos create --file INPUT` | `POST /v1/videos/tasks` | +| List Effects | `beatapi_list_effects` | `beatapi effects list` | `GET /v1/effects` | +| Read Effect | `beatapi_get_effect` | `beatapi effects get EFFECT` | `GET /v1/effects/{effect_id}` | +| Create Effect task | `beatapi_create_effect` | `beatapi effects create --file INPUT` | `POST /v1/effects/tasks` | | Check usage | `beatapi_get_usage` | `beatapi usage` | `GET /v1/usage` | | Upload local media | `beatapi_upload_file` | `beatapi files upload PATH` | `POST /v1/files` | | Create Music Video | `beatapi_create_music_video` | `beatapi music-video create --file INPUT` | `POST /v1/music-video/tasks` | diff --git a/skills/beatapi-video/references/beatapi.openapi.yaml b/skills/beatapi-video/references/beatapi.openapi.yaml index 65e1dc9..36e850d 100644 --- a/skills/beatapi-video/references/beatapi.openapi.yaml +++ b/skills/beatapi-video/references/beatapi.openapi.yaml @@ -6,11 +6,15 @@ info: name: BeatAPI Terms of Service url: https://beatapi.io/terms-of-service description: | - BeatAPI provides async video workflows and short-lived Realtime Video - Sessions behind one BeatAPI-native API. Async integrations create a task, - poll until it finishes, then read the hosted video URL from `output.media`. - Realtime browser integrations create a Session with the same Bearer API key, - then pass only the returned BeatAPI `client_secret` to `@beatapi/realtime`. + BeatAPI provides a unified API for image generation, video generation, Effects, asynchronous video workflows, + and short-lived Realtime Video Sessions. + + For asynchronous operations, create a task, poll the shared task endpoint or receive webhook events, + and read hosted output URLs from `output.media`. For Realtime, create a Session on a trusted server + with your Bearer API key and pass only the returned short-lived `client_secret` to the browser. + + Customer balances and usage are USD-denominated. Compatibility fields such + as `credit_balance` and `credits_reserved` remain in the API; 1 Credit = $1 USD. ## 5 minute Quick Start @@ -18,7 +22,7 @@ info: 2. Create an API key in [Dashboard → API Keys](https://beatapi.io/dashboard/apikeys) and send it as `Authorization: Bearer `. - Credit packs are available from + USD balance top-ups are available from [Dashboard → Billing](https://beatapi.io/dashboard/billing). 3. Use public HTTPS URLs for input media. If your files are local, upload them with `POST /v1/files` first. @@ -27,7 +31,8 @@ info: 6. Add webhooks later if you do not want to poll. ```bash - export BEATAPI_API_KEY="sk_your_key" + read -rsp "BeatAPI API key: " BEATAPI_API_KEY && echo + export BEATAPI_API_KEY curl https://api.beatapi.io/v1/workflows @@ -71,13 +76,13 @@ info: public internet. Localhost, private network URLs, and data URLs are rejected. Use `POST /v1/files` for local images, audio, or subtitles. - Each verified new user account starts with 50 welcome credits valid for 14 days and - 1 active processing concurrency. Lifetime paid credit purchases unlock higher limits: + Each verified new user account starts with a $2 welcome balance that never expires and + 1 active processing concurrency. Lifetime paid purchases unlock higher limits: $10+ = 2, $100+ = 5, $1,000+ = 10, $5,000+ = 15, and $20,000+ = 30. `GET /v1/usage` returns current usage totals, concurrency limit, and active processing task count. Active concurrency measures tasks that are currently using BeatAPI processing resources. storyboard_ready and requires_action - tasks can have settled credits but do not count toward active processing + tasks can have settled USD usage but do not count toward active processing concurrency. ## Webhooks are optional @@ -95,6 +100,10 @@ tags: description: Discover the workflow IDs available for task creation. - name: Music Video description: Create music video tasks from images, audio, and optional creative controls. + - name: Effects + description: Discover versioned effects and create image or video effect tasks. + - name: Generation + description: Discover BeatAPI generation models and create image or video tasks. - name: Ecommerce Video description: Create product ad video tasks from product images and duration. - name: Tasks @@ -107,6 +116,70 @@ tags: description: Upload local assets and use the returned HTTPS URL as workflow input. - name: Webhooks description: Manage optional completion callbacks. +webhooks: + taskCompleted: + post: + operationId: receiveBeatApiTaskEvent + x-fern-ignore: true + tags: [Webhooks] + summary: Receive a BeatAPI task completion event + description: | + BeatAPI sends this request to each active endpoint subscribed to the event. + Verify `x-beatapi-signature` against the exact request body and use polling + as the source of truth if delivery is delayed or fails. + security: [] + parameters: + - in: header + name: x-beatapi-event + required: true + schema: { type: string, enum: [task.succeeded, task.failed] } + - in: header + name: x-beatapi-timestamp + required: true + schema: { type: string } + - in: header + name: x-beatapi-signature + required: true + schema: { type: string } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/WebhookEvent' } + example: + id: evt_123 + event: task.succeeded + created_at: 1782210300 + data: + id: task_8K2qA + object: task + task_kind: video + capability_id: seedance-2.5 + capability_version: null + media_type: video + model: seedance-2.5 + status: succeeded + stage: succeeded + created_at: 1782210000 + updated_at: 1782210300 + completed_at: 1782210300 + output: + media: + - type: video + url: https://media.beatapi.io/outputs/task_8K2qA/0.mp4 + mime_type: video/mp4 + r2_url: https://media.beatapi.io/outputs/task_8K2qA/0.mp4 + usage: + credits_reserved: 1.55 + credits_charged: 1.55 + billable_duration_seconds: 5 + credits_settled: 1.55 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + responses: + '200': { description: Event accepted } components: securitySchemes: BearerAuth: @@ -144,17 +217,22 @@ components: example: shot_xxx index: type: integer + description: Zero-based shot order in the storyboard. example: 0 status: $ref: '#/components/schemas/TaskStatus' + description: Current lifecycle state for this storyboard shot. duration_seconds: type: integer + description: Planned or generated shot duration in seconds. example: 5 prompt: type: string + description: Creative instruction used to generate this shot. example: Opening lyric shot. lyric_text: type: string + description: Lyric segment aligned with this shot when available. example: Intro media: type: object @@ -162,19 +240,24 @@ components: properties: type: type: string + description: Hosted media type for the materialized shot. example: video url: type: string format: uri + description: BeatAPI-hosted HTTPS URL for the materialized shot. example: https://media.beatapi.io/outputs/task_8K2qA/shots/0.mp4 mime_type: type: string + description: MIME type of the hosted shot media. example: video/mp4 created_at: type: integer + description: Unix timestamp when the shot record was created. example: 1782210000 updated_at: type: integer + description: Unix timestamp when the shot record last changed. example: 1782210300 Storyboard: type: object @@ -182,6 +265,7 @@ components: properties: shots: type: array + description: Ordered Music Video storyboard shots. The array may be empty before storyboard generation completes. items: $ref: '#/components/schemas/StoryboardShot' ShotMedia: @@ -230,46 +314,88 @@ components: required: [credits_reserved, credits_settled, credits_refunded, credits_charged] properties: credits_reserved: - type: integer - description: BeatAPI customer credits reserved for this task. + type: number + format: double + multipleOf: 0.01 + description: USD amount reserved for this task. The compatibility field name is retained; 1 Credit equals $1 USD. credits_charged: - type: integer - description: BeatAPI customer credits charged when the task or operation is accepted. + type: number + format: double + multipleOf: 0.01 + description: USD amount charged when the task or operation is accepted. billable_duration_seconds: type: integer - description: Server-detected or request-declared billable duration used for credit calculation. + description: Server-detected or request-declared billable duration used for USD calculation. credits_settled: - type: integer - description: BeatAPI customer credits settled after successful work. + type: number + format: double + multipleOf: 0.01 + description: USD amount settled after successful work. credits_refunded: - type: integer - description: BeatAPI customer credits refunded after failed eligible work. + type: number + format: double + multipleOf: 0.01 + description: USD amount refunded after failed eligible work. Task: type: object - required: [id, object, workflow, status, stage, created_at, updated_at, completed_at, output, usage, request_id, error_code, error_message] + required: [id, object, task_kind, capability_id, capability_version, status, stage, created_at, updated_at, completed_at, output, usage, request_id, error_code, error_message] properties: id: type: string + description: Stable BeatAPI task ID used for polling and support. example: task_8K2qA object: type: string enum: [task] + description: Object discriminator; always `task`. + task_kind: + type: string + enum: [workflow, effect, image, video] + description: Public task family that determines which capability fields are present. + capability_id: + type: string + description: Stable BeatAPI workflow, Effect, or generation model ID selected when the task was accepted. + capability_version: + type: [integer, 'null'] + description: Immutable capability version used by this task. Legacy workflow rows are returned as version 1. workflow: type: string enum: [music-video, ecommerce-video] + description: Present for workflow tasks; identifies the selected BeatAPI workflow. example: music-video + effect_id: + type: string + description: Present for Effect tasks; stable selected Effect ID. + example: video-muscle-max + effect_version: + type: integer + description: Present for Effect tasks; immutable Effect version used for processing. + example: 1 + media_type: + type: string + enum: [image, video] + description: Present when task_kind is image or video. + model: + type: string + description: Stable BeatAPI model alias. It is independent from internal execution routing. status: $ref: '#/components/schemas/TaskStatus' + description: Current task lifecycle status. Stop polling at `succeeded` or `failed`; Music Video can also require manual action. stage: $ref: '#/components/schemas/TaskStatus' + description: Current processing stage, exposed separately so workflow progress can be tracked. storyboard: $ref: '#/components/schemas/Storyboard' + description: Music Video storyboard metadata when available. created_at: type: integer + description: Unix timestamp when BeatAPI accepted the task. updated_at: type: integer + description: Unix timestamp of the latest task update. completed_at: type: [integer, 'null'] + description: Terminal Unix timestamp, or null while work is in progress. output: description: Output is null until the task succeeds. oneOf: @@ -279,54 +405,129 @@ components: properties: media: type: array + description: BeatAPI-hosted result assets. items: type: object required: [type, url, mime_type] properties: type: type: string - enum: [video] + enum: [image, video] + description: Result asset type. url: type: string format: uri + description: BeatAPI-hosted HTTPS result URL. mime_type: type: string - example: video/mp4 + description: Result asset MIME type. + examples: [video/mp4, image/png, image/jpeg, image/webp] r2_url: type: string format: uri + description: Primary BeatAPI-hosted result URL for clients that need one canonical asset. usage: $ref: '#/components/schemas/TaskUsage' + description: USD reservation, settlement, refund, and optional billable duration for this task. request_id: type: string + description: Correlation ID to retain for logs and BeatAPI support. example: req_abc123 error_code: type: [string, 'null'] + description: Machine-readable terminal failure code, or null when no task failure is recorded. example: processing_timeout error_message: type: [string, 'null'] + description: Human-readable terminal failure detail, or null when no task failure is recorded. + Effect: + type: object + required: [id, object, name, description, output_type, category, tags, input, options, preview, version, status] + properties: + id: { type: string, example: video-muscle-max } + object: { type: string, enum: [effect] } + name: { type: string, example: Muscle Transformation } + description: { type: string } + output_type: { type: string, enum: [image, video] } + category: { type: string, example: transformation } + tags: { type: array, items: { type: string } } + input: + type: object + required: [images_min, images_max, accepted_types] + properties: + images_min: { type: integer, minimum: 1 } + images_max: { type: integer, minimum: 1 } + accepted_types: + type: array + items: { type: string, enum: [image/jpeg, image/png, image/webp] } + max_size_mb: + type: integer + minimum: 1 + description: Maximum downloaded bytes per input image. When omitted, BeatAPI enforces 50 MB. + max_dimension_px: + type: integer + minimum: 1 + description: Maximum decoded width or height. BeatAPI inspects the actual image header before charging. + subject_requirements: { type: array, items: { type: string } } + options: + type: object + properties: + aspect_ratios: { type: array, items: { type: string } } + resolutions: { type: array, items: { type: string } } + duration_seconds: { type: array, items: { type: integer } } + bgm: { type: boolean } + seed: { type: boolean } + preview: + type: object + required: [cover_url, media_url] + properties: + cover_url: { type: [string, 'null'], format: uri } + media_url: { type: [string, 'null'], format: uri } + version: { type: integer, minimum: 1 } + status: { type: string, enum: [testing, active, paused] } + EffectResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/Effect' } + EffectListResponse: + type: object + required: [data] + properties: + data: + type: object + required: [object, data] + properties: + object: { type: string, enum: [list] } + data: { type: array, items: { $ref: '#/components/schemas/Effect' } } File: type: object required: [id, object, url, key, mime_type, size_bytes, purpose, created_at] properties: id: type: string + description: Stable uploaded file ID. example: file_3xYz9 object: type: string enum: [file] + description: Object discriminator; always `file`. url: type: string format: uri + description: Long-lived BeatAPI HTTPS URL to use in workflow or model requests. example: https://media.beatapi.io/inputs/file_3xYz9.mp3 key: type: string + description: BeatAPI storage key for support and diagnostics. example: inputs/file_3xYz9.mp3 mime_type: type: string + description: Accepted MIME type detected for the uploaded file. example: audio/mpeg size_bytes: type: integer + description: Uploaded file size in bytes. example: 1048576 audio_duration_seconds: type: number @@ -339,8 +540,10 @@ components: purpose: type: string enum: [input] + description: File purpose; currently always `input`. created_at: type: integer + description: Unix timestamp when the file was stored. example: 1782210000 WebhookEndpoint: type: object @@ -348,33 +551,41 @@ components: properties: id: type: string + description: Stable webhook endpoint ID used for get, update, and delete operations. example: wh_9aBcD object: type: string enum: [webhook_endpoint] + description: Object discriminator; always `webhook_endpoint`. url: type: string format: uri + description: Public HTTPS callback URL receiving subscribed task events. example: https://example.com/beatapi-webhook description: type: string + description: Account-defined label for the endpoint. example: Production webhook events: type: array + description: Task event types delivered to this endpoint. items: type: string enum: [task.succeeded, task.failed] status: type: string enum: [active, disabled] + description: Delivery status. Disabled endpoints do not receive events. secret: type: string description: Returned in full only when the endpoint is created. Later responses return a masked value. example: whsec_example_masked created_at: type: integer + description: Unix timestamp when the endpoint was created. updated_at: type: integer + description: Unix timestamp when the endpoint last changed. WebhookEvent: type: object required: [id, event, created_at, data] @@ -406,28 +617,340 @@ components: properties: data: $ref: '#/components/schemas/WorkflowList' + GenerationModel: + type: object + additionalProperties: false + required: [id, object, name, media_type, input_modes] + properties: + id: + type: string + enum: [nano-banana, nano-banana-pro, gpt-image-2, seedream-5-pro, minimax-h3, seedance-2, seedance-2-fast, seedance-2-mini, veo-3.1, seedance-2.5, kling-3] + object: { type: string, enum: [generation_model] } + name: { type: string } + media_type: { type: string, enum: [image, video] } + input_modes: + type: array + items: { type: string, enum: [text, image, frames, reference] } + GenerationModelList: + type: object + required: [object, data] + properties: + object: { type: string, enum: [list] } + data: + type: array + items: { $ref: '#/components/schemas/GenerationModel' } + GenerationModelListResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/GenerationModelList' } + ImageGenerationTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/NanoBananaImageRequest' + - $ref: '#/components/schemas/NanoBananaProImageRequest' + - $ref: '#/components/schemas/GptImage2Request' + - $ref: '#/components/schemas/Seedream5ProImageRequest' + discriminator: + propertyName: model + mapping: + nano-banana: '#/components/schemas/NanoBananaImageRequest' + nano-banana-pro: '#/components/schemas/NanoBananaProImageRequest' + gpt-image-2: '#/components/schemas/GptImage2Request' + seedream-5-pro: '#/components/schemas/Seedream5ProImageRequest' + NanoBananaImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: nano-banana, description: Must be `nano-banana`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation instructions. } + aspect_ratio: + type: string + enum: ['1:1', '9:16', '16:9', '3:4', '4:3', '3:2', '2:3', '5:4', '4:5', '21:9', auto] + default: '1:1' + description: Output image aspect ratio. + output_format: { type: string, enum: [png, jpeg], default: png, description: Output image file format. } + NanoBananaProImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: nano-banana-pro, description: Must be `nano-banana-pro`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 8 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: ['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9', auto] + default: '1:1' + description: Output image aspect ratio. + resolution: { type: string, enum: [1K, 2K, 4K], default: 1K, description: Output resolution tier. } + output_format: { type: string, enum: [png, jpg], default: png, description: Output image file format. } + GptImage2Request: + type: object + additionalProperties: false + required: [model, prompt] + description: | + `auto` only supports 1K. `1:1` does not support 4K. At 2K/4K, + `5:4`, `4:5`, `3:1`, `1:3`, and `9:21` are unavailable. + properties: + model: { type: string, const: gpt-image-2, description: Must be `gpt-image-2`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 16 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: [auto, '1:1', '3:2', '2:3', '4:3', '3:4', '5:4', '4:5', '16:9', '9:16', '2:1', '1:2', '3:1', '1:3', '21:9', '9:21'] + default: auto + description: Output image aspect ratio. Availability also depends on resolution. + resolution: { type: string, enum: [1K, 2K, 4K], default: 1K, description: Output resolution tier. } + Seedream5ProImageRequest: + type: object + additionalProperties: false + required: [model, prompt] + properties: + model: { type: string, const: seedream-5-pro, description: Must be `seedream-5-pro`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Generation or image-editing instructions. } + images: + type: array + minItems: 1 + maxItems: 10 + description: Public HTTPS reference-image URLs. Omit for text-to-image. + items: { type: string, format: uri, pattern: '^https://' } + aspect_ratio: + type: string + enum: [auto, '1:1', '4:3', '3:4', '16:9', '9:16', '3:2', '2:3', '21:9'] + default: '1:1' + description: Output image aspect ratio. + resolution: { type: string, enum: [1K, 2K], default: 1K, description: Output resolution tier. } + output_format: { type: string, enum: [png, jpeg], default: png, description: Output image file format. } + VideoGenerationTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/MinimaxH3VideoRequest' + - $ref: '#/components/schemas/Seedance2VideoRequest' + - $ref: '#/components/schemas/Seedance2FastVideoRequest' + - $ref: '#/components/schemas/Seedance2MiniVideoRequest' + - $ref: '#/components/schemas/Veo31VideoRequest' + - $ref: '#/components/schemas/Seedance25VideoRequest' + - $ref: '#/components/schemas/Kling3VideoRequest' + discriminator: + propertyName: model + mapping: + minimax-h3: '#/components/schemas/MinimaxH3VideoRequest' + seedance-2: '#/components/schemas/Seedance2VideoRequest' + seedance-2-fast: '#/components/schemas/Seedance2FastVideoRequest' + seedance-2-mini: '#/components/schemas/Seedance2MiniVideoRequest' + veo-3.1: '#/components/schemas/Veo31VideoRequest' + seedance-2.5: '#/components/schemas/Seedance25VideoRequest' + kling-3: '#/components/schemas/Kling3VideoRequest' + MinimaxH3VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input.' + properties: + model: { type: string, const: minimax-h3, description: Must be `minimax-h3`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [768P, 2K], default: 768P, description: Output resolution tier. } + Seedance2VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2, description: Must be `seedance-2`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p, 1080p, 4k], default: 720p, description: Output resolution tier. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + Seedance2FastVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2-fast, description: Must be `seedance-2-fast`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p], default: 720p, description: Output resolution tier. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + Seedance2MiniVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: 'Low-cost Seedance 2.0 route. `images` cannot be combined with any `reference_*` input. Generated audio is not supported.' + properties: + model: { type: string, const: seedance-2-mini, description: Must be `seedance-2-mini`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 9, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, enum: [480p, 720p], default: 720p, description: Output resolution tier. } + Veo31VideoRequest: + allOf: + - oneOf: + - $ref: '#/components/schemas/Veo31TextOrFrameVideoRequest' + - $ref: '#/components/schemas/Veo31ReferenceVideoRequest' + Veo31TextOrFrameVideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: | + Veo 3.1 text or first/last-frame generation. Output is fixed at 8 seconds + and defaults to the Quality tier. + properties: + model: { type: string, const: veo-3.1, description: Must be `veo-3.1`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + aspect_ratio: { type: string, enum: ['16:9', '9:16', auto], default: '16:9', description: Output video aspect ratio. } + quality: { type: string, enum: [Quality, Fast, Lite], default: Quality, description: Text or frame generation tier. } + watermark: { type: string, description: Optional watermark text forwarded to the selected model. } + enable_translation: { type: boolean, description: Allow prompt translation before generation. } + Veo31ReferenceVideoRequest: + type: object + additionalProperties: false + required: [model, prompt, reference_images] + description: | + Veo 3.1 reference-image generation. Output is fixed at 8 seconds and + supports the Fast or Lite tier, defaulting to Fast. + properties: + model: { type: string, const: veo-3.1, description: Must be `veo-3.1`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + reference_images: { type: array, minItems: 1, maxItems: 3, description: Public HTTPS reference images., items: { type: string, format: uri, pattern: '^https://' } } + aspect_ratio: { type: string, enum: ['16:9', '9:16', auto], default: '16:9', description: Output video aspect ratio. } + quality: { type: string, enum: [Fast, Lite], default: Fast, description: Reference-image generation tier. } + watermark: { type: string, description: Optional watermark text forwarded to the selected model. } + enable_translation: { type: boolean, description: Allow prompt translation before generation. } + Seedance25VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: '`images` cannot be combined with any `reference_*` input. An audio reference also requires at least one reference image or video.' + properties: + model: { type: string, const: seedance-2.5, description: Must be `seedance-2.5`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs., items: { type: string, format: uri, pattern: '^https://' } } + reference_images: { type: array, minItems: 1, maxItems: 30, description: Public HTTPS image references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_videos: { type: array, minItems: 1, maxItems: 10, description: Public HTTPS video references for multimodal reference generation., items: { type: string, format: uri, pattern: '^https://' } } + reference_audios: { type: array, minItems: 1, maxItems: 10, description: Public HTTPS audio references. Audio also requires at least one reference image or video., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 4, maximum: 30, default: 5, description: Requested output duration in seconds. } + aspect_ratio: { type: string, enum: [adaptive, '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: adaptive, description: Output video aspect ratio. } + resolution: { type: string, const: 720p, default: 720p, description: Seedance 2.5 currently returns 720p output. } + generate_audio: { type: boolean, default: true, description: Generate synchronized audio with the video. } + seed: { type: integer, minimum: -1, maximum: 4294967295, default: -1, description: Reproducibility seed. Use -1 for a random seed. } + KlingShot: + type: object + additionalProperties: false + required: [prompt, duration] + properties: + prompt: { type: string, minLength: 1, maxLength: 500, description: Instructions for this shot. } + duration: { type: integer, minimum: 1, maximum: 12, description: Shot duration in seconds. All shot durations must sum to the task duration. } + KlingElement: + type: object + additionalProperties: false + required: [name, element_input_urls] + description: Use 2-4 image URLs or one video URL. A video element may include one audio URL and a 3-8 second segment in milliseconds. + properties: + name: { type: string, minLength: 1, description: Stable name used to reference this element in the prompt. } + description: { type: string, description: Optional description of the subject or object. } + element_input_urls: + type: array + minItems: 1 + maxItems: 4 + description: Two to four image URLs, or one video URL. + items: { type: string, format: uri, pattern: '^https://' } + element_input_audio_urls: + type: array + minItems: 1 + maxItems: 1 + description: Optional audio URL used with a video element. + items: { type: string, format: uri, pattern: '^https://' } + start_time: { type: integer, minimum: 0, maximum: 30000, description: Video element segment start time in milliseconds. } + end_time: { type: integer, minimum: 0, maximum: 30000, description: Video element segment end time in milliseconds. The segment must be 3-8 seconds. } + Kling3VideoRequest: + type: object + additionalProperties: false + required: [model, prompt] + description: Multi-shot mode accepts one first-frame image, requires `multi_prompt`, and defaults sound to true. Shot durations must sum to `duration`. + properties: + model: { type: string, const: kling-3, description: Must be `kling-3`. } + prompt: { type: string, minLength: 1, maxLength: 5000, description: Video generation instructions. } + images: { type: array, minItems: 1, maxItems: 2, description: One first-frame image or first- and last-frame images as public HTTPS URLs. Multi-shot mode accepts exactly one., items: { type: string, format: uri, pattern: '^https://' } } + duration: { type: integer, minimum: 3, maximum: 15, default: 5, description: Requested output duration in seconds. } + aspect_ratio: + type: string + enum: ['16:9', '9:16', '1:1'] + description: Defaults to 16:9 for text generation. Omit it with frame images to adapt to the input aspect ratio. + resolution: { type: string, enum: [std, pro, 4K], default: pro, description: Output quality tier. } + sound: { type: boolean, description: Generate synchronized sound. Defaults to true in multi-shot mode. } + multi_shots: { type: boolean, default: false, description: Enable storyboard-style multi-shot generation. } + multi_prompt: + type: array + minItems: 1 + maxItems: 5 + description: Shot definitions required when `multi_shots=true`. + items: { $ref: '#/components/schemas/KlingShot' } + elements: + type: array + maxItems: 3 + description: Up to three reusable subject or object references. + items: { $ref: '#/components/schemas/KlingElement' } TaskResponse: type: object required: [data] properties: data: $ref: '#/components/schemas/Task' + description: Accepted or current BeatAPI task state. Usage: type: object - required: [object, credit_balance, total_tasks, credits_settled, credits_refunded, concurrency, by_workflow] + required: [object, credit_balance, total_tasks, credits_settled, credits_refunded, concurrency, by_workflow, by_capability, by_model, by_api_key] properties: object: type: string enum: [usage] credit_balance: - type: integer - description: Current credit balance. It may be negative. + type: number + format: double + multipleOf: 0.01 + description: Current USD balance. The compatibility field name is retained; 1 Credit equals $1 USD. The balance may be negative. total_tasks: type: integer credits_settled: - type: integer + type: number + format: double + multipleOf: 0.01 credits_refunded: - type: integer + type: number + format: double + multipleOf: 0.01 concurrency: type: object required: [limit, active] @@ -437,9 +960,10 @@ components: example: 2 active: type: integer - description: Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled credits without counting toward this value. + description: Active processing tasks currently using BeatAPI processing resources. Music Video storyboard_ready and requires_action tasks can have settled USD usage without counting toward this value. by_workflow: type: array + description: Compatibility view containing workflow tasks only. Image, video, and Effect tasks are reported under by_capability instead. items: type: object required: [workflow, tasks, credits_settled] @@ -450,7 +974,54 @@ components: tasks: type: integer credits_settled: + type: number + format: double + multipleOf: 0.01 + by_capability: + type: array + items: + type: object + required: [task_kind, capability_id, tasks, credits_settled] + properties: + task_kind: + type: string + enum: [workflow, effect, image, video] + capability_id: + type: string + tasks: + type: integer + credits_settled: + type: number + format: double + multipleOf: 0.01 + by_model: + type: array + items: + type: object + required: [media_type, model, tasks, credits_settled] + properties: + media_type: + type: string + enum: [image, video] + model: + type: string + tasks: type: integer + credits_settled: + type: number + format: double + multipleOf: 0.01 + by_api_key: + type: array + items: + type: object + required: [api_key_id, title, key_prefix, tasks, credits_settled] + properties: + api_key_id: { type: string } + title: { type: string } + key_prefix: { type: string } + tasks: { type: integer } + credits_settled: { type: number, format: double, multipleOf: 0.01 } realtime: type: object required: [sessions, credits, active] @@ -459,8 +1030,10 @@ components: type: integer description: Total BeatAPI realtime sessions for this account. credits: - type: integer - description: Credits settled by connected realtime sessions. + type: number + format: double + multipleOf: 0.01 + description: USD amount settled by connected realtime sessions. active: type: integer description: Realtime sessions in ready, connecting, or active state. @@ -470,6 +1043,213 @@ components: properties: data: $ref: '#/components/schemas/Usage' + MusicVideoTaskCreateRequest: + oneOf: + - $ref: '#/components/schemas/StandardMusicVideoTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoTaskCreateRequest' + discriminator: + propertyName: mv_tier + mapping: + standard: '#/components/schemas/StandardMusicVideoTaskCreateRequest' + premium: '#/components/schemas/PremiumMusicVideoTaskCreateRequest' + StandardMusicVideoTaskCreateRequest: + type: object + required: [images, audio_url] + allOf: + - if: + required: [lip_sync] + properties: + lip_sync: { const: true } + then: + required: [lip_ref_url] + properties: + lip_ref_url: {} + not: + anyOf: + - required: [mv_mode] + properties: { mv_mode: {} } + - required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_tier: + type: string + enum: [standard] + default: standard + description: May be omitted to preserve the backwards-compatible Standard contract. + images: + type: array + minItems: 1 + maxItems: 7 + description: Standard scene images. Provide 1-7 public HTTPS PNG, JPEG, or WebP URLs; place the primary subject or opening scene first. Upload local files through `POST /v1/files` and use the returned `data.url`. + items: { type: string, format: uri } + audio_url: + type: string + format: uri + description: Public HTTPS audio URL; Standard audio must be 10-180 seconds. + prompt: { type: string, maxLength: 3000, description: "Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters." } + language: { type: string, enum: [en, zh], description: Dialogue and lyric language used by the Standard workflow. } + quality: { type: string, enum: [standard, high], default: standard, description: Generation quality tier. High quality is unavailable at 540p. } + style: { type: string, maxLength: 200, description: "Optional concise visual style, such as cinematic, anime, documentary, or fashion editorial." } + aspect_ratio: { type: string, enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], description: Target output placement. Set explicitly for the destination player or social feed. } + resolution: { type: string, enum: [540p, 720p, 1080p], default: 720p, description: Output resolution. 540p cannot be combined with high quality or lip sync. } + lip_sync: + type: boolean + default: false + description: Generate lip-synchronized performance. When true, `lip_ref_url` is required. + lip_ref_url: + type: string + format: uri + description: Public HTTPS close-up, front-facing face image used for Standard lip sync. + add_subtitle: { type: boolean, default: false, description: Burn generated or supplied subtitles into the final video. } + subtitle_color: { type: string, pattern: '^#[0-9A-Fa-f]{6}$', example: '#FFFFFF', description: Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. } + srt_url: { type: string, format: uri, description: Optional public HTTPS `.srt` subtitle file. Upload a local subtitle through `POST /v1/files`. } + duration: + type: integer + minimum: 10 + maximum: 180 + description: Billing fallback only; detected audio duration wins. + compose_mode: + type: string + enum: [auto, manual] + default: auto + description: Auto composes the final Music Video; manual pauses at `requires_action` so shots can be reviewed or edited before compose. + PremiumMusicVideoTaskCreateRequest: + allOf: + - oneOf: + - $ref: '#/components/schemas/PremiumMusicVideoSingTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoSingPerformTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoDanceTaskCreateRequest' + - $ref: '#/components/schemas/PremiumMusicVideoPerformTaskCreateRequest' + discriminator: + propertyName: mv_mode + mapping: + sing: '#/components/schemas/PremiumMusicVideoSingTaskCreateRequest' + sing_perform: '#/components/schemas/PremiumMusicVideoSingPerformTaskCreateRequest' + dance: '#/components/schemas/PremiumMusicVideoDanceTaskCreateRequest' + perform: '#/components/schemas/PremiumMusicVideoPerformTaskCreateRequest' + PremiumMusicVideoTaskRequestBase: + type: object + required: [mv_tier, mv_mode, audio_url] + not: + anyOf: + - required: [quality] + properties: { quality: {} } + - required: [language] + properties: { language: {} } + - required: [lip_sync] + properties: { lip_sync: {} } + - required: [lip_ref_url] + properties: { lip_ref_url: {} } + - required: [srt_url] + properties: { srt_url: {} } + - required: [compose_mode] + properties: { compose_mode: {} } + properties: + mv_tier: { type: string, enum: [premium], description: Selects the Premium Music Video workflow and its mode-specific inputs. } + mv_mode: { type: string, enum: [sing, sing_perform, dance, perform], description: Premium performance mode. Sing modes require `lip_ref_urls`; dance and perform require exactly six `images`. } + audio_url: + type: string + format: uri + description: Public HTTPS audio URL; Premium audio must be 10-300 seconds. + prompt: { type: string, maxLength: 3000, description: "Optional creative direction for story, setting, performance, camera, lighting, and pacing. Maximum 3000 characters." } + style: { type: string, maxLength: 200 } + aspect_ratio: { type: string, enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], description: Target output placement. Set explicitly for the destination player or social feed. } + resolution: + type: string + enum: [720p] + default: 720p + description: Premium output is fixed to 720p. + add_subtitle: { type: boolean, default: false, description: Burn generated subtitles into the final video. } + subtitle_color: { type: string, pattern: '^#[0-9A-Fa-f]{6}$', example: '#FFFFFF', description: Subtitle text color as a six-digit hexadecimal value. Used when subtitles are enabled. } + duration: + type: integer + minimum: 10 + maximum: 300 + description: Premium billing fallback only; detected audio duration wins. + PremiumMusicVideoSingTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [lip_ref_urls] + properties: + mv_mode: { type: string, enum: [sing] } + images: + type: array + minItems: 0 + maxItems: 6 + description: Optional Premium scene images for sing mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. + items: { type: string, format: uri } + lip_ref_urls: + type: array + minItems: 1 + maxItems: 2 + description: Required for sing mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. + items: { type: string, format: uri } + PremiumMusicVideoSingPerformTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [lip_ref_urls] + properties: + mv_mode: { type: string, enum: [sing_perform] } + images: + type: array + minItems: 0 + maxItems: 6 + description: Optional Premium scene images for sing and perform mode. Provide up to six public HTTPS PNG, JPEG, or WebP URLs. + items: { type: string, format: uri } + lip_ref_urls: + type: array + minItems: 1 + maxItems: 2 + description: Required for sing and perform mode. One or two public HTTPS close-up, front-facing face images for lip synchronization. + items: { type: string, format: uri } + PremiumMusicVideoDanceTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [images] + not: + required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_mode: { type: string, enum: [dance] } + images: + type: array + minItems: 6 + maxItems: 6 + description: Required for dance mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. + items: { type: string, format: uri } + PremiumMusicVideoPerformTaskCreateRequest: + allOf: + - $ref: '#/components/schemas/PremiumMusicVideoTaskRequestBase' + - type: object + required: [images] + not: + required: [lip_ref_urls] + properties: { lip_ref_urls: {} } + properties: + mv_mode: { type: string, enum: [perform] } + images: + type: array + minItems: 6 + maxItems: 6 + description: Required for perform mode. Provide exactly six public HTTPS PNG, JPEG, or WebP scene images. + items: { type: string, format: uri } + EditMusicVideoShotRequest: + type: object + additionalProperties: false + required: [prompt] + properties: + prompt: + type: string + maxLength: 3000 + images: + type: array + minItems: 0 + maxItems: 6 + description: Premium tasks only. Optional replacement scene images; an empty array is treated as omitted. Standard tasks reject this field. + items: { type: string, format: uri } RealtimeSession: type: object required: [id, object, status, expires_at, max_duration_seconds, allowed_origins, credits, request_id, created_at, connected_at, closed_at] @@ -477,41 +1257,66 @@ components: id: type: string pattern: '^rts_' + description: Stable Realtime Session ID used to inspect or close the session. object: type: string enum: [realtime.session] + description: Object discriminator; always `realtime.session`. status: type: string enum: [ready, connecting, active, closed, failed, expired] description: Active means BeatAPI accepted the first billing heartbeat after remote output began. - client_secret: - type: string - description: Returned only by POST. Give this short-lived BeatAPI secret to the browser SDK; never give the browser an sk_ API key. - pattern: '^brt_live_' expires_at: type: string format: date-time + description: Time when the unconnected short-lived session credential expires. max_duration_seconds: type: integer enum: [15, 60, 300] + description: Maximum selected live duration and billing tier in seconds. allowed_origins: type: array - items: { type: string, format: uri } + description: Exact browser origins authorized to use this Session. + items: + type: string + format: uri + pattern: '^(https://[A-Za-z0-9.-]+(?::[0-9]+)?|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]+)?)$' + description: Exact browser origin. Use HTTPS in production; HTTP is accepted only for localhost development. + example: https://app.example.com credits: type: object required: [reserved, settled, refunded] + description: USD reservation, settlement, and refund lifecycle for this Realtime Session. Compatibility field names are retained. properties: - reserved: { type: integer } - settled: { type: integer } - refunded: { type: integer } + reserved: { type: number, format: double, multipleOf: 0.01, description: USD amount reserved when the Session is created. } + settled: { type: number, format: double, multipleOf: 0.01, description: USD amount settled after the first accepted billing heartbeat. } + refunded: { type: number, format: double, multipleOf: 0.01, description: USD amount refunded if the Session ends without billing activation. } request_id: type: string - created_at: { type: string, format: date-time } + description: Correlation ID to retain for logs and BeatAPI support. + created_at: { type: string, format: date-time, description: Time when the Session was created. } connected_at: type: [string, 'null'] format: date-time description: Time of the first accepted BeatAPI billing heartbeat; null before billing activation. - closed_at: { type: [string, 'null'], format: date-time } + closed_at: { type: [string, 'null'], format: date-time, description: "Time when the Session closed, or null while it remains open." } + RealtimeSessionCreated: + allOf: + - $ref: '#/components/schemas/RealtimeSession' + - type: object + required: [client_secret] + properties: + client_secret: + type: string + description: Short-lived BeatAPI browser credential returned only by POST. Never expose an sk_ API key to the browser. + pattern: '^brt_live_' + RealtimeSessionCreateResponse: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/RealtimeSessionCreated' + description: Created Realtime Session including the one-time short-lived browser credential. RealtimeSessionResponse: type: object required: [data] @@ -524,6 +1329,7 @@ components: properties: data: $ref: '#/components/schemas/File' + description: Uploaded file metadata and the public HTTPS URL to use in later requests. WebhookEndpointList: type: object required: [object, data] @@ -547,6 +1353,7 @@ components: properties: data: $ref: '#/components/schemas/WebhookEndpoint' + description: Created or retrieved webhook endpoint. Public API responses return the full signing secret at creation and mask it afterward; authenticated dashboard owners can explicitly reveal it again. DeleteResponse: type: object required: [data] @@ -565,10 +1372,12 @@ components: properties: error: type: object + description: Structured BeatAPI error. Use `code` for program logic and retain `request_id` for support. required: [code, message, request_id] properties: code: type: string + description: Stable machine-readable error code. enum: - bad_request - unauthorized @@ -578,6 +1387,7 @@ components: - idempotency_conflict - user_concurrency_exceeded - rate_limit_exceeded + - content_policy_violation - processing_unavailable - processing_failed - processing_timeout @@ -592,8 +1402,10 @@ components: - internal_error message: type: string + description: Human-readable detail intended for logs and debugging. request_id: type: string + description: Correlation ID to retain for BeatAPI support. retry_after_seconds: type: integer description: Present on retryable rate-limit or capacity responses when the client should wait before retrying. @@ -637,6 +1449,28 @@ components: message: Too many polling requests. Poll every 5-10 seconds. request_id: req_xxx retry_after_seconds: 12 + InternalError: + description: BeatAPI could not complete the request because of an internal or storage failure. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: internal_error + message: Internal error. Contact support with the request_id if the problem continues. + request_id: req_xxx + ProcessingUnavailable: + description: BeatAPI processing is temporarily unavailable or did not complete within the processing window. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: processing_unavailable + message: Task processing is temporarily unavailable. + request_id: req_xxx paths: /v1/workflows: get: @@ -667,32 +1501,439 @@ paths: '429': $ref: '#/components/responses/RateLimited' + /v1/media/models: + get: + operationId: listGenerationModels + tags: [Generation] + summary: List BeatAPI image and video generation models + description: Returns stable BeatAPI model aliases and public input modes. Internal execution routing is not part of this contract. + security: [] + parameters: + - in: query + name: media_type + schema: { type: string, enum: [image, video] } + responses: + '200': + description: Generation model list + content: + application/json: + schema: { $ref: '#/components/schemas/GenerationModelListResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/images/tasks: + post: + operationId: createImageGenerationTask + tags: [Generation] + summary: Create an image generation task + description: | + Creates one asynchronous image task. Select the model-specific request + contract with `model`, save the returned `data.id`, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. + security: [{ BearerAuth: [] }] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/ImageGenerationTaskCreateRequest' } + examples: + Nano Banana: + summary: Nano Banana + value: + model: nano-banana + prompt: Editorial product photograph on a warm stone pedestal. + aspect_ratio: '1:1' + output_format: png + Nano Banana Pro: + summary: Nano Banana Pro + value: + model: nano-banana-pro + prompt: Place the supplied product in a premium editorial studio scene. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '4:5' + resolution: 2K + output_format: png + GPT Image 2: + summary: GPT Image 2 + value: + model: gpt-image-2 + prompt: Create a clean campaign image from the supplied product reference. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '1:1' + resolution: 2K + Seedream 5 Pro: + summary: Seedream 5 Pro + value: + model: seedream-5-pro + prompt: Recompose the product as a cinematic storefront campaign image. + images: ['https://media.beatapi.io/samples/smart-bottle.png'] + aspect_ratio: '16:9' + resolution: 2K + output_format: png + responses: + '201': + description: Image generation task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + examples: + Nano Banana Pro: + summary: Nano Banana Pro + value: + data: + id: task_8K2qA + object: task + task_kind: image + capability_id: nano-banana-pro + capability_version: null + media_type: image + model: nano-banana-pro + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 0.09 + credits_charged: 0.09 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': { description: Insufficient USD balance, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '409': { description: Idempotency key conflicts with another request body, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/videos/tasks: + post: + operationId: createVideoGenerationTask + tags: [Generation] + summary: Create a video generation task + description: | + Creates one asynchronous video task. Select the model-specific request + contract with `model`, save the returned `data.id`, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. + security: [{ BearerAuth: [] }] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/VideoGenerationTaskCreateRequest' } + examples: + MiniMax H3: + summary: MiniMax H3 + value: + model: minimax-h3 + prompt: A slow cinematic push through a misty mountain village at dawn. + duration: 5 + aspect_ratio: '16:9' + resolution: 768P + Seedance 2: + summary: Seedance 2 + value: + model: seedance-2 + prompt: A handheld tracking shot through a crowded neon night market. + duration: 8 + aspect_ratio: '16:9' + resolution: 1080p + generate_audio: true + Seedance 2 Fast: + summary: Seedance 2 Fast + value: + model: seedance-2-fast + prompt: A fast cinematic production draft through a neon night market. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + generate_audio: true + Seedance 2 Mini: + summary: Seedance 2 Mini + value: + model: seedance-2-mini + prompt: A low-cost storyboard draft for a product reveal. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + Veo 3.1 Quality: + summary: Veo 3.1 Quality + value: + model: veo-3.1 + prompt: A cinematic aerial reveal of a quiet coastal village at sunrise. + aspect_ratio: '16:9' + quality: Quality + Veo 3.1 Fast: + summary: Veo 3.1 Fast + value: + model: veo-3.1 + prompt: A fast cinematic product reveal with natural camera motion. + aspect_ratio: '16:9' + quality: Fast + Veo 3.1 Lite: + summary: Veo 3.1 Lite + value: + model: veo-3.1 + prompt: A concise storyboard-ready product reveal. + aspect_ratio: '16:9' + quality: Lite + Veo 3.1 Reference Fast: + summary: Veo 3.1 Reference Fast + value: + model: veo-3.1 + prompt: Create a cohesive cinematic scene using the supplied visual references. + reference_images: + - https://media.beatapi.io/samples/neon-singer.png + - https://media.beatapi.io/samples/smart-bottle.png + aspect_ratio: '16:9' + quality: Fast + Seedance 2.5: + summary: Seedance 2.5 + value: + model: seedance-2.5 + prompt: A cinematic tracking shot through a rain-lit night market. + duration: 5 + aspect_ratio: '16:9' + resolution: 720p + generate_audio: true + seed: -1 + Kling 3: + summary: Kling 3 + value: + model: kling-3 + prompt: A dramatic product reveal with a slow orbiting camera move. + duration: 5 + aspect_ratio: '16:9' + resolution: pro + sound: true + responses: + '201': + description: Video generation task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + examples: + MiniMax H3: + summary: MiniMax H3 + value: + data: + id: task_8K2qA + object: task + task_kind: video + capability_id: minimax-h3 + capability_version: null + media_type: video + model: minimax-h3 + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 0.2 + credits_charged: 0.2 + billable_duration_seconds: 5 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': { description: Insufficient USD balance, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '409': { description: Idempotency key conflicts with another request body, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/effects: + get: + operationId: listEffects + tags: [Effects] + summary: List active Effects + security: [] + description: Returns only versioned Effects that have passed BeatAPI publication gates. Internal integration names, template ids, costs, and execution context are never exposed. + parameters: + - in: query + name: output_type + schema: { type: string, enum: [image, video] } + - in: query + name: category + schema: { type: string } + responses: + '200': + description: Active Effect catalog + content: + application/json: + schema: { $ref: '#/components/schemas/EffectListResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '429': { $ref: '#/components/responses/RateLimited' } + + /v1/effects/{effect_id}: + get: + operationId: getEffect + tags: [Effects] + summary: Get an active Effect + security: [] + parameters: + - in: path + name: effect_id + required: true + schema: { type: string } + responses: + '200': + description: Effect definition and immutable current version contract + content: + application/json: + schema: { $ref: '#/components/schemas/EffectResponse' } + '404': + description: Effect is unknown or not currently published. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + + /v1/effects/tasks: + post: + operationId: createEffectTask + tags: [Effects] + summary: Create an Effect task + security: [{ BearerAuth: [] }] + description: | + Creates an asynchronous image or video Effect task. Read the catalog + first: image count, accepted input types, output resolution/duration, + and execution contract are fixed by the selected Effect version. Send + an `Idempotency-Key`; an exact replay returns the + accepted task before remote input URLs are revalidated, while a changed + body returns `idempotency_conflict`. + + The USD amount is reserved atomically when accepted, settled on success, and + fully refunded after a definite processing failure. An uncertain create + result is not blindly retried and never switches integrations automatically. + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string, maxLength: 255 } + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: false + required: [effect_id, images] + properties: + effect_id: { type: string, example: video-muscle-max, description: Stable published Effect ID from `GET /v1/effects`. } + effect_version: + type: integer + minimum: 1 + description: Optional immutable version. Omit to use the current published version. + images: + type: array + minItems: 1 + maxItems: 7 + description: Public HTTPS input images in the order required by the selected Effect version. Read `GET /v1/effects/{effect_id}` for the exact count and accepted media rules; upload local files with `POST /v1/files`. + items: { type: string, format: uri } + options: + type: object + additionalProperties: false + description: Optional controls supported by the selected Effect version. Omit unsupported controls; the catalog is the source of truth. + properties: + aspect_ratio: { type: string, description: Requested output aspect ratio when the selected Effect exposes this option. } + resolution: { type: string, description: Requested output resolution when the selected Effect exposes this option. } + duration: { type: integer, description: Requested video duration in seconds when the selected Effect exposes this option. } + bgm: { type: boolean, description: Include background music when supported by the selected Effect. } + seed: { type: integer, description: Optional deterministic seed when supported by the selected Effect. } + example: + effect_id: video-muscle-max + images: ['https://media.beatapi.io/samples/portrait.png'] + options: { resolution: 720p, duration: 12 } + responses: + '201': + description: Effect task accepted + content: + application/json: + schema: { $ref: '#/components/schemas/TaskResponse' } + example: + data: + id: task_effect123 + object: task + task_kind: effect + capability_id: video-muscle-max + capability_version: 1 + effect_id: video-muscle-max + effect_version: 1 + status: queued + stage: queued + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: { credits_reserved: 1.2, credits_charged: 1.2, credits_settled: 0, credits_refunded: 0 } + request_id: req_effect123 + error_code: null + error_message: null + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': + description: Insufficient USD balance. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '404': + description: Effect or requested version is unavailable. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '409': + description: Idempotency key conflicts with another request body. + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '429': { $ref: '#/components/responses/RateLimited' } + /v1/music-video/tasks: post: operationId: createMusicVideoTask tags: [Music Video] x-apidog-folder: Music Video API/Create Video - summary: Create Music Video + summary: Create a Music Video workflow task security: - BearerAuth: [] + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + example: mv-create-cus_123-01 description: | - Music Video requires public HTTPS image URLs and a public HTTPS audio URL. - Prompt, language, quality, style, lip reference, subtitle, and format - controls are optional. BeatAPI detects the audio duration before task - creation and charges the detected billable duration at the selected - per-second customer-credit rate. If audio duration cannot be detected, - `duration` is used as the billing fallback. + Music Video requires public HTTPS media URLs. Requests that omit + `mv_tier` use `standard` and preserve the existing API behavior. + Premium retains the configured integration host but uses a distinct + execution path selected internally by BeatAPI. + Shared controls include prompt, aspect ratio, subtitles, and the tier's + billing fallback. Language, quality, `lip_sync`, `lip_ref_url`, + `srt_url`, and `compose_mode` are Standard-only. Premium uses `mv_mode` + plus `style` and mode-specific images or `lip_ref_urls`. BeatAPI detects audio duration before task + creation and records the billable duration in Task usage. If audio + duration cannot be detected, `duration` is used as the billing fallback. Input limits: - - Images must contain 1-7 public HTTPS URLs. + - Standard images must contain 1-7 public HTTPS URLs. + - Premium `sing` and `sing_perform` accept 0-6 scene images and require 1-2 `lip_ref_urls`. + - Premium `dance` and `perform` require exactly 6 scene images. - Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller. - Image aspect ratio should be between 1:4 and 4:1. - - Audio must be a public HTTPS mp3, wav, aac, or m4a URL between 10 and 180 seconds. + - Standard audio must be 10-180 seconds; Premium audio must be 10-300 seconds and contain vocals or lyrics rather than instrumental-only audio. - The audio file should be 50 MB or smaller. - `prompt` is optional and must be at most 3000 characters. - - `lip_ref_url`, when provided, must be a public HTTPS image URL. Use a clear, front-facing close-up face reference for best lip-sync results. - - `srt_url`, when provided, must point to an `.srt` subtitle file. - - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; it must be 10-180 seconds and cannot override a detected audio duration. + - Standard `lip_sync=true` requires `lip_ref_url`. It must be a public HTTPS image URL showing a clear, front-facing close-up face. + - Standard `srt_url`, when provided, must point to an `.srt` subtitle file. + - `duration` is only a billing fallback when BeatAPI cannot detect the audio length; Standard accepts 10-180 seconds and Premium accepts 10-300 seconds. It cannot override a detected audio duration. BeatAPI validates URL shape, text limits, enum values, combination limits, and audio duration at task creation. Files uploaded through @@ -700,15 +1941,6 @@ paths: Third-party media URLs must follow the same media requirements and may be rejected during processing if invalid. - Customer pricing: - - MV 540p standard: 4 credits/s - - MV 720p standard: 5 credits/s - - MV 1080p standard: 6 credits/s - - lip_sync add-on: +2 credits/s - - MV 720p high: 16 credits/s - - MV 1080p high: 18 credits/s - - Ecommerce Video 1080p: 15 credits/s - Combination limits: - `quality=high` is not supported with `resolution=540p`. - `lip_sync=true` is not supported with `resolution=540p`. @@ -724,76 +1956,23 @@ paths: content: application/json: schema: - type: object - required: [images, audio_url] - properties: - images: - type: array - minItems: 1 - maxItems: 7 - description: 1-7 public HTTPS image URLs. Use png, jpg, jpeg, or webp images; each image should be 50 MB or smaller, with aspect ratio from 1:4 to 4:1. /v1/files uploads are checked before use; third-party URLs may be rejected during processing if invalid. - items: - type: string - format: uri - audio_url: - type: string - format: uri - description: Public HTTPS audio URL. Use mp3, wav, aac, or m4a; file size should be 50 MB or smaller and duration must be 10-180 seconds. - prompt: - type: string - maxLength: 3000 - description: Optional creative prompt, at most 3000 characters. - language: - type: string - enum: [en, zh] - lip_sync: - type: boolean - lip_ref_url: - type: string - format: uri - description: Public HTTPS image URL for lip-sync face reference. Use a clear, front-facing close-up face reference. - style: - type: string - maxLength: 200 - description: Optional style phrase, at most 200 characters. - quality: - type: string - enum: [standard, high] - default: standard - aspect_ratio: - type: string - enum: ['1:1', '16:9', '9:16', '4:3', '3:4'] - resolution: - type: string - enum: [540p, 720p, 1080p] - default: 720p - add_subtitle: - type: boolean - subtitle_color: - type: string - pattern: '^#[0-9A-Fa-f]{6}$' - example: '#FFFFFF' - srt_url: - type: string - format: uri - duration: - type: integer - minimum: 10 - maximum: 180 - description: Billing fallback when audio duration cannot be detected. It must be 10-180 seconds and cannot override a detected audio duration. - compose_mode: - type: string - enum: [auto, manual] - default: auto - example: - images: - - https://media.beatapi.io/samples/neon-singer.png - audio_url: https://media.beatapi.io/samples/neon-singer-preview.mp3 - prompt: Neon rooftop performance with metro cutaways and cinematic light trails. - language: en - quality: standard - resolution: 720p - compose_mode: auto + $ref: '#/components/schemas/MusicVideoTaskCreateRequest' + examples: + standard_backwards_compatible: + summary: Standard music video + value: + mv_tier: standard + images: ['https://media.beatapi.io/samples/neon-singer.png'] + audio_url: https://media.beatapi.io/samples/neon-singer-preview.mp3 + resolution: 720p + premium_sing: + value: { mv_tier: premium, mv_mode: sing, images: [], lip_ref_urls: ['https://media.beatapi.io/samples/singer.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_sing_perform: + value: { mv_tier: premium, mv_mode: sing_perform, images: ['https://media.beatapi.io/samples/stage.png'], lip_ref_urls: ['https://media.beatapi.io/samples/singer.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_dance: + value: { mv_tier: premium, mv_mode: dance, images: ['https://media.beatapi.io/1.png', 'https://media.beatapi.io/2.png', 'https://media.beatapi.io/3.png', 'https://media.beatapi.io/4.png', 'https://media.beatapi.io/5.png', 'https://media.beatapi.io/6.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } + premium_perform: + value: { mv_tier: premium, mv_mode: perform, images: ['https://media.beatapi.io/1.png', 'https://media.beatapi.io/2.png', 'https://media.beatapi.io/3.png', 'https://media.beatapi.io/4.png', 'https://media.beatapi.io/5.png', 'https://media.beatapi.io/6.png'], audio_url: 'https://media.beatapi.io/samples/song.mp3', resolution: 720p } responses: '201': description: Task accepted @@ -801,28 +1980,34 @@ paths: application/json: schema: $ref: '#/components/schemas/TaskResponse' - example: - data: - id: task_8K2qA - object: task - workflow: music-video - status: queued - stage: queued - storyboard: - shots: [] - created_at: 1782210000 - updated_at: 1782210000 - completed_at: null - output: null - usage: - credits_reserved: 75 - credits_charged: 75 - billable_duration_seconds: 15 - credits_settled: 0 - credits_refunded: 0 - request_id: req_abc123 - error_code: null - error_message: null + examples: + standard_backwards_compatible: + summary: Standard music video + value: + data: + id: task_8K2qA + object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 + workflow: music-video + status: queued + stage: queued + storyboard: + shots: [] + created_at: 1782210000 + updated_at: 1782210000 + completed_at: null + output: null + usage: + credits_reserved: 1.5 + credits_charged: 1.5 + billable_duration_seconds: 15 + credits_settled: 0 + credits_refunded: 0 + request_id: req_abc123 + error_code: null + error_message: null '400': $ref: '#/components/responses/BadRequest' '401': @@ -838,6 +2023,17 @@ paths: code: insufficient_credits message: Account balance is not sufficient for this task. request_id: req_xxx + '409': + description: The Idempotency-Key was reused with a different body or while another request with that key is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: idempotency_conflict + message: This Idempotency-Key was already used with a different request body. + request_id: req_xxx '429': description: User concurrency exceeded. content: @@ -855,13 +2051,16 @@ paths: operationId: editMusicVideoShot tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Edit Shot + summary: Edit a Music Video storyboard shot security: - BearerAuth: [] description: | Edit one storyboard shot using its BeatAPI `shot_id`. This operation - charges BeatAPI customer credits using the selected quality/resolution - rate and the shot duration. Default shot duration is 5 seconds. + charges the customer USD balance using the applicable task tier and shot + duration. Standard edits accept only `prompt`. Premium edits accept + `prompt` plus up to 6 optional replacement `images`. Generation quality, + resolution, and shot duration are inherited from the original task and + are not editable request fields. When the edit finishes, BeatAPI stores the edited shot media and exposes it on that shot. The existing final Music Video is not replaced until you call compose with the selected shot ids. @@ -878,35 +2077,21 @@ paths: schema: type: string example: shot_xxx + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key for this task, shot, and request body returns the accepted task without charging the USD amount again; changing any of them returns `409 idempotency_conflict`. + example: music-edit-task_8K2qA-shot_xxx-01 requestBody: required: true content: application/json: schema: - type: object - required: [prompt] - properties: - prompt: - type: string - maxLength: 3000 - duration: - type: integer - minimum: 1 - maximum: 180 - default: 5 - quality: - type: string - enum: [standard, high] - default: standard - resolution: - type: string - enum: [540p, 720p, 1080p] - default: 720p + $ref: '#/components/schemas/EditMusicVideoShotRequest' example: prompt: Night city chorus with brighter face lighting. - duration: 5 - quality: standard - resolution: 720p + images: ['https://media.beatapi.io/samples/stage.png'] responses: '202': description: Shot edit accepted @@ -918,19 +2103,37 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '402': + description: Account balance is not sufficient for this shot edit. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: The Idempotency-Key was reused for a different task, shot, or request body, or the same request is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '404': description: Task or shot not found. content: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/music-video/tasks/{task_id}/shots/{shot_id}/media: post: operationId: getMusicVideoShotMedia tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Get Shot Media + summary: Retrieve a Music Video storyboard shot media URL security: - BearerAuth: [] description: | @@ -989,18 +2192,24 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/music-video/tasks/{task_id}/compose: post: operationId: composeMusicVideoTask tags: [Music Video] x-apidog-folder: Music Video API/Advanced Editing - summary: Compose Video + summary: Compose a Music Video task from selected shots security: - BearerAuth: [] description: | Compose selected BeatAPI storyboard shots into the final Music Video. - This operation charges a fixed 1 BeatAPI customer credit. + This operation charges a fixed $1 USD. parameters: - in: path name: task_id @@ -1008,6 +2217,12 @@ paths: schema: type: string example: task_8K2qA + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key for this task and request body returns the accepted task without charging the $1 compose amount again; changing either returns `409 idempotency_conflict`. + example: music-compose-task_8K2qA-01 requestBody: required: true content: @@ -1034,22 +2249,51 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '402': + description: Account balance is not sufficient for this compose operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: The Idempotency-Key was reused for a different task or request body, or the same request is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '404': description: Task or shot not found. content: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' + '502': + $ref: '#/components/responses/ProcessingUnavailable' /v1/ecommerce-video/tasks: post: operationId: createEcommerceVideoTask tags: [Ecommerce Video] x-apidog-folder: Ecommerce Video API - summary: Create Ecommerce Video + summary: Create an Ecommerce Video workflow task security: - BearerAuth: [] - description: Ecommerce Video requires product images and an explicit output duration. + parameters: + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional retry key. Reusing the same key with the same request body returns the accepted task; reusing it with a different body returns `409 idempotency_conflict`. + example: ecommerce-create-cus_123-01 + description: | + Ecommerce Video creates a complete product video from public HTTPS product or + lifestyle images, an explicit duration, and optional creative direction. Upload + local images with `POST /v1/files`, save the returned Task ID, and poll + `GET /v1/tasks/{task_id}` until the task succeeds or fails. requestBody: required: true content: @@ -1062,6 +2306,7 @@ paths: type: array minItems: 1 maxItems: 7 + description: Primary product or scene image first, followed by up to six additional public HTTPS PNG, JPEG, or WebP product or lifestyle images. Upload local files with `POST /v1/files` and use the returned `data.url`. items: type: string format: uri @@ -1069,21 +2314,25 @@ paths: type: integer minimum: 10 maximum: 60 + description: Required target output duration in seconds and the basis for USD calculation. Allowed range is 10-60 seconds. prompt: type: string maxLength: 2000 + description: Optional creative direction, audience, product benefit, offer, tone, scenes, or call to action. Maximum 2000 characters. aspect_ratio: type: string enum: ['16:9', '9:16', '1:1'] + description: Target output placement. Use 16:9 for landscape, 9:16 for vertical social, or 1:1 for square placements; set explicitly for stable layout. language: type: string enum: [en, zh] + description: Dialogue and narration language. Use `en` for English or `zh` for Chinese; set explicitly when the prompt contains mixed languages. example: images: - https://media.beatapi.io/samples/smart-bottle.png duration: 15 prompt: Fast product launch ad for paid social. - aspect_ratio: 9:16 + aspect_ratio: '9:16' responses: '201': description: Task accepted @@ -1095,6 +2344,9 @@ paths: data: id: task_p9Lm2 object: task + task_kind: workflow + capability_id: ecommerce-video + capability_version: 1 workflow: ecommerce-video status: queued stage: queued @@ -1103,8 +2355,8 @@ paths: completed_at: null output: null usage: - credits_reserved: 225 - credits_charged: 225 + credits_reserved: 4.5 + credits_charged: 4.5 billable_duration_seconds: 15 credits_settled: 0 credits_refunded: 0 @@ -1126,6 +2378,17 @@ paths: code: insufficient_credits message: Account balance is not sufficient for this task. request_id: req_xxx + '409': + description: The Idempotency-Key was reused with a different body or while another request with that key is still being processed. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: + code: idempotency_conflict + message: This Idempotency-Key was already used with a different request body. + request_id: req_xxx '429': description: User concurrency exceeded. content: @@ -1168,6 +2431,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: queued stage: queued @@ -1178,8 +2444,8 @@ paths: completed_at: null output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 credits_settled: 0 credits_refunded: 0 @@ -1192,6 +2458,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: storyboard_ready stage: storyboard_ready @@ -1210,10 +2479,10 @@ paths: updated_at: 1782210300 output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 - credits_settled: 75 + credits_settled: 1.5 credits_refunded: 0 request_id: req_abc123 error_code: null @@ -1224,6 +2493,9 @@ paths: data: id: task_8K2qA object: task + task_kind: workflow + capability_id: music-video + capability_version: 1 workflow: music-video status: failed stage: failed @@ -1234,11 +2506,11 @@ paths: completed_at: 1782210600 output: null usage: - credits_reserved: 75 - credits_charged: 75 + credits_reserved: 1.5 + credits_charged: 1.5 billable_duration_seconds: 15 credits_settled: 0 - credits_refunded: 75 + credits_refunded: 1.5 request_id: req_abc123 error_code: processing_timeout error_message: Task waited too long for platform capacity. @@ -1262,9 +2534,9 @@ paths: security: - BearerAuth: [] description: | - Reserve credits and allocate a short-lived BeatAPI realtime session. Send a unique + Reserve the selected USD amount and allocate a short-lived BeatAPI realtime session. Send a unique `Idempotency-Key`; retries with the same user, key, and body return the same session - and deterministic short-lived `client_secret` without reserving credits or capacity + and deterministic short-lived `client_secret` without reserving funds or capacity twice. The browser receives only that BeatAPI secret and connects with `@beatapi/realtime`. @@ -1280,7 +2552,8 @@ paths: - in: header name: Idempotency-Key required: true - schema: { type: string, maxLength: 128 } + schema: { type: string, minLength: 1, maxLength: 128 } + example: rts-create-cus_123-01 requestBody: required: true content: @@ -1292,14 +2565,22 @@ paths: max_duration_seconds: type: integer enum: [15, 60, 300] + description: Required maximum live session duration in seconds. The USD amount is reserved for the selected 15, 60, or 300 second tier. allowed_origins: type: array minItems: 1 maxItems: 10 - items: { type: string, format: uri } + description: Exact browser origins allowed to use the short-lived session secret. + items: + type: string + format: uri + pattern: '^(https://[A-Za-z0-9.-]+(?::[0-9]+)?|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]+)?)$' + description: Exact browser origin. Use HTTPS in production; HTTP is accepted only for localhost development. + example: https://app.example.com metadata: type: object maxProperties: 20 + description: Optional server-defined string metadata for your own correlation. Up to 20 keys; keys are at most 64 characters and values at most 256 characters. propertyNames: { maxLength: 64 } additionalProperties: { type: string, maxLength: 256 } example: @@ -1311,11 +2592,25 @@ paths: description: Realtime session created content: application/json: - schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } + schema: { $ref: '#/components/schemas/RealtimeSessionCreateResponse' } + example: + data: + id: rts_8K2qA + object: realtime.session + status: ready + client_secret: brt_live_example_short_lived_secret + expires_at: '2026-08-12T10:01:00.000Z' + max_duration_seconds: 60 + allowed_origins: ['https://app.example.com'] + credits: { reserved: 1.2, settled: 0, refunded: 0 } + request_id: req_abc123 + created_at: '2026-08-12T10:00:00.000Z' + connected_at: null + closed_at: null '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '402': - description: Insufficient credits + description: Insufficient USD balance content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } '409': description: Idempotency conflict @@ -1379,24 +2674,44 @@ paths: example: data: object: usage - credit_balance: 1080 + credit_balance: 21.6 total_tasks: 12 - credits_settled: 720 - credits_refunded: 450 + credits_settled: 14.4 + credits_refunded: 9 concurrency: limit: 2 active: 1 realtime: sessions: 3 - credits: 90 + credits: 1.8 active: 1 by_workflow: - workflow: music-video tasks: 8 - credits_settled: 480 + credits_settled: 9.6 - workflow: ecommerce-video tasks: 4 - credits_settled: 240 + credits_settled: 4.8 + by_capability: + - task_kind: image + capability_id: seedream-5-pro + tasks: 3 + credits_settled: 0.42 + - task_kind: video + capability_id: veo-3.1 + tasks: 2 + credits_settled: 14 + by_model: + - media_type: image + model: seedream-5-pro + tasks: 3 + credits_settled: 0.42 + by_api_key: + - api_key_id: key_abc123 + title: Production + key_prefix: sk_live_abcd + tasks: 12 + credits_settled: 14.4 '401': $ref: '#/components/responses/Unauthorized' @@ -1419,7 +2734,7 @@ paths: (`image/png`, `image/jpeg`, `image/webp`). - Audio: `mp3`, `wav`, `aac`, `m4a` (`audio/mpeg`, `audio/wav`, `audio/aac`, `audio/mp4`). - - Audio uploads must be 10-180 seconds. + - Audio uploads must be 10-300 seconds. The selected Music Video tier applies its own task limit: Standard 10-180 seconds; Premium 10-300 seconds. - Subtitles: `srt` (`application/x-subrip`; multipart uploads may use `text/plain` only when the filename ends in `.srt`). - PDF, generic text files, octet-stream uploads, videos, and zip files @@ -1445,6 +2760,22 @@ paths: purpose: type: string enum: [input] + image/png: + schema: { type: string, format: binary } + image/jpeg: + schema: { type: string, format: binary } + image/webp: + schema: { type: string, format: binary } + audio/mpeg: + schema: { type: string, format: binary } + audio/wav: + schema: { type: string, format: binary } + audio/aac: + schema: { type: string, format: binary } + audio/mp4: + schema: { type: string, format: binary } + application/x-subrip: + schema: { type: string, format: binary } responses: '201': description: File uploaded @@ -1468,6 +2799,10 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' /v1/webhooks: get: @@ -1499,6 +2834,10 @@ paths: updated_at: 1782210000 '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' post: operationId: createWebhookEndpoint tags: [Webhooks] @@ -1507,8 +2846,9 @@ paths: security: - BearerAuth: [] description: | - The signing secret is returned only once at creation time. Store it - securely. Later responses return a masked secret. + The public API returns the signing secret in full at creation time. Store + it securely; later public API responses return a masked secret. An + authenticated dashboard owner can explicitly reveal the secret again. BeatAPI sends these headers with each delivery: - `x-beatapi-event`: `task.succeeded` or `task.failed` @@ -1572,8 +2912,9 @@ paths: ``` Reject old timestamps to prevent replay attacks. A 5 minute window is - recommended. Failed deliveries are retried at most 3 times with fixed - backoff windows of 1 minute, 5 minutes, and 15 minutes. Polling + recommended. A delivery is attempted at most 3 times total: the initial + request plus up to 2 retries, with fixed backoff windows of 1 minute and + 5 minutes. Polling `GET /v1/tasks/{task_id}` remains the source of truth. requestBody: required: true @@ -1586,10 +2927,14 @@ paths: url: type: string format: uri + pattern: '^https://' + description: Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. description: type: string + description: Optional internal label for identifying the endpoint in your account. events: type: array + description: Task events to deliver. Omit to subscribe to both `task.succeeded` and `task.failed`. items: type: string enum: [task.succeeded, task.failed] @@ -1618,6 +2963,10 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' /v1/webhooks/{id}: get: @@ -1660,6 +3009,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' patch: operationId: updateWebhookEndpoint tags: [Webhooks] @@ -1684,6 +3037,8 @@ paths: url: type: string format: uri + pattern: '^https://' + description: Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL. description: type: string status: @@ -1707,6 +3062,16 @@ paths: $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' + '404': + description: Webhook endpoint not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' delete: operationId: deleteWebhookEndpoint tags: [Webhooks] @@ -1734,3 +3099,13 @@ paths: deleted: true '401': $ref: '#/components/responses/Unauthorized' + '404': + description: Webhook endpoint not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimited' + '500': + $ref: '#/components/responses/InternalError' diff --git a/skills/beatapi-video/references/credits-and-limits.md b/skills/beatapi-video/references/credits-and-limits.md index 593e9a1..33bb294 100644 --- a/skills/beatapi-video/references/credits-and-limits.md +++ b/skills/beatapi-video/references/credits-and-limits.md @@ -1,23 +1,23 @@ -# Credits and launch limits +# USD balance and launch limits Use the bundled OpenAPI contract as the source of truth if these launch values change. -## Customer credit rates +## Customer balance -| Workflow/control | Credits | -| --- | ---: | -| Music Video 540p standard | 4 per second | -| Music Video 720p standard | 5 per second | -| Music Video 1080p standard | 6 per second | -| Music Video lip-sync add-on | +2 per second | -| Music Video 720p high | 16 per second | -| Music Video 1080p high | 18 per second | -| Ecommerce Video 1080p | 15 per second | -| Manual composition | 1 fixed credit | +Customer balances and usage are USD-denominated. Compatibility fields retain +names such as `credit_balance`, `credits_reserved`, `credits_charged`, +`credits_settled`, and `credits_refunded`; 1 Credit = $1 USD. Treat these as +decimal USD amounts, not integer token counts. -Shot editing uses the selected Music Video quality/resolution rate multiplied -by shot duration. Default shot duration is 5 seconds. +Do not use legacy per-second credit tables. Image, video, workflow, Realtime, +and Effect prices depend on the selected public contract and can change. Check +the current public documentation, then read the exact reserved/charged amount +from the accepted task or session. Manual Music Video composition is the one +fixed public exception at $1 USD. + +Each verified new account currently receives a $2 welcome balance that does not +expire. Do not promise it without checking the current contract. For Music Video creation, BeatAPI bills detected audio duration. Use the request `duration` only as a 10-180 second fallback when duration detection @@ -41,7 +41,7 @@ local inputs with `beatapi files upload`. Run `beatapi usage` before paid work. Check: -- `credit_balance`; +- `credit_balance` as a USD amount; - `concurrency.limit`; - `concurrency.active`. @@ -54,7 +54,7 @@ counting as active processing. - Allowed maximum durations are 15, 60, and 300 seconds. - Every create request requires 1-10 exact HTTPS `allowed_origins` and an `Idempotency-Key`. -- Creation reserves credits; billing activates only after the first accepted +- Creation reserves USD balance; billing activates only after the first accepted heartbeat after remote output begins. Inspect `credits.reserved`, `credits.settled`, and `credits.refunded` on the session. - A Realtime session has its own capacity rules. Treat diff --git a/skills/beatapi-video/references/generation-and-effects.md b/skills/beatapi-video/references/generation-and-effects.md new file mode 100644 index 0000000..183a68b --- /dev/null +++ b/skills/beatapi-video/references/generation-and-effects.md @@ -0,0 +1,53 @@ +# Generation models and Effects + +Use stable BeatAPI aliases only. Discover the current catalog before choosing a +model; never expose or invent internal provider routes. + +## Image models + +| Alias | Public modes | Important request rule | +| --- | --- | --- | +| `nano-banana` | text | Prompt only; no reference images | +| `nano-banana-pro` | text, reference | Up to 8 public HTTPS images | +| `gpt-image-2` | text, reference | Up to 16 public HTTPS images; aspect-ratio availability depends on resolution | +| `seedream-5-pro` | text, reference | Up to 10 public HTTPS images | + +## Video models + +| Alias | Public modes | Important request rule | +| --- | --- | --- | +| `minimax-h3` | text, frames, reference | 4-15 seconds; frame inputs cannot be mixed with reference inputs | +| `seedance-2` | text, frames, reference | 4-15 seconds; supports generated audio | +| `seedance-2-fast` | text, frames, reference | 4-15 seconds; 480p or 720p | +| `seedance-2-mini` | text, frames, reference | Low-cost 4-15 second route; generated audio is unavailable | +| `veo-3.1` | text, frames, reference | Fixed 8 seconds; frame and reference-image inputs cannot be mixed | +| `seedance-2.5` | text, frames, reference | 4-30 seconds; current output is 720p | +| `kling-3` | text, frames, reference | 3-15 seconds; multi-shot mode requires `multi_prompt` | + +Run `beatapi models list` or call `beatapi_list_generation_models` before +generation. Then copy `assets/image-generation.json` or +`assets/video-generation.json`, select exactly one model alias, and retain only +fields in that model's schema in `beatapi.openapi.yaml`. + +Do not combine `images` with `reference_*` fields when the selected video model +forbids it. Audio references require at least one reference image or video for +the Seedance routes. Reject unknown fields instead of forwarding them. + +## Effects + +Effects are versioned published capabilities, not model aliases. + +1. List active Effects with `beatapi_list_effects` or `beatapi effects list`. +2. Read the selected Effect with `beatapi_get_effect` or + `beatapi effects get EFFECT_ID`. +3. Validate image count, MIME types, dimensions, subject requirements, and + supported options against that returned version. +4. Copy `assets/effect-task.json`, preserve the selected `effect_id`, and omit + unsupported options. +5. Create with `beatapi_create_effect` or `beatapi effects create --file ...`. + Use one stable idempotency key for retries of the same request. +6. Poll the shared task endpoint and require hosted output before reporting + completion. + +The catalog exposes only publication-safe fields. Never infer provider names, +template IDs, internal costs, or execution context. diff --git a/submission/release-notes.md b/submission/release-notes.md index 4b91421..94123d7 100644 --- a/submission/release-notes.md +++ b/submission/release-notes.md @@ -1,8 +1,12 @@ -# BeatAPI 0.2.0 +# BeatAPI next -Realtime-aligned Skills-only submission. +Unified generation and Effect API update. - Creates and monitors BeatAPI Music Video and Ecommerce Video workflows. +- Discovers four image and seven video model aliases and creates model-specific + image and video tasks. +- Discovers published versioned Effects, validates their current input contract, + and creates Effect tasks. - Creates, reads, and closes short-lived Realtime Video sessions while keeping the browser client secret out of model-visible output. - Handles local media upload, manual storyboard review, shot operations, @@ -11,5 +15,5 @@ Realtime-aligned Skills-only submission. the official CLI without placing credentials in conversations. - Requires the globally installed `beatapi` CLI for Skills-only hosts that do not supply BeatAPI MCP tools. -- Matches the current BeatAPI OpenAPI `1.0.0-launch` Realtime baseline. -- Includes six positive and three negative review cases. +- Matches the current BeatAPI OpenAPI `1.0.0-launch` unified API baseline. +- Includes model, Effect, workflow, Realtime, security, and recovery review cases. diff --git a/test/mcp-e2e.test.ts b/test/mcp-e2e.test.ts index e842bd4..176c09f 100644 --- a/test/mcp-e2e.test.ts +++ b/test/mcp-e2e.test.ts @@ -35,6 +35,44 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () ); return; } + if (request.url === "/v1/media/models") { + response.end( + JSON.stringify({ + data: { + object: "list", + data: [ + { + id: "nano-banana", + object: "generation_model", + name: "Nano Banana", + media_type: "image", + input_modes: ["text"], + }, + ], + }, + }), + ); + return; + } + if (request.url === "/v1/effects") { + response.end( + JSON.stringify({ + data: { + object: "list", + data: [{ id: "video-muscle-max", object: "effect" }], + }, + }), + ); + return; + } + if (request.url === "/v1/effects/video-muscle-max") { + response.end( + JSON.stringify({ + data: { id: "video-muscle-max", object: "effect", version: 1 }, + }), + ); + return; + } if (request.url === "/v1/usage") { response.end( JSON.stringify({ @@ -84,6 +122,28 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () ); return; } + if ( + request.url === "/v1/images/tasks" || + request.url === "/v1/videos/tasks" || + request.url === "/v1/effects/tasks" + ) { + response.statusCode = 201; + response.end( + JSON.stringify({ + data: { + id: `task_${request.url.split("/")[2]}`, + object: "task", + task_kind: request.url.includes("images") + ? "image" + : request.url.includes("videos") + ? "video" + : "effect", + status: "queued", + }, + }), + ); + return; + } if (request.url === "/v1/realtime/sessions" && request.method === "POST") { response.statusCode = 201; response.end( @@ -178,7 +238,7 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () try { await client.connect(transport); const listed = await client.listTools(); - assert.equal(listed.tools.length, 19); + assert.equal(listed.tools.length, 25); assert.ok(listed.tools.every((tool) => !/api[_-]?key/i.test(JSON.stringify(tool.inputSchema)))); const workflows = await client.callTool({ @@ -190,6 +250,55 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () "music-video", ); + const models = await client.callTool({ + name: "beatapi_list_generation_models", + arguments: {}, + }); + assert.equal( + (models.structuredContent as { result: Array<{ id: string }> }).result[0]?.id, + "nano-banana", + ); + + const imageTask = await client.callTool({ + name: "beatapi_create_image", + arguments: { model: "nano-banana", prompt: "Editorial still" }, + }); + assert.equal( + (imageTask.structuredContent as { result: { id: string } }).result.id, + "task_images", + ); + + const effects = await client.callTool({ + name: "beatapi_list_effects", + arguments: {}, + }); + assert.equal( + (effects.structuredContent as { result: Array<{ id: string }> }).result[0]?.id, + "video-muscle-max", + ); + + const effect = await client.callTool({ + name: "beatapi_get_effect", + arguments: { effect_id: "video-muscle-max" }, + }); + assert.equal( + (effect.structuredContent as { result: { version: number } }).result.version, + 1, + ); + + const effectTask = await client.callTool({ + name: "beatapi_create_effect", + arguments: { + effect_id: "video-muscle-max", + images: ["https://media.example.com/portrait.png"], + idempotency_key: "effect-mcp-test", + }, + }); + assert.equal( + (effectTask.structuredContent as { result: { id: string } }).result.id, + "task_effects", + ); + const musicTask = await client.callTool({ name: "beatapi_create_music_video", arguments: { @@ -302,7 +411,13 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () ); const authenticatedRequests = requests.filter( - (request) => request.path !== "/v1/workflows", + (request) => + request.path !== "/v1/workflows" && + request.path !== "/v1/media/models" && + !( + request.method === "GET" && + request.path.startsWith("/v1/effects") + ), ); assert.ok( authenticatedRequests.every( @@ -313,6 +428,18 @@ test("bundled stdio MCP serves BeatAPI tools and protects credentials", async () requests.find((request) => request.path === "/v1/workflows")?.authorization, undefined, ); + assert.equal( + requests.find((request) => request.path === "/v1/media/models")?.authorization, + undefined, + ); + assert.ok( + requests + .filter( + (request) => + request.method === "GET" && request.path.startsWith("/v1/effects"), + ) + .every((request) => request.authorization === undefined), + ); } finally { await client.close().catch(() => undefined); await transport.close().catch(() => undefined); diff --git a/test/plugin.test.ts b/test/plugin.test.ts index ae588f8..59b0bec 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -10,6 +10,12 @@ const root = resolve(import.meta.dirname, ".."); const expectedToolNames = [ "beatapi_check_setup", "beatapi_list_workflows", + "beatapi_list_generation_models", + "beatapi_create_image", + "beatapi_create_video", + "beatapi_list_effects", + "beatapi_get_effect", + "beatapi_create_effect", "beatapi_get_usage", "beatapi_upload_file", "beatapi_create_music_video", @@ -47,6 +53,9 @@ test("marks read, write, paid, and destructive tools accurately", () => { for (const name of [ "beatapi_check_setup", "beatapi_list_workflows", + "beatapi_list_generation_models", + "beatapi_list_effects", + "beatapi_get_effect", "beatapi_get_usage", "beatapi_get_task", "beatapi_wait_for_task", @@ -59,6 +68,9 @@ test("marks read, write, paid, and destructive tools accurately", () => { for (const name of [ "beatapi_create_music_video", + "beatapi_create_image", + "beatapi_create_video", + "beatapi_create_effect", "beatapi_edit_music_video_shot", "beatapi_compose_music_video", "beatapi_create_ecommerce_video",