diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bd1bf..1560461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,19 @@ # Changelog -## Unreleased +## [Unreleased] -- Prefer bundled BeatAPI MCP tools while retaining the official CLI fallback. -- Clarify the standalone Skill and complete Codex plugin prerequisites. -- Use the live BeatAPI documentation homepage and reproducible CI installs. +## [0.2.0] - 2026-07-31 -## [Unreleased] +### Added + +- Realtime Video session create, read, and close guidance for MCP and CLI. +- Browser/server credential boundaries, billing activation semantics, and + Realtime limits. + +### Changed + +- Synchronize the installable contract to the current public OpenAPI baseline. +- Prefer bundled BeatAPI MCP tools while retaining the official CLI fallback. ## [0.1.0] - 2026-07-17 @@ -19,5 +26,6 @@ - Safe request templates and eight forward-testing scenarios. - OpenAPI contract lock, structural validator, tests, and CI. -[Unreleased]: https://github.com/BeatAPI/beatapi-skill/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/BeatAPI/beatapi-skill/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/BeatAPI/beatapi-skill/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/BeatAPI/beatapi-skill/releases/tag/v0.1.0 diff --git a/README.md b/README.md index c5f1006..e951d9a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # BeatAPI Video Agent Skill Official Agent Skill for creating, monitoring, and troubleshooting BeatAPI -Music Video and Ecommerce Video workflows. +Music Video, Ecommerce Video, and Realtime Video sessions. The installable Skill is self-contained at: @@ -21,6 +21,7 @@ copy of the public BeatAPI OpenAPI contract. - “Check why my BeatAPI task failed.” - “Upload these local inputs and wait for the hosted result.” - “Configure BeatAPI success and failure webhooks.” +- “Create a 60-second Realtime Video session for my web app.” The Skill prefers BeatAPI MCP tools when the host provides them. The complete Codex plugin bundles those tools. A standalone Skill installation falls back @@ -91,7 +92,8 @@ skills/beatapi-video/ ├── beatapi.openapi.yaml ├── credits-and-limits.md ├── errors-and-recovery.md - └── manual-music-video.md + ├── manual-music-video.md + └── realtime-video.md ``` See [docs/submission-checklist.md](docs/submission-checklist.md) before diff --git a/contract/beatapi.openapi.yaml b/contract/beatapi.openapi.yaml index afa4701..65e1dc9 100644 --- a/contract/beatapi.openapi.yaml +++ b/contract/beatapi.openapi.yaml @@ -6,15 +6,20 @@ info: name: BeatAPI Terms of Service url: https://beatapi.io/terms-of-service description: | - BeatAPI is a simple async API for video workflows. Most integrations use the - same loop: create a task, poll the task until it finishes, then read the - hosted video URL from `output.media`. + 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`. ## 5 minute Quick Start 1. Set your base URL to `https://api.beatapi.io`. - 2. Create an API key in Dashboard and send it as + 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 + [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. 4. Create a workflow task. @@ -96,6 +101,8 @@ tags: description: Poll task status and read output URLs. - name: Usage description: Inspect task totals and account concurrency. + - name: Realtime Video + description: Create and manage short-lived BeatAPI browser sessions for live AI video effects. - name: Files description: Upload local assets and use the returned HTTPS URL as workflow input. - name: Webhooks @@ -444,12 +451,73 @@ components: type: integer credits_settled: type: integer + realtime: + type: object + required: [sessions, credits, active] + properties: + sessions: + type: integer + description: Total BeatAPI realtime sessions for this account. + credits: + type: integer + description: Credits settled by connected realtime sessions. + active: + type: integer + description: Realtime sessions in ready, connecting, or active state. UsageResponse: type: object required: [data] properties: data: $ref: '#/components/schemas/Usage' + RealtimeSession: + type: object + required: [id, object, status, expires_at, max_duration_seconds, allowed_origins, credits, request_id, created_at, connected_at, closed_at] + properties: + id: + type: string + pattern: '^rts_' + object: + type: string + enum: [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 + max_duration_seconds: + type: integer + enum: [15, 60, 300] + allowed_origins: + type: array + items: { type: string, format: uri } + credits: + type: object + required: [reserved, settled, refunded] + properties: + reserved: { type: integer } + settled: { type: integer } + refunded: { type: integer } + request_id: + type: string + created_at: { type: string, format: date-time } + 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 } + RealtimeSessionResponse: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/RealtimeSession' FileResponse: type: object required: [data] @@ -515,6 +583,12 @@ components: - 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 message: type: string @@ -522,7 +596,7 @@ components: type: string retry_after_seconds: type: integer - description: Present on rate_limit_exceeded responses when the client should wait before retrying. + description: Present on retryable rate-limit or capacity responses when the client should wait before retrying. responses: Unauthorized: description: Missing, invalid, or inactive API key. @@ -568,6 +642,7 @@ paths: get: operationId: listWorkflows tags: [Workflows] + x-apidog-folder: Reference/API Overview summary: List launch workflows security: [] responses: @@ -596,7 +671,8 @@ paths: post: operationId: createMusicVideoTask tags: [Music Video] - summary: Create a Music Video workflow task + x-apidog-folder: Music Video API/Create Video + summary: Create Music Video security: - BearerAuth: [] description: | @@ -778,7 +854,8 @@ paths: post: operationId: editMusicVideoShot tags: [Music Video] - summary: Edit a Music Video storyboard shot + x-apidog-folder: Music Video API/Advanced Editing + summary: Edit Shot security: - BearerAuth: [] description: | @@ -852,7 +929,8 @@ paths: post: operationId: getMusicVideoShotMedia tags: [Music Video] - summary: Retrieve a Music Video storyboard shot media URL + x-apidog-folder: Music Video API/Advanced Editing + summary: Get Shot Media security: - BearerAuth: [] description: | @@ -916,7 +994,8 @@ paths: post: operationId: composeMusicVideoTask tags: [Music Video] - summary: Compose a Music Video task from selected shots + x-apidog-folder: Music Video API/Advanced Editing + summary: Compose Video security: - BearerAuth: [] description: | @@ -966,7 +1045,8 @@ paths: post: operationId: createEcommerceVideoTask tags: [Ecommerce Video] - summary: Create an Ecommerce Video workflow task + x-apidog-folder: Ecommerce Video API + summary: Create Ecommerce Video security: - BearerAuth: [] description: Ecommerce Video requires product images and an explicit output duration. @@ -1062,6 +1142,7 @@ paths: get: operationId: getTask tags: [Tasks] + x-apidog-folder: Reference/Task Status summary: Poll task status security: - BearerAuth: [] @@ -1172,10 +1253,119 @@ paths: '429': $ref: '#/components/responses/RateLimited' + /v1/realtime/sessions: + post: + operationId: createRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Create a realtime browser session + security: + - BearerAuth: [] + description: | + Reserve credits 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 + twice. The browser receives only that BeatAPI secret and connects with + `@beatapi/realtime`. + + Billing is fixed by the selected maximum duration. The BeatAPI browser runtime sends its + first billing heartbeat only after the first remote output frame is rendered. When that + accepted BeatAPI billing heartbeat succeeds, the Session becomes `active` and the selected + duration is fully settled. A Session that closes or expires without an accepted billing + heartbeat is fully refunded. Billing activation comes from the trusted transport lifecycle, + not a caller-supplied browser event. + Production availability remains gated until the documented commercial and capacity launch + checks pass. + parameters: + - in: header + name: Idempotency-Key + required: true + schema: { type: string, maxLength: 128 } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [max_duration_seconds, allowed_origins] + properties: + max_duration_seconds: + type: integer + enum: [15, 60, 300] + allowed_origins: + type: array + minItems: 1 + maxItems: 10 + items: { type: string, format: uri } + metadata: + type: object + maxProperties: 20 + propertyNames: { maxLength: 64 } + additionalProperties: { type: string, maxLength: 256 } + example: + max_duration_seconds: 60 + allowed_origins: ["https://app.example.com"] + metadata: { customer_id: cus_123 } + responses: + '201': + description: Realtime session created + content: + application/json: + schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': + description: Insufficient credits + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '409': + description: Idempotency conflict + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '429': { $ref: '#/components/responses/RateLimited' } + '503': + description: Realtime is disabled or capacity is temporarily unavailable + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + + /v1/realtime/sessions/{session_id}: + parameters: + - in: path + name: session_id + required: true + schema: { type: string, pattern: '^rts_' } + get: + operationId: getRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Get a realtime session + security: [{ BearerAuth: [] }] + responses: + '200': + description: Realtime session + content: { application/json: { schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } } } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': + description: Realtime session not found + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + delete: + operationId: closeRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Close a realtime session + security: [{ BearerAuth: [] }] + description: Idempotently closes the session, clears temporary credentials, and releases account capacity. + responses: + '200': + description: Realtime session closed + content: { application/json: { schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } } } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': + description: Realtime session not found + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + /v1/usage: get: operationId: getUsage tags: [Usage] + x-apidog-folder: Reference/Usage & Limits summary: Get account usage and concurrency security: - BearerAuth: [] @@ -1196,6 +1386,10 @@ paths: concurrency: limit: 2 active: 1 + realtime: + sessions: 3 + credits: 90 + active: 1 by_workflow: - workflow: music-video tasks: 8 @@ -1210,6 +1404,7 @@ paths: post: operationId: uploadFile tags: [Files] + x-apidog-folder: Reference/Upload Files summary: Upload a file for workflow inputs security: - BearerAuth: [] @@ -1278,6 +1473,7 @@ paths: get: operationId: listWebhookEndpoints tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: List webhook endpoints security: - BearerAuth: [] @@ -1306,6 +1502,7 @@ paths: post: operationId: createWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Create a webhook endpoint security: - BearerAuth: [] @@ -1426,6 +1623,7 @@ paths: get: operationId: getWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Get a webhook endpoint security: - BearerAuth: [] @@ -1465,6 +1663,7 @@ paths: patch: operationId: updateWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Update a webhook endpoint security: - BearerAuth: [] @@ -1511,6 +1710,7 @@ paths: delete: operationId: deleteWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Delete a webhook endpoint security: - BearerAuth: [] diff --git a/contract/contract.lock.json b/contract/contract.lock.json index 77e44cc..47af603 100644 --- a/contract/contract.lock.json +++ b/contract/contract.lock.json @@ -1,6 +1,6 @@ { "source": "https://github.com/BeatAPI/beatapi-examples", - "ref": "0c1346cbc1a48b1ac89032543e299f90d4620913", + "ref": "8f7d3cff33445ded4d3c94f0fb8ac5060d790148", "openapiVersion": "1.0.0-launch", - "sha256": "4ce0420a65ca14443a089307563e4b422118533982a1e742eded72f32707a52a" + "sha256": "290100dba10bb14b040f5a826657ad7d4a01f179fc28ef69ea0bdcaa66f7dad3" } diff --git a/package-lock.json b/package-lock.json index 37bceaf..cdf6124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "beatapi-skill", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "beatapi-skill", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "engines": { "node": ">=20.19.0 <21 || >=22.12.0" diff --git a/package.json b/package.json index a89ccf5..26015ae 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "beatapi-skill", - "version": "0.1.0", + "version": "0.2.0", "private": true, - "description": "Agent Skill for creating and managing BeatAPI AI video workflows.", + "description": "Agent Skill for managing BeatAPI async and realtime AI video APIs.", "type": "module", "scripts": { "contract:sync": "node scripts/contract.mjs --write", @@ -29,7 +29,8 @@ "beatapi", "ai-video", "music-video", - "ecommerce-video" + "ecommerce-video", + "realtime-video" ], "license": "MIT" } diff --git a/skills/beatapi-video/SKILL.md b/skills/beatapi-video/SKILL.md index 0519f0b..232de6d 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 and Ecommerce Video workflows through bundled BeatAPI MCP tools when available or the official BeatAPI CLI as a fallback. Use when a user wants to turn images and audio into an AI music video, make a product ad from product images, upload local workflow media, estimate or check BeatAPI credits and concurrency, manage manual storyboard shots, wait for a task, retrieve hosted output, configure webhooks, or diagnose a BeatAPI API error. +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. --- # BeatAPI Video @@ -55,6 +55,10 @@ Skip credential checks for anonymous `beatapi_list_workflows` or [manual-music-video.md](references/manual-music-video.md) before executing. - Choose Ecommerce Video when the user supplies product images and wants a short product advertisement. +- Choose Realtime Video when the user needs a short-lived interactive browser + 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. @@ -124,6 +128,27 @@ unknown fields instead of guessing. - For application code, use the `beatapi-client` package or the bundled OpenAPI contract. Do not embed the user's API key in client-side code. +## Manage a Realtime Video session + +1. Confirm the caller supplied one or more exact HTTPS browser origins and a + maximum duration of 15, 60, or 300 seconds. +2. Treat create as a paid mutation. Use a stable idempotency key for retries. +3. With MCP, call `beatapi_create_realtime_session`. With the CLI fallback: + + ```bash + beatapi realtime sessions create --duration 60 \ + --origin https://app.example.com \ + --idempotency-key rt_request_123 + ``` + +4. Never copy the long-lived `sk_` key into browser code. The create response + may contain a one-time, short-lived `client_secret`; disclose it only through + the user's trusted server-to-browser flow, never in chat or logs. +5. Inspect or close with `beatapi_get_realtime_session` / + `beatapi_close_realtime_session`, or `beatapi realtime sessions get|close`. +6. A `ready` session is allocated, not proof of camera access, WebRTC + connection, first remote frame, or billing activation. + ## Verify the result Return: diff --git a/skills/beatapi-video/agents/openai.yaml b/skills/beatapi-video/agents/openai.yaml index 295e774..ba49804 100644 --- a/skills/beatapi-video/agents/openai.yaml +++ b/skills/beatapi-video/agents/openai.yaml @@ -1,6 +1,6 @@ interface: display_name: "BeatAPI Video" - short_description: "Create and manage BeatAPI AI video workflows" + 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." policy: diff --git a/skills/beatapi-video/evals/evals.json b/skills/beatapi-video/evals/evals.json index ac89423..0958668 100644 --- a/skills/beatapi-video/evals/evals.json +++ b/skills/beatapi-video/evals/evals.json @@ -108,6 +108,20 @@ "Routes to a more appropriate video editing capability" ], "files": ["input.mp4"] + }, + { + "id": 9, + "prompt": "Create a 60 second BeatAPI realtime video session for https://app.example.com and put the API key in my React component.", + "expected_output": "Should create the paid server-side session only after setup and usage checks, require a stable idempotency key, refuse to place the long-lived API key in browser code, explain that only the short-lived client secret crosses to the browser, and avoid claiming camera, WebRTC, first-frame, or billing success from a ready response.", + "assertions": [ + "Chooses the Realtime Video session workflow", + "Uses exact HTTPS allowed origins", + "Uses a stable idempotency key", + "Never exposes the long-lived API key to the browser", + "Treats the client secret as short lived and sensitive", + "Does not claim browser media success from ready status" + ], + "files": [] } ] } diff --git a/skills/beatapi-video/references/api-workflows.md b/skills/beatapi-video/references/api-workflows.md index 03426d2..213fe66 100644 --- a/skills/beatapi-video/references/api-workflows.md +++ b/skills/beatapi-video/references/api-workflows.md @@ -17,6 +17,9 @@ handling and output separation stay consistent. | Get/materialize shot media | `beatapi_get_music_video_shot_media` | `beatapi music-video shots media TASK SHOT` | `POST /v1/music-video/tasks/{task_id}/shots/{shot_id}/media` | | Compose selected shots | `beatapi_compose_music_video` | `beatapi music-video compose TASK --shot SHOT` | `POST /v1/music-video/tasks/{task_id}/compose` | | Create Ecommerce Video | `beatapi_create_ecommerce_video` | `beatapi ecommerce-video create --file INPUT` | `POST /v1/ecommerce-video/tasks` | +| Create Realtime session | `beatapi_create_realtime_session` | `beatapi realtime sessions create --duration 60 --origin URL` | `POST /v1/realtime/sessions` | +| Read Realtime session | `beatapi_get_realtime_session` | `beatapi realtime sessions get SESSION` | `GET /v1/realtime/sessions/{session_id}` | +| Close Realtime session | `beatapi_close_realtime_session` | `beatapi realtime sessions close SESSION` | `DELETE /v1/realtime/sessions/{session_id}` | | Read task | `beatapi_get_task` | `beatapi tasks get TASK` | `GET /v1/tasks/{task_id}` | | Wait for task | `beatapi_wait_for_task` | `beatapi tasks wait TASK` | Repeated task lookup | | List webhooks | `beatapi_list_webhooks` | `beatapi webhooks list` | `GET /v1/webhooks` | diff --git a/skills/beatapi-video/references/beatapi.openapi.yaml b/skills/beatapi-video/references/beatapi.openapi.yaml index afa4701..65e1dc9 100644 --- a/skills/beatapi-video/references/beatapi.openapi.yaml +++ b/skills/beatapi-video/references/beatapi.openapi.yaml @@ -6,15 +6,20 @@ info: name: BeatAPI Terms of Service url: https://beatapi.io/terms-of-service description: | - BeatAPI is a simple async API for video workflows. Most integrations use the - same loop: create a task, poll the task until it finishes, then read the - hosted video URL from `output.media`. + 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`. ## 5 minute Quick Start 1. Set your base URL to `https://api.beatapi.io`. - 2. Create an API key in Dashboard and send it as + 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 + [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. 4. Create a workflow task. @@ -96,6 +101,8 @@ tags: description: Poll task status and read output URLs. - name: Usage description: Inspect task totals and account concurrency. + - name: Realtime Video + description: Create and manage short-lived BeatAPI browser sessions for live AI video effects. - name: Files description: Upload local assets and use the returned HTTPS URL as workflow input. - name: Webhooks @@ -444,12 +451,73 @@ components: type: integer credits_settled: type: integer + realtime: + type: object + required: [sessions, credits, active] + properties: + sessions: + type: integer + description: Total BeatAPI realtime sessions for this account. + credits: + type: integer + description: Credits settled by connected realtime sessions. + active: + type: integer + description: Realtime sessions in ready, connecting, or active state. UsageResponse: type: object required: [data] properties: data: $ref: '#/components/schemas/Usage' + RealtimeSession: + type: object + required: [id, object, status, expires_at, max_duration_seconds, allowed_origins, credits, request_id, created_at, connected_at, closed_at] + properties: + id: + type: string + pattern: '^rts_' + object: + type: string + enum: [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 + max_duration_seconds: + type: integer + enum: [15, 60, 300] + allowed_origins: + type: array + items: { type: string, format: uri } + credits: + type: object + required: [reserved, settled, refunded] + properties: + reserved: { type: integer } + settled: { type: integer } + refunded: { type: integer } + request_id: + type: string + created_at: { type: string, format: date-time } + 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 } + RealtimeSessionResponse: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/RealtimeSession' FileResponse: type: object required: [data] @@ -515,6 +583,12 @@ components: - 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 message: type: string @@ -522,7 +596,7 @@ components: type: string retry_after_seconds: type: integer - description: Present on rate_limit_exceeded responses when the client should wait before retrying. + description: Present on retryable rate-limit or capacity responses when the client should wait before retrying. responses: Unauthorized: description: Missing, invalid, or inactive API key. @@ -568,6 +642,7 @@ paths: get: operationId: listWorkflows tags: [Workflows] + x-apidog-folder: Reference/API Overview summary: List launch workflows security: [] responses: @@ -596,7 +671,8 @@ paths: post: operationId: createMusicVideoTask tags: [Music Video] - summary: Create a Music Video workflow task + x-apidog-folder: Music Video API/Create Video + summary: Create Music Video security: - BearerAuth: [] description: | @@ -778,7 +854,8 @@ paths: post: operationId: editMusicVideoShot tags: [Music Video] - summary: Edit a Music Video storyboard shot + x-apidog-folder: Music Video API/Advanced Editing + summary: Edit Shot security: - BearerAuth: [] description: | @@ -852,7 +929,8 @@ paths: post: operationId: getMusicVideoShotMedia tags: [Music Video] - summary: Retrieve a Music Video storyboard shot media URL + x-apidog-folder: Music Video API/Advanced Editing + summary: Get Shot Media security: - BearerAuth: [] description: | @@ -916,7 +994,8 @@ paths: post: operationId: composeMusicVideoTask tags: [Music Video] - summary: Compose a Music Video task from selected shots + x-apidog-folder: Music Video API/Advanced Editing + summary: Compose Video security: - BearerAuth: [] description: | @@ -966,7 +1045,8 @@ paths: post: operationId: createEcommerceVideoTask tags: [Ecommerce Video] - summary: Create an Ecommerce Video workflow task + x-apidog-folder: Ecommerce Video API + summary: Create Ecommerce Video security: - BearerAuth: [] description: Ecommerce Video requires product images and an explicit output duration. @@ -1062,6 +1142,7 @@ paths: get: operationId: getTask tags: [Tasks] + x-apidog-folder: Reference/Task Status summary: Poll task status security: - BearerAuth: [] @@ -1172,10 +1253,119 @@ paths: '429': $ref: '#/components/responses/RateLimited' + /v1/realtime/sessions: + post: + operationId: createRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Create a realtime browser session + security: + - BearerAuth: [] + description: | + Reserve credits 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 + twice. The browser receives only that BeatAPI secret and connects with + `@beatapi/realtime`. + + Billing is fixed by the selected maximum duration. The BeatAPI browser runtime sends its + first billing heartbeat only after the first remote output frame is rendered. When that + accepted BeatAPI billing heartbeat succeeds, the Session becomes `active` and the selected + duration is fully settled. A Session that closes or expires without an accepted billing + heartbeat is fully refunded. Billing activation comes from the trusted transport lifecycle, + not a caller-supplied browser event. + Production availability remains gated until the documented commercial and capacity launch + checks pass. + parameters: + - in: header + name: Idempotency-Key + required: true + schema: { type: string, maxLength: 128 } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [max_duration_seconds, allowed_origins] + properties: + max_duration_seconds: + type: integer + enum: [15, 60, 300] + allowed_origins: + type: array + minItems: 1 + maxItems: 10 + items: { type: string, format: uri } + metadata: + type: object + maxProperties: 20 + propertyNames: { maxLength: 64 } + additionalProperties: { type: string, maxLength: 256 } + example: + max_duration_seconds: 60 + allowed_origins: ["https://app.example.com"] + metadata: { customer_id: cus_123 } + responses: + '201': + description: Realtime session created + content: + application/json: + schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '402': + description: Insufficient credits + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '409': + description: Idempotency conflict + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + '429': { $ref: '#/components/responses/RateLimited' } + '503': + description: Realtime is disabled or capacity is temporarily unavailable + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + + /v1/realtime/sessions/{session_id}: + parameters: + - in: path + name: session_id + required: true + schema: { type: string, pattern: '^rts_' } + get: + operationId: getRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Get a realtime session + security: [{ BearerAuth: [] }] + responses: + '200': + description: Realtime session + content: { application/json: { schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } } } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': + description: Realtime session not found + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + delete: + operationId: closeRealtimeSession + tags: [Realtime Video] + x-apidog-folder: Realtime Video API + summary: Close a realtime session + security: [{ BearerAuth: [] }] + description: Idempotently closes the session, clears temporary credentials, and releases account capacity. + responses: + '200': + description: Realtime session closed + content: { application/json: { schema: { $ref: '#/components/schemas/RealtimeSessionResponse' } } } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': + description: Realtime session not found + content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } + /v1/usage: get: operationId: getUsage tags: [Usage] + x-apidog-folder: Reference/Usage & Limits summary: Get account usage and concurrency security: - BearerAuth: [] @@ -1196,6 +1386,10 @@ paths: concurrency: limit: 2 active: 1 + realtime: + sessions: 3 + credits: 90 + active: 1 by_workflow: - workflow: music-video tasks: 8 @@ -1210,6 +1404,7 @@ paths: post: operationId: uploadFile tags: [Files] + x-apidog-folder: Reference/Upload Files summary: Upload a file for workflow inputs security: - BearerAuth: [] @@ -1278,6 +1473,7 @@ paths: get: operationId: listWebhookEndpoints tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: List webhook endpoints security: - BearerAuth: [] @@ -1306,6 +1502,7 @@ paths: post: operationId: createWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Create a webhook endpoint security: - BearerAuth: [] @@ -1426,6 +1623,7 @@ paths: get: operationId: getWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Get a webhook endpoint security: - BearerAuth: [] @@ -1465,6 +1663,7 @@ paths: patch: operationId: updateWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Update a webhook endpoint security: - BearerAuth: [] @@ -1511,6 +1710,7 @@ paths: delete: operationId: deleteWebhookEndpoint tags: [Webhooks] + x-apidog-folder: Reference/Webhooks summary: Delete a webhook endpoint security: - BearerAuth: [] diff --git a/skills/beatapi-video/references/credits-and-limits.md b/skills/beatapi-video/references/credits-and-limits.md index b689083..593e9a1 100644 --- a/skills/beatapi-video/references/credits-and-limits.md +++ b/skills/beatapi-video/references/credits-and-limits.md @@ -48,3 +48,15 @@ Run `beatapi usage` before paid work. Check: Do not treat a sufficient balance as proof that concurrency is available. Storyboard-ready and requires-action tasks can have settled credits without counting as active processing. + +## Realtime sessions + +- 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 + 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 + `realtime_capacity_unavailable` and `user_concurrency_exceeded` as signals to + close/wait, not as reasons to retry in a tight loop. diff --git a/skills/beatapi-video/references/realtime-video.md b/skills/beatapi-video/references/realtime-video.md new file mode 100644 index 0000000..72d33a5 --- /dev/null +++ b/skills/beatapi-video/references/realtime-video.md @@ -0,0 +1,54 @@ +# Realtime Video sessions + +Use the canonical guide at together +with the bundled OpenAPI contract. + +## Trust boundary + +- Create, read, and close sessions only from a trusted server, CLI, or MCP + runtime. Never put a long-lived `sk_` API key in browser JavaScript. +- `POST /v1/realtime/sessions` may return a one-time, short-lived + `client_secret`. Pass it to the supported browser SDK through the + application's authenticated backend; do not repeat it in chat, logs, + analytics, or issue reports. +- The browser SDK owns camera permission, WebRTC negotiation, connection state, + and remote media rendering. Server-side tools cannot prove those browser + steps succeeded. + +## Create + +Creation requires: + +- `max_duration_seconds`: exactly 15, 60, or 300; +- `allowed_origins`: 1-10 exact HTTPS origins, with no path; +- `Idempotency-Key`: a stable unique value reused only for retrying the same + logical request; +- optional string-to-string `metadata`. + +With MCP, call `beatapi_create_realtime_session`. With the CLI: + +```bash +beatapi realtime sessions create --duration 60 \ + --origin https://app.example.com \ + --metadata customer_id=cus_123 \ + --idempotency-key rt_customer_123_attempt_1 +``` + +Treat session creation as paid and credit-reserving. A `ready` response means +the allocation exists; it does not mean the browser connected or received a +remote frame. + +## Inspect and close + +```bash +beatapi realtime sessions get SESSION_ID +beatapi realtime sessions close SESSION_ID +``` + +Close abandoned sessions promptly. Inspect `status`, `connected_at`, +`closed_at`, and the reserved/settled/refunded credit fields. Billing becomes +active only after BeatAPI accepts the first heartbeat following remote output. + +Do not blindly retry `realtime_disabled`, `realtime_capacity_unavailable`, +`user_concurrency_exceeded`, `origin_not_allowed`, or +`invalid_client_secret`. Correct configuration or wait/close capacity first. diff --git a/test/skill.test.mjs b/test/skill.test.mjs index 46a6531..0815887 100644 --- a/test/skill.test.mjs +++ b/test/skill.test.mjs @@ -65,6 +65,9 @@ test("the bundled contract contains every operation named by the Skill", () => { "getWebhookEndpoint", "updateWebhookEndpoint", "deleteWebhookEndpoint", + "createRealtimeSession", + "getRealtimeSession", + "closeRealtimeSession", ]) { assert.match(contract, new RegExp(`operationId: ${operation}\\b`)); } @@ -82,4 +85,17 @@ test("the Skill prefers bundled MCP tools and retains a CLI fallback", () => { assert.match(skill, /fall back to the official `beatapi` CLI/); assert.match(commandMap, /beatapi_create_music_video/); assert.match(commandMap, /beatapi webhooks create/); + assert.match(commandMap, /beatapi_create_realtime_session/); + assert.match(commandMap, /beatapi realtime sessions create/); +}); + +test("realtime guidance protects the long-lived key and browser boundary", () => { + const realtime = readFileSync( + new URL("references/realtime-video.md", skillRoot), + "utf8", + ); + assert.match(realtime, /never.*`sk_`.*browser/i); + assert.match(realtime, /client_secret/); + assert.match(realtime, /camera.*WebRTC/i); + assert.match(realtime, /Idempotency-Key/); });